Erweiterung und Anpassung Demo

This commit is contained in:
Tom 2026-10-27 23:04:51 +01:00
parent b42f9261a5
commit dc2768af71
25 changed files with 369 additions and 36 deletions

View file

@ -23,8 +23,8 @@ Voraussetzungen: `uv`, Go ≥ 1.26, `zip`, `curl`. Für die Browser-Prüfungen G
scripts/demo.sh # Server mit Beispieldaten auf http://127.0.0.1:8080; Keys stehen in .demo/keys.txt scripts/demo.sh # Server mit Beispieldaten auf http://127.0.0.1:8080; Keys stehen in .demo/keys.txt
``` ```
In der Weboberfläche mit einem der Keys anmelden, z. B. als Bert (Prüfer): unter „Prüfungen“ wartet In der Weboberfläche mit einem der Keys anmelden, z. B. als „Prüfer“: unter „Prüfungen“ wartet
`pub-munin-nutzen 1.5.0`. Als Anna (Autorin) hochladen, als Shop-Admin Personen und Keys anlegen, `pub-munin-nutzen 1.5.0`. Als „Autor“ hochladen, als Shop-Admin Personen und Keys anlegen,
als Plattform-Admin (`demo-admin`) die Systemseite. als Plattform-Admin (`demo-admin`) die Systemseite.
Mit dem CLI ein Repo abgleichen: Mit dem CLI ein Repo abgleichen:

View file

@ -19,8 +19,10 @@ Im Browser mit einem Key aus `.demo/keys.txt` anmelden:
| Key | Rolle | Was ausprobieren | | Key | Rolle | Was ausprobieren |
|---|---|---| |---|---|---|
| Bert Prüfer | Prüfer | unter „Prüfungen“ wartet `pub-munin-nutzen 1.5.0` | | Prüfer | Prüfer | unter „Prüfungen“ wartet `pub-munin-nutzen 1.5.0` |
| Anna Autorin | Autorin | Pakete anlegen und hochladen | | Prüfer 2 | Prüfer | zweite Freigabe bei Risiko „Skripte“ oder „Rechte“ |
| Admin | Prüfer + Tenant-Admin (Tenant plattform) | Freigabe bei Risiko „Rechte“ |
| Autor | Autor | Pakete anlegen und hochladen |
| Shop-Admin | Tenant-Admin | Projekte, Personen und Keys anlegen | | Shop-Admin | Tenant-Admin | Projekte, Personen und Keys anlegen |
| `demo-admin` | Plattform-Admin | Systemseite, Audit, alle Tenants | | `demo-admin` | Plattform-Admin | Systemseite, Audit, alle Tenants |
@ -65,11 +67,40 @@ opencode # oder: claude
> /pub-release-notiz 2.0 Kunden > /pub-release-notiz 2.0 Kunden
``` ```
**Pakete für den Alltag:** Die Demo veröffentlicht außerdem diese Pakete (Quellen in
`contract/examples/packages/`), alle mit Risiko „Text“:
| Paket | Art | Ausprobieren |
|---|---|---|
| `pub-schlank` | Skill | „Bau das minimal: …“ |
| `pub-doku-schreiben` | Skill | „Schreib eine README für diesen Ordner“ |
| `pub-grill-me` | Skill | „Grill me: ich will … bauen“ – eine Frage nach der anderen |
| `pub-fehlersuche` | Skill | einen fehlschlagenden Test zeigen |
| `pub-erklaeren` | Befehl | `/pub-erklaeren src/` |
| `pub-plan` | Befehl | `/pub-plan "Login per SSO"` |
| `pub-commit-text` | Befehl | nach `git add` einfach `/pub-commit-text` |
| `pub-recherche` | Subagent (lesen, Netz) | „Lass recherchieren, ab welcher Version …“ |
| `pub-doku-pruefer` | Subagent (lesen) | „Prüf die Doku gegen den Code“ |
| `pub-sicherheits-review` | Subagent (lesen) | „Sicherheitsreview der letzten Änderungen“ |
Mit dem Integrations-Key „Softwareverteilung“ (Tenant shop) holen, ohne `hugin`:
```sh
export HUGIN_KEY=<Key „Softwareverteilung“ aus .demo/keys.txt>
curl -s http://127.0.0.1:8080/api/v1/delivery/packages -H "Authorization: Bearer $HUGIN_KEY"
curl -s -X POST http://127.0.0.1:8080/api/v1/delivery/bundle -H "Authorization: Bearer $HUGIN_KEY" \
-H 'Content-Type: application/json' -o bundle.zip \
-d '{"packages":["pub-grill-me","pub-plan","pub-recherche"],"tools":{"claude":"2.1.302","opencode":"1.19.4"}}'
```
Unter Windows dasselbe mit `hugin-install.ps1 -Packages pub-grill-me,pub-plan,pub-recherche`
([Integrationen und PowerShell](http://127.0.0.1:8080/help/integration)).
**Was sich zu testen lohnt:** **Was sich zu testen lohnt:**
- Eine verwaltete Datei von Hand ändern, dann `hugin sync` → Befund „Handänderung“; - Eine verwaltete Datei von Hand ändern, dann `hugin sync` → Befund „Handänderung“;
`hugin sync --overwrite` setzt sie zurück. `hugin sync --overwrite` setzt sie zurück.
- Als Bert `pub-munin-nutzen 1.5.0` freigeben, dann `hugin check` (meldet die neue - Als „Prüfer“ `pub-munin-nutzen 1.5.0` freigeben, dann `hugin check` (meldet die neue
Version) und `hugin sync`. Version) und `hugin sync`.
- Ein Paket aus `packages` entfernen und `hugin sync` → seine Dateien verschwinden. - Ein Paket aus `packages` entfernen und `hugin sync` → seine Dateien verschwinden.
- Claude Code fragt beim ersten Start, ob es den MCP-Server aus `.mcp.json` nutzen darf. - Claude Code fragt beim ersten Start, ob es den MCP-Server aus `.mcp.json` nutzen darf.
@ -78,7 +109,7 @@ opencode # oder: claude
```sh ```sh
cd /tmp cd /tmp
export HUGIN_KEY=<Key von Anna Autorin> HUGIN_SERVER=http://127.0.0.1:8080 export HUGIN_KEY=<Key von Autor> HUGIN_SERVER=http://127.0.0.1:8080
hugin new skill pub-mein-test hugin new skill pub-mein-test
``` ```
@ -94,7 +125,7 @@ hugin try --remove # wieder aufräumen
cd /tmp && hugin upload --submit pub-mein-test # hochladen und zur Prüfung einreichen cd /tmp && hugin upload --submit pub-mein-test # hochladen und zur Prüfung einreichen
``` ```
Danach als Bert im Browser unter „Prüfungen“ freigeben. Danach als „Prüfer“ im Browser unter „Prüfungen“ freigeben.
**Linter ausprobieren:** **Linter ausprobieren:**
@ -160,7 +191,7 @@ Hand lohnt es trotzdem einmal, um das Verhalten zu kennen.
| Version `1.4.0` öffnen, Tab „Inhalt“ anklicken, dann ← → Pos1 Ende | Tabs wechseln per Tastatur ⚙ | | Version `1.4.0` öffnen, Tab „Inhalt“ anklicken, dann ← → Pos1 Ende | Tabs wechseln per Tastatur ⚙ |
| „Mein Projekt“ | Repos gruppiert; Filter „Alle“ / „Mit neuerer Version“; „Gemeldet“ als relative Zeit („vor 2 Tagen“), genaue Zeit im Tooltip | | „Mein Projekt“ | Repos gruppiert; Filter „Alle“ / „Mit neuerer Version“; „Gemeldet“ als relative Zeit („vor 2 Tagen“), genaue Zeit im Tooltip |
### 5.3 Als Bert (Prüfer) und Carla (Prüferin + Tenant-Admin) ### 5.3 Als „Prüfer“ und „Admin“ (Prüfer + Tenant-Admin)
| Schritt | Erwartung | | Schritt | Erwartung |
|---|---| |---|---|
@ -168,10 +199,10 @@ Hand lohnt es trotzdem einmal, um das Verhalten zu kennen.
| Version öffnen → „Prüfen“ → ohne Kommentar „Zurückgeben“ | Browser verlangt einen Kommentar, nichts wird abgeschickt ⚙ | | Version öffnen → „Prüfen“ → ohne Kommentar „Zurückgeben“ | Browser verlangt einen Kommentar, nichts wird abgeschickt ⚙ |
| Mit Kanal „stabil“ freigeben | zurück in „Prüfungen“; danach meldet im Test-Repo `hugin check` die neue Version | | Mit Kanal „stabil“ freigeben | zurück in „Prüfungen“; danach meldet im Test-Repo `hugin check` die neue Version |
| Danach als Shop-Admin „Überblick“ → Karte „Veraltete Installationen“ | springt zur Tabelle der Repos mit alter Version | | Danach als Shop-Admin „Überblick“ → Karte „Veraltete Installationen“ | springt zur Tabelle der Repos mit alter Version |
| Rechte-Paket: als Anna eine Skill-Version mit `extra: {claude: {allowed-tools: [Bash]}}` hochladen und einreichen, Bert gibt frei | Bert sieht im Dialog kein Kanal-Feld, sondern „Den Kanal wählt die letzte nötige Freigabe“; danach sieht Carla in „Prüfungen“ „Freigaben 1 von 2“ und „Tenant-Admin nötig“ | | Rechte-Paket: als „Autor“ eine Skill-Version mit `extra: {claude: {allowed-tools: [Bash]}}` hochladen und einreichen, „Prüfer“ gibt frei | „Prüfer“ sieht im Dialog kein Kanal-Feld, sondern „Den Kanal wählt die letzte nötige Freigabe“; danach sieht „Admin“ in „Prüfungen“ „Freigaben 1 von 2“ und „Tenant-Admin nötig“ |
| Carla gibt frei | Kanal-Feld mit Hinweis zum Risiko; danach freigegeben | | „Admin“ gibt frei | Kanal-Feld mit Hinweis zum Risiko; danach freigegeben |
### 5.4 Als Anna (Autorin) ### 5.4 Als „Autor“
| Schritt | Erwartung | | Schritt | Erwartung |
|---|---| |---|---|
@ -246,7 +277,7 @@ lässt diesen Eintrag in Ruhe):
| „Richte pub-release-notiz dauerhaft in diesem Repo ein.“ | trägt `pub-release-notiz` in `.hugin.yaml` ein und führt `hugin sync` aus (OpenCode fragt vorher nach Erlaubnis für den Befehl); **schreibt nicht selbst** nach `.opencode/commands/` | | „Richte pub-release-notiz dauerhaft in diesem Repo ein.“ | trägt `pub-release-notiz` in `.hugin.yaml` ein und führt `hugin sync` aus (OpenCode fragt vorher nach Erlaubnis für den Befehl); **schreibt nicht selbst** nach `.opencode/commands/` |
| Danach `hugin status` und `cat hugin.lock` | `pub-release-notiz` steht in beiden; die Datei `.opencode/commands/pub-release-notiz.md` hat Hugin geschrieben | | Danach `hugin status` und `cat hugin.lock` | `pub-release-notiz` steht in beiden; die Datei `.opencode/commands/pub-release-notiz.md` hat Hugin geschrieben |
| OpenCode neu starten, `/pub-release-notiz 2.0 Kunden` | der Befehl läuft | | OpenCode neu starten, `/pub-release-notiz 2.0 Kunden` | der Befehl läuft |
| Als Anna eine neue Version von `pub-release-notiz` hochladen, Bert gibt frei; dann `hugin check` | meldet die neue Version (das geht nur, weil `hugin sync` installiert hat) | | Als „Autor“ eine neue Version von `pub-release-notiz` hochladen, „Prüfer“ gibt frei; dann `hugin check` | meldet die neue Version (das geht nur, weil `hugin sync` installiert hat) |
Gegenprobe ohne `hugin` im `PATH` (z. B. `env PATH=/usr/bin:/bin opencode`): Der Agent Gegenprobe ohne `hugin` im `PATH` (z. B. `env PATH=/usr/bin:/bin opencode`): Der Agent
soll melden, dass `hugin` fehlt, und **nichts selbst schreiben**. Schreibt er die Datei soll melden, dass `hugin` fehlt, und **nichts selbst schreiben**. Schreibt er die Datei
@ -265,7 +296,7 @@ Der automatische Test dazu: `scripts/tool-smoke-test.sh`, Prüfungen „Installi
### 5.9 Lokaler MCP-Server (Python) ### 5.9 Lokaler MCP-Server (Python)
Das Beispiel `pub-zeit-lokal` braucht nur die Standardbibliothek. Als Anna im Test-Repo Das Beispiel `pub-zeit-lokal` braucht nur die Standardbibliothek. Als „Autor“ im Test-Repo
aus Abschnitt 3 (mit `tools: [opencode]` oder `[claude, opencode]`): aus Abschnitt 3 (mit `tools: [opencode]` oder `[claude, opencode]`):
```sh ```sh
@ -285,8 +316,8 @@ hugin try ~/repos/dev/hugin/contract/examples/packages/pub-zeit-lokal/1.0.0
| `hugin try --remove`, dann `hugin mcp prune --dry-run` | „würde löschen pub-zeit-lokal 1.0.0“ | | `hugin try --remove`, dann `hugin mcp prune --dry-run` | „würde löschen pub-zeit-lokal 1.0.0“ |
| `mkdir -p /tmp/hugin-mcp/eigener && hugin mcp prune` | löscht nur das Hugin-Paket; `eigener` bleibt | | `mkdir -p /tmp/hugin-mcp/eigener && hugin mcp prune` | löscht nur das Hugin-Paket; `eigener` bleibt |
Freigabe und Verteilung: als Anna `hugin upload --submit` (persönlicher Key nötig, H043 Freigabe und Verteilung: als „Autor“ `hugin upload --submit` (persönlicher Key nötig, H043
beim Projekt-Key), Risiko „Skripte“, also zwei Freigaben (Bert und Carla). Die beim Projekt-Key), Risiko „Skripte“, also zwei Freigaben („Prüfer“ und „Prüfer 2“). Die
Versionsseite zeigt „Lokaler Server“ mit Ablage. Danach im Repo `pub-zeit-lokal` unter Versionsseite zeigt „Lokaler Server“ mit Ablage. Danach im Repo `pub-zeit-lokal` unter
`packages` eintragen und `hugin sync`; `hugin check` meldet, wenn der Server auf dem `packages` eintragen und `hugin sync`; `hugin check` meldet, wenn der Server auf dem
Rechner fehlt. Rechner fehlt.
@ -298,7 +329,7 @@ lehnt der Linter mit H037 ab.
### 5.10 OpenCode-Plugin ### 5.10 OpenCode-Plugin
Das Beispiel `pub-specht-plugin` gibt OpenCode das Werkzeug `specht_marke`. Als Anna im Das Beispiel `pub-specht-plugin` gibt OpenCode das Werkzeug `specht_marke`. Als „Autor“ im
Test-Repo (`tools: [opencode]` oder `[claude, opencode]`): Test-Repo (`tools: [opencode]` oder `[claude, opencode]`):
```sh ```sh
@ -318,8 +349,8 @@ hugin try ~/repos/dev/hugin/contract/examples/packages/pub-specht-plugin/1.0.0
| `targets` auf `[claude, opencode]`, `hugin validate` | H038 | | `targets` auf `[claude, opencode]`, `hugin validate` | H038 |
| `hugin try --remove` | Ordner und Startdatei weg | | `hugin try --remove` | Ordner und Startdatei weg |
Freigabe: `hugin upload --submit`, dann Bert und **Carla** (Tenant-Admin); mit Bert und Freigabe: `hugin upload --submit`, dann „Prüfer“ und **„Admin“** (Tenant-Admin); mit „Prüfer“ und
Dora allein bleibt es bei „Tenant-Admin nötig“. Danach abonnieren und `hugin sync`. „Prüfer 2“ allein bleibt es bei „Tenant-Admin nötig“. Danach abonnieren und `hugin sync`.
### 5.11 Windows (von Hand) ### 5.11 Windows (von Hand)

View file

@ -0,0 +1,10 @@
Schlage eine Commit-Nachricht für die vorgemerkten Änderungen (git diff --cached)
vor. Committe nicht selbst.
- Erste Zeile höchstens 72 Zeichen, im Imperativ, ohne Punkt am Ende.
- Danach eine Leerzeile und ein kurzer Text: warum die Änderung nötig ist und
was sich für Nutzer ändert. Das Was steht im Diff.
- Halte dich an die Form der letzten Commits im Repo, falls sie einheitlich ist.
- Mischt der Diff mehrere Themen, schlag vor, ihn aufzuteilen.
Weitere Wünsche: $ARGUMENTS

View file

@ -0,0 +1,7 @@
id: pub-commit-text
kind: command
version: 1.0.0
owner: kern
scope: public
description: Schlägt eine Commit-Nachricht für die vorgemerkten Änderungen vor.
license: intern

View file

@ -0,0 +1,11 @@
Du prüfst Dokumentation gegen den Code. Du änderst nichts, du schreibst nur Befunde.
Prüfe:
- Befehle, Optionen, Umgebungsvariablen und Felder: gibt es sie im Code so?
- Beispiele: passen Aufrufe und Ausgaben zum aktuellen Verhalten?
- Verweise auf Dateien, Abschnitte und Adressen: gibt es das Ziel?
- Widersprüche zwischen zwei Stellen der Dokumentation.
Für jeden Befund: Datei und Zeile der Dokumentation, was nicht stimmt, die
Stelle im Code als Beleg und ein Vorschlag für den neuen Text. Ohne Befund
sag kurz, was du geprüft hast.

View file

@ -0,0 +1,12 @@
id: pub-doku-pruefer
kind: subagent
version: 1.0.0
owner: kern
scope: public
description: >
Prüft, ob Dokumentation und Code zusammenpassen: veraltete Befehle, Optionen,
Beispiele und Verweise. Nach Änderungen an Befehlen, Schnittstellen oder
Konfiguration einsetzen, bevor ein Pull Request entsteht.
capability: read
model: fast
license: intern

View file

@ -0,0 +1,30 @@
# Dokumentation schreiben
## Vor dem Schreiben
1. Kläre, wer liest: Nutzer, Entwicklerinnen im Team, Betrieb. Jede Gruppe
bekommt eigene Abschnitte, keine Mischung.
2. Suche, wo das Thema schon steht. Eine Sache steht an genau einer Stelle;
anderswo wird darauf verwiesen.
## Beim Schreiben
- Erst das Ergebnis, dann der Weg: Was kann man danach, welcher Befehl, was
kommt heraus.
- Jedes Beispiel muss so funktionieren, wie es dasteht. Befehle und Ausgaben
vorher ausprobieren oder aus dem Code ableiten.
- Kurze Sätze, aktive Form, ein Gedanke pro Absatz. Tabellen für Vergleiche,
Listen für Schritte.
- Fachbegriffe beim ersten Auftreten erklären oder auf das Glossar verweisen.
## Kommentare im Code
Kommentare erklären das Warum, nicht das Was: Annahmen, Grenzen, Gründe für
einen ungewöhnlichen Weg. Was der Code selbst sagt, kommt nicht in einen
Kommentar.
## Nach Code-Änderungen
Suche nach Namen von geänderten Befehlen, Optionen, Feldern und Fehlermeldungen
in der Dokumentation und passe jede Fundstelle an. Nenne am Ende, welche Dateien
du geändert hast und welche du geprüft, aber gelassen hast.

View file

@ -0,0 +1,11 @@
id: pub-doku-schreiben
kind: skill
version: 1.0.0
owner: kern
scope: public
description: >
Dokumentation schreiben und pflegen: README, Anleitungen, Kommentare und
Änderungshinweise. Nutze diesen Skill, wenn du Dokumentation anlegst oder
änderst, oder wenn eine Code-Änderung Verhalten ändert, das irgendwo
beschrieben ist.
license: intern

View file

@ -0,0 +1,8 @@
Erkläre {{pfad}} für jemanden, der neu im Projekt ist. Ändere nichts.
1. Wozu ist es da: ein bis zwei Sätze.
2. Wie hängt es mit dem Rest zusammen: wer ruft es auf, was ruft es auf.
3. Der Ablauf an einem typischen Beispiel, mit Verweisen auf Datei und Zeile.
4. Stolperstellen: Ungewöhnliches, Annahmen, bekannte Grenzen.
Weitere Wünsche: $ARGUMENTS

View file

@ -0,0 +1,8 @@
id: pub-erklaeren
kind: command
version: 1.0.0
owner: kern
scope: public
description: Erklärt Code, eine Datei oder einen Ordner für jemanden, der neu im Projekt ist.
arguments: [pfad]
license: intern

View file

@ -0,0 +1,34 @@
# Fehlersuche
## 1. Nachstellen
Finde den kürzesten Weg, den Fehler auszulösen: ein Test, ein Befehl, eine
Eingabe. Ohne verlässliches Nachstellen gibt es keine verlässliche Behebung.
## 2. Eingrenzen
- Lies die vollständige Fehlermeldung und den Stacktrace, von unten nach oben.
- Vergleiche mit einem Zustand, in dem es ging: anderer Commit, andere Eingabe,
anderer Rechner.
- Halbiere den Suchraum: Eingabe verkleinern, Schritte weglassen, Ausgaben an
Zwischenstellen.
## 3. Ursache belegen
Formuliere eine Vermutung und prüfe sie mit einem Experiment, bevor du etwas
änderst. Eine Vermutung ist erst die Ursache, wenn sie das beobachtete Verhalten
vollständig erklärt.
## 4. Beheben
- Behebe die Ursache, nicht das Symptom.
- Schreibe, wo möglich, zuerst einen Test, der den Fehler zeigt, und mache ihn
dann grün.
- Prüfe, ob derselbe Fehler an anderen Stellen steckt.
## Nicht
- Mehrere Dinge auf einmal ändern.
- Fehler unterdrücken, Tests abschalten oder Wartezeiten einbauen, ohne die
Ursache zu kennen.
- Nach dem zweiten erfolglosen Versuch weiterraten: dann zurück zu Schritt 2.

View file

@ -0,0 +1,10 @@
id: pub-fehlersuche
kind: skill
version: 1.0.0
owner: kern
scope: public
description: >
Fehler systematisch finden statt raten: nachstellen, eingrenzen, Ursache
belegen, dann beheben. Nutze diesen Skill bei Fehlermeldungen, fehlschlagenden
Tests, falschem Verhalten oder wenn ein erster Behebungsversuch nicht gewirkt hat.
license: intern

View file

@ -0,0 +1,31 @@
# Plan löchern
Du bist ein kritisches, aber wohlwollendes Gegenüber. Ziel ist ein gemeinsames,
lückenloses Verständnis des Vorhabens, bevor jemand Code schreibt.
## Ablauf
1. Lass dir das Vorhaben in wenigen Sätzen beschreiben, falls es noch nicht
dasteht. Lies vorhandene Unterlagen und den betroffenen Code selbst, statt
danach zu fragen.
2. Stelle **eine Frage pro Nachricht**. Warte die Antwort ab.
3. Gib zu jeder Frage deine eigene Empfehlung mit kurzer Begründung. Die Person
soll bestätigen oder widersprechen können, statt bei null anzufangen.
4. Geh dem Entscheidungsbaum nach: Klärt eine Antwort einen Zweig, frage in
diesem Zweig weiter, bis er erledigt ist, dann der nächste.
5. Hör auf, wenn keine offene Entscheidung mehr übrig ist, nicht nach einer
festen Zahl von Fragen.
## Worauf du fragst
- Ziel und Erfolg: Woran merkt man, dass es fertig und gut ist?
- Grenzen: Was gehört ausdrücklich nicht dazu?
- Randfälle, Fehlerfälle, Last, gleichzeitige Zugriffe.
- Daten: woher, wohin, wem gehören sie, was passiert mit alten Daten?
- Betrieb: Rollout, Rückweg, Überwachung, Rechte.
- Annahmen, die niemand geprüft hat.
## Am Ende
Fasse zusammen: getroffene Entscheidungen, bewusst offene Punkte, nächste
Schritte. Keine Umsetzung, solange die Person nicht ausdrücklich darum bittet.

View file

@ -0,0 +1,11 @@
id: pub-grill-me
kind: skill
version: 1.0.0
owner: kern
scope: public
description: >
Einen Plan, ein Konzept oder eine Idee gründlich löchern, bevor gebaut wird:
eine Frage nach der anderen, bis alle Lücken geklärt sind. Nutze diesen
Skill, wenn die Person „grill me“, „lös mich“, „stell mir Fragen“ oder „prüf
meinen Plan“ sagt, oder um ein Vorhaben vor der Umsetzung abzuklopfen.
license: intern

View file

@ -0,0 +1,10 @@
Plane die folgende Aufgabe, setze sie aber noch nicht um: {{aufgabe}}
1. Lies den betroffenen Code und nenne die Dateien, die sich ändern.
2. Beschreibe die Änderung in Schritten, die einzeln prüfbar sind.
3. Nenne Risiken, offene Fragen und was du bewusst weglässt.
4. Sag, wie man am Ende prüft, dass es funktioniert (Tests, Befehle).
Warte danach auf Zustimmung oder Änderungswünsche.
Weitere Wünsche: $ARGUMENTS

View file

@ -0,0 +1,8 @@
id: pub-plan
kind: command
version: 1.0.0
owner: kern
scope: public
description: Plant eine Aufgabe, bevor Code entsteht, und legt den Plan zur Abstimmung vor.
arguments: [aufgabe]
license: intern

View file

@ -0,0 +1,11 @@
Du recherchierst eine Frage und änderst nichts.
1. Suche zuerst im Projekt: Code, Dokumentation, Konfiguration.
2. Suche danach im Netz, bevorzugt in offizieller Dokumentation und in den
Quellen der Bibliothek. Achte auf die Version, die das Projekt nutzt.
3. Trenne Belegtes von Vermutetem.
Antworte knapp:
- **Antwort:** zwei bis fünf Sätze.
- **Belege:** Datei und Zeile oder Adresse, je Aussage.
- **Offen:** was du nicht klären konntest.

View file

@ -0,0 +1,12 @@
id: pub-recherche
kind: subagent
version: 1.0.0
owner: kern
scope: public
description: >
Recherchiert eine Frage im Code und im Netz und liefert eine knappe Antwort mit
Quellen. Einsetzen für Fragen zu Bibliotheken, Versionen, Fehlermeldungen oder
wie etwas im Projekt gelöst ist, damit der Hauptkontext schlank bleibt.
capability: [read, web]
model: standard
license: intern

View file

@ -0,0 +1,26 @@
# Schlank umsetzen
Ziel ist die kleinste Änderung, die die Aufgabe vollständig löst.
## Vorgehen
1. Formuliere in einem Satz, was nach der Änderung anders ist. Passt es nicht in
einen Satz, ist die Aufgabe zu groß; schlage eine Aufteilung vor.
2. Suche, was es schon gibt: vorhandene Funktionen, Bibliotheken des Projekts,
bestehende Muster. Wiederverwenden schlägt neu schreiben.
3. Ändere nur, was die Aufgabe verlangt. Kein Aufräumen nebenbei, kein
Umbenennen, keine Formatierung fremder Zeilen.
4. Prüfe am Ende jede neue Zeile: Würde etwas fehlen, wenn sie weg wäre?
## Lass weg
- Optionen, Parameter und Konfiguration, die heute niemand braucht.
- Abstraktionen für einen einzigen Fall (Interface mit einer Umsetzung, Fabrik
für ein Objekt).
- Fehlerbehandlung für Fälle, die nicht eintreten können.
- Neue Abhängigkeiten, wenn die Standardbibliothek reicht.
## Sag es
Siehst du sinnvolle Erweiterungen, nenne sie am Ende in einer kurzen Liste
„Bewusst weggelassen“, statt sie einzubauen.

View file

@ -0,0 +1,11 @@
id: pub-schlank
kind: skill
version: 1.0.0
owner: kern
scope: public
description: >
Schlank umsetzen: die kleinste Änderung, die die Aufgabe löst, ohne Vorrat für
später. Nutze diesen Skill, wenn du Code schreibst oder änderst und die Person
„minimal“, „lean“, „einfach“ oder „nur das Nötige“ sagt, oder wenn eine Lösung
größer wird als die Aufgabe.
license: intern

View file

@ -0,0 +1,12 @@
Du prüfst Änderungen auf Sicherheitslücken. Du änderst nichts, du schreibst nur Befunde.
Achte besonders auf:
- Eingaben von außen: Injection (SQL, Shell, Pfade), fehlende Prüfung, Größen.
- Anmeldung und Rechte: fehlende Prüfungen, Rechte ausweiten, fremde Daten lesen.
- Geheimnisse: Keys im Code, in Logs oder Fehlermeldungen.
- Dateien und Befehle: Pfade außerhalb des erlaubten Ordners, Ausführen von Eingaben.
- Abhängigkeiten: neue Pakete, unsichere Versionen.
Für jeden Befund: Datei und Zeile, Schweregrad (hoch, mittel, niedrig), ein
konkretes Angriffsszenario und ein Vorschlag zur Behebung. Melde nur, was du am
Code belegen kannst; Vermutungen kennzeichnest du als solche.

View file

@ -0,0 +1,12 @@
id: pub-sicherheits-review
kind: subagent
version: 1.0.0
owner: kern
scope: public
description: >
Prüft Änderungen auf Sicherheitslücken: Eingaben, Rechte, Geheimnisse,
Abhängigkeiten. Einsetzen vor dem Merge von Änderungen an Anmeldung, Rechten,
Schnittstellen nach außen oder beim Umgang mit Dateien und Befehlen.
capability: read
model: thorough
license: intern

View file

@ -11,6 +11,13 @@
# veröffentlicht die Beispielpakete und schreibt alle Keys nach .demo/keys.txt. # veröffentlicht die Beispielpakete und schreibt alle Keys nach .demo/keys.txt.
set -euo pipefail set -euo pipefail
RESET=""
case "${1:-}" in
"") ;;
--neu|--new) RESET=1 ;;
*) echo "Unbekannt: $1 (erlaubt: --neu)"; exit 2 ;;
esac
ROOT="$(cd "$(dirname "$0")/.." && pwd)" ROOT="$(cd "$(dirname "$0")/.." && pwd)"
DEMO="$ROOT/.demo" DEMO="$ROOT/.demo"
PORT="${PORT:-8080}" PORT="${PORT:-8080}"
@ -19,12 +26,12 @@ mkdir -p "$DEMO"
if [[ -n "${HUGIN_DATABASE_URL:-}" ]]; then if [[ -n "${HUGIN_DATABASE_URL:-}" ]]; then
# Eigene Datenbank (z. B. PostgreSQL): die Keys gehören zu dieser Datenbank # Eigene Datenbank (z. B. PostgreSQL): die Keys gehören zu dieser Datenbank
KEYS="$DEMO/keys-$(printf '%s' "$HUGIN_DATABASE_URL" | shasum | cut -c1-8).txt" KEYS="$DEMO/keys-$(printf '%s' "$HUGIN_DATABASE_URL" | shasum | cut -c1-8).txt"
if [[ "${1:-}" == "--neu" ]]; then if [[ -n "$RESET" ]]; then
echo "--neu mit eigener Datenbank: Daten dort selbst löschen, z. B. docker compose down -v"; exit 2 echo "--neu mit eigener Datenbank: Daten dort selbst löschen, z. B. docker compose down -v"; exit 2
fi fi
else else
# Nur die SQLite-Demo verwerfen; Keys anderer Datenbanken (keys-*.txt) bleiben # Nur die SQLite-Demo verwerfen; Keys anderer Datenbanken (keys-*.txt) bleiben
[[ "${1:-}" == "--neu" ]] && rm -f "$DEMO"/hugin.db* "$DEMO/keys.txt" "$DEMO/server.log" [[ -n "$RESET" ]] && rm -f "$DEMO"/hugin.db* "$DEMO/keys.txt" "$DEMO/server.log"
export HUGIN_DATABASE_URL="sqlite:///$DEMO/hugin.db" export HUGIN_DATABASE_URL="sqlite:///$DEMO/hugin.db"
KEYS="$DEMO/keys.txt" KEYS="$DEMO/keys.txt"
fi fi
@ -61,9 +68,10 @@ else
done done
person() { api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/$1/people" -d "$2" | field key; } person() { api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/$1/people" -d "$2" | field key; }
key() { api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/$1/keys" -d "$2" | field key; } key() { api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/$1/keys" -d "$2" | field key; }
ANNA=$(person plattform '{"name":"Anna Autorin","mail":"anna@firma.de","project":"kern","roles":["pub_author"]}') AUTHOR=$(person plattform '{"name":"Autor","mail":"autor@firma.de","project":"kern","roles":["pub_author"]}')
BERT=$(person plattform '{"name":"Bert Prüfer","mail":"bert@firma.de","roles":["pub_reviewer"]}') REVIEWER=$(person plattform '{"name":"Prüfer","mail":"pruefer@firma.de","roles":["pub_reviewer"]}')
CARLA=$(person plattform '{"name":"Carla Admin","mail":"carla@firma.de","roles":["pub_reviewer","tenant_admin"]}') REVIEWER2=$(person plattform '{"name":"Prüfer 2","mail":"pruefer2@firma.de","roles":["pub_reviewer"]}')
ADMIN_PERSON=$(person plattform '{"name":"Admin","mail":"admin@firma.de","roles":["pub_reviewer","tenant_admin"]}')
SHOP_ADMIN=$(key shop '{"kind":"tenant","name":"Shop-Admin"}') SHOP_ADMIN=$(key shop '{"kind":"tenant","name":"Shop-Admin"}')
SHOP_CI=$(key shop '{"kind":"project","name":"Shop CI","project":"api"}') SHOP_CI=$(key shop '{"kind":"project","name":"Shop CI","project":"api"}')
SHOP_INT=$(key shop '{"kind":"integration","name":"Softwareverteilung"}') SHOP_INT=$(key shop '{"kind":"integration","name":"Softwareverteilung"}')
@ -71,14 +79,19 @@ else
publish() { publish() {
local zip="$DEMO/package.zip" local zip="$DEMO/package.zip"
rm -f "$zip"; (cd "$ROOT/contract/examples/packages/$1" && zip -qr "$zip" .) rm -f "$zip"; (cd "$ROOT/contract/examples/packages/$1" && zip -qr "$zip" .)
curl -fsS -H "Authorization: Bearer $ANNA" -H "Content-Type: application/zip" --data-binary "@$zip" "$URL/api/v1/drafts" >/dev/null curl -fsS -H "Authorization: Bearer $AUTHOR" -H "Content-Type: application/zip" --data-binary "@$zip" "$URL/api/v1/drafts" >/dev/null
local base="$URL/api/v1/packages/${1%/*}/versions/${1#*/}" local base="$URL/api/v1/packages/${1%/*}/versions/${1#*/}"
api -H "Authorization: Bearer $ANNA" -X POST "$base/submit" >/dev/null api -H "Authorization: Bearer $AUTHOR" -X POST "$base/submit" >/dev/null
[[ "${2:-}" == "open" ]] || api -H "Authorization: Bearer $BERT" "$base/approve" -d '{"comment":"Demo"}' >/dev/null [[ "${2:-}" == "open" ]] || api -H "Authorization: Bearer $REVIEWER" "$base/approve" -d '{"comment":"Demo"}' >/dev/null
} }
for p in pub-munin-nutzen/1.4.0 pub-hausregeln/1.0.0 pub-munin-anbindung/1.0.0 pub-release-notiz/1.0.0 pub-code-review/1.0.0; do for p in pub-munin-nutzen/1.4.0 pub-hausregeln/1.0.0 pub-munin-anbindung/1.0.0 pub-release-notiz/1.0.0 pub-code-review/1.0.0; do
publish "$p" publish "$p"
done done
# Pakete für den Alltag: Skills, Befehle und Subagenten zum Ausprobieren
for p in pub-schlank pub-doku-schreiben pub-grill-me pub-fehlersuche pub-erklaeren pub-plan pub-commit-text \
pub-recherche pub-doku-pruefer pub-sicherheits-review; do
publish "$p/1.0.0"
done
publish pub-munin-nutzen/1.5.0 open # liegt bei den Prüfungen publish pub-munin-nutzen/1.5.0 open # liegt bei den Prüfungen
curl -fsS "${ADMIN[@]}" -X PUT -H "Content-Type: application/json" "$URL/api/v1/admin/mandatory/pub-munin-anbindung" -d '{"tenant":null}' curl -fsS "${ADMIN[@]}" -X PUT -H "Content-Type: application/json" "$URL/api/v1/admin/mandatory/pub-munin-anbindung" -d '{"tenant":null}'
api -H "Authorization: Bearer $SHOP_CI" "$URL/api/v1/usage" \ api -H "Authorization: Bearer $SHOP_CI" "$URL/api/v1/usage" \
@ -89,9 +102,10 @@ Hugin-Demo — Keys zum Anmelden unter $URL
(nur für diese lokale Demo; .demo/ löschen oder scripts/demo.sh --neu) (nur für diese lokale Demo; .demo/ löschen oder scripts/demo.sh --neu)
Plattform-Admin (alles, System) $HUGIN_ADMIN_TOKEN Plattform-Admin (alles, System) $HUGIN_ADMIN_TOKEN
Anna Autorin (pub_author, Person) $ANNA Autor (pub_author, Person) $AUTHOR
Bert Prüfer (pub_reviewer, Person) $BERT Prüfer (pub_reviewer, Person) $REVIEWER
Carla Admin (Prüferin + Admin, Person) $CARLA Prüfer 2 (pub_reviewer, Person) $REVIEWER2
Admin (Prüfer + Tenant-Admin, Person) $ADMIN_PERSON
Shop-Admin (Tenant shop) $SHOP_ADMIN Shop-Admin (Tenant shop) $SHOP_ADMIN
Shop CI (Projekt shop/api, lesen) $SHOP_CI Shop CI (Projekt shop/api, lesen) $SHOP_CI
Softwareverteilung (Integration shop) $SHOP_INT Softwareverteilung (Integration shop) $SHOP_INT

View file

@ -61,10 +61,10 @@ AUTHOR=$(api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/plattform/keys" -d '{"kind
REVIEWER=$(api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/plattform/keys" -d '{"kind":"project","name":"pruefer","project":"kern","roles":["pub_reviewer"]}' | key) REVIEWER=$(api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/plattform/keys" -d '{"kind":"project","name":"pruefer","project":"kern","roles":["pub_reviewer"]}' | key)
READER=$(api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/plattform/keys" -d '{"kind":"project","name":"leser","project":"kern"}' | key) READER=$(api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/plattform/keys" -d '{"kind":"project","name":"leser","project":"kern"}' | key)
# Lokaler Server hat Risiko „Skripte“: persönlicher Key, zwei Freigaben von Personen # Lokaler Server hat Risiko „Skripte“: persönlicher Key, zwei Freigaben von Personen
# Plugin hat Risiko „Rechte“: eine der zwei Freigaben vom Tenant-Admin (Carla) # Plugin hat Risiko „Rechte“: eine der zwei Freigaben vom Tenant-Admin
person() { api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/plattform/people" -d "{\"name\":\"$1\",\"mail\":\"$1@smoke.test\",\"project\":\"kern\",\"roles\":$2}" | key; } person() { api "${ADMIN[@]}" "$URL/api/v1/admin/tenants/plattform/people" -d "{\"name\":\"$1\",\"mail\":\"$1@smoke.test\",\"project\":\"kern\",\"roles\":$2}" | key; }
ANNA=$(person anna '["pub_author"]'); BERT=$(person bert '["pub_reviewer"]'); DORA=$(person dora '["pub_reviewer"]') PERSON_AUTHOR=$(person autor '["pub_author"]'); PERSON_REVIEWER=$(person pruefer '["pub_reviewer"]')
CARLA=$(person carla '["pub_reviewer","tenant_admin"]') PERSON_REVIEWER2=$(person pruefer2 '["pub_reviewer"]'); PERSON_ADMIN=$(person admin '["pub_reviewer","tenant_admin"]')
# MCP-Paket zeigt auf diesen Hugin selbst; so lässt sich auch die Verbindung prüfen # MCP-Paket zeigt auf diesen Hugin selbst; so lässt sich auch die Verbindung prüfen
PACKAGES="$WORK/packages" PACKAGES="$WORK/packages"
@ -95,8 +95,8 @@ publish() {
publish_example() { publish_example() {
local zip="$WORK/package.zip" id=$1 base="$URL/api/v1/packages/$1/versions/1.0.0"; shift local zip="$WORK/package.zip" id=$1 base="$URL/api/v1/packages/$1/versions/1.0.0"; shift
rm -f "$zip"; (cd "$ROOT/contract/examples/packages/$id/1.0.0" && zip -qr "$zip" .) rm -f "$zip"; (cd "$ROOT/contract/examples/packages/$id/1.0.0" && zip -qr "$zip" .)
curl -fsS -H "Authorization: Bearer $ANNA" -H "Content-Type: application/zip" --data-binary "@$zip" "$URL/api/v1/drafts" >/dev/null curl -fsS -H "Authorization: Bearer $PERSON_AUTHOR" -H "Content-Type: application/zip" --data-binary "@$zip" "$URL/api/v1/drafts" >/dev/null
api -H "Authorization: Bearer $ANNA" -X POST "$base/submit" >/dev/null api -H "Authorization: Bearer $PERSON_AUTHOR" -X POST "$base/submit" >/dev/null
for r in "$@"; do api -H "Authorization: Bearer $r" "$base/approve" -d '{"comment":"Rauchtest"}' >/dev/null; done for r in "$@"; do api -H "Authorization: Bearer $r" "$base/approve" -d '{"comment":"Rauchtest"}' >/dev/null; done
} }
@ -105,9 +105,9 @@ IDS=(pub-smoke-skill pub-smoke-manual pub-smoke-rules pub-smoke-command pub-smok
for id in "${IDS[@]}"; do publish "$id"; done for id in "${IDS[@]}"; do publish "$id"; done
# Lokaler MCP-Server (Python, nur Standardbibliothek): hugin sync installiert ihn nach # Lokaler MCP-Server (Python, nur Standardbibliothek): hugin sync installiert ihn nach
# HUGIN_MCP_HOME, die Werkzeuge starten ihn über hugin mcp run # HUGIN_MCP_HOME, die Werkzeuge starten ihn über hugin mcp run
publish_example pub-zeit-lokal "$BERT" "$DORA"; IDS+=(pub-zeit-lokal) publish_example pub-zeit-lokal "$PERSON_REVIEWER" "$PERSON_REVIEWER2"; IDS+=(pub-zeit-lokal)
# OpenCode-Plugin mit eigenem Werkzeug; Claude Code bekommt davon nichts # OpenCode-Plugin mit eigenem Werkzeug; Claude Code bekommt davon nichts
publish_example pub-specht-plugin "$BERT" "$CARLA"; IDS+=(pub-specht-plugin) publish_example pub-specht-plugin "$PERSON_REVIEWER" "$PERSON_ADMIN"; IDS+=(pub-specht-plugin)
export HUGIN_MCP_HOME="$WORK/mcp" ZEIT_ZONE_HINWEIS=smoke export HUGIN_MCP_HOME="$WORK/mcp" ZEIT_ZONE_HINWEIS=smoke
(cd "$ROOT/cli" && go build -o "$WORK/bin/hugin" ./cmd/hugin) (cd "$ROOT/cli" && go build -o "$WORK/bin/hugin" ./cmd/hugin)
ln -s "$WORK/bin/hugin" "$WORK/hugin" ln -s "$WORK/bin/hugin" "$WORK/hugin"

View file

@ -23,6 +23,9 @@ def ids(p: Package, context: Context = Context()) -> list[str]:
@pytest.mark.parametrize("name", [ @pytest.mark.parametrize("name", [
"pub-munin-nutzen/1.4.0", "pub-munin-nutzen/1.5.0", "pub-hausregeln/1.0.0", "pub-munin-anbindung/1.0.0", "pub-munin-nutzen/1.4.0", "pub-munin-nutzen/1.5.0", "pub-hausregeln/1.0.0", "pub-munin-anbindung/1.0.0",
"pub-release-notiz/1.0.0", "pub-code-review/1.0.0", "pub-zeit-lokal/1.0.0", "pub-specht-plugin/1.0.0", "pub-release-notiz/1.0.0", "pub-code-review/1.0.0", "pub-zeit-lokal/1.0.0", "pub-specht-plugin/1.0.0",
"pub-schlank/1.0.0", "pub-doku-schreiben/1.0.0", "pub-grill-me/1.0.0", "pub-fehlersuche/1.0.0",
"pub-erklaeren/1.0.0", "pub-plan/1.0.0", "pub-commit-text/1.0.0",
"pub-recherche/1.0.0", "pub-doku-pruefer/1.0.0", "pub-sicherheits-review/1.0.0",
]) ])
def test_examples_are_error_free(name): def test_examples_are_error_free(name):
assert error_free(validate(example(name))) assert error_free(validate(example(name)))