# Izg Decision Sheet

> Bündelt viele Entscheidungsfragen in ein Dokument, das der User ausserhalb der CLI in einem HTML-Renderer beantwortet und als Antwort-Datei zurückgibt. Statt zehn AskUserQuestion-Runden ein Sheet. Dieser Skill sollte verwendet werden, wenn mehr als drei Entscheidungen offen sind, wenn der User sie am Stück oder in Ruhe beantworten will (Fragenkatalog, Entscheidungsliste, Sheet, Fragebogen), oder wenn eine exportierte .answers.json eingelesen werden soll.

- Skill: `is-noname/izg-decision-sheet` (Agent Skill, multi-file: 14 files)
- Install (CLI): `npx skillmds@latest add is-noname/izg-decision-sheet`
- Raw SKILL.md: https://api.skillmd.com/api/skills/is-noname/izg-decision-sheet/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: is-noname (https://skillmd.com/u/is-noname)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/is-noname/izg-decision-sheet

---


# Decision Sheet

Entscheidungsfragen verlassen die CLI: Agent schreibt ein Sheet, der User beantwortet
es im Browser, die Antworten kommen als kompakte JSON zurück.

Drei bewegliche Teile:

| Teil | Wo | Wer |
|------|-----|-----|
| Sheet `<slug>.jsonl` | `<projekt>/.decisions/` | Agent schreibt |
| Renderer `index.html` | im Skill; global gespiegelt nach `~/ai-shared/izg-decision-sheet/` | User bedient |
| Antworten `<slug>.answers.json` | `<projekt>/.decisions/` | User exportiert |

`assets/index.html` **nie direkt lesen** — reine Renderer-Vorlage fuer `render_sheet.py`,
ohne Informationswert fuer dich. Bei Renderfehlern die Fehlermeldung von `render_sheet.py`
lesen, nicht die HTML.

Die Scripts im Skill funktionieren überall — auch auf einem System, auf dem der
globale Setup-Schritt nie gelaufen ist. Die Hooks sind Komfort, keine Voraussetzung:
sie sparen dir pro Sheet ein paar Tool-Calls, mehr nicht. Du musst nicht wissen, ob
sie da sind — der Ablauf ist mit und ohne derselbe.

### Scripts im Überblick

| Script | Aufruf | Ausgabe |
|--------|--------|---------|
| `render_sheet.py` | `python3 <skill>/scripts/render_sheet.py .decisions/<slug>.jsonl` | Validiert das Sheet, meldet alle Verstöße auf einmal, baut die HTML und öffnet das Fenster |
| `fetch_answers.py` | `python3 <skill>/scripts/fetch_answers.py [--slug <slug>]` | Holt die neueste `.answers.json` aus dem Download-Ordner nach `.decisions/`, ruft `resolve_answers.py` mit auf und gibt die aufgelöste Entscheidungstabelle aus |
| `resolve_answers.py` | `python3 <skill>/scripts/resolve_answers.py .decisions/<slug>.jsonl .decisions/<slug>.answers.json` | Führt Sheet und Antwort-Datei zusammen, Zeile pro Frage mit Wert und Herkunft |

Quelltext dieser Scripts nur bei einem echten Fehler im Script selbst lesen — für den
normalen Ablauf genügt die Tabelle.

## Wann dieser Skill statt AskUserQuestion

Ab etwa vier offenen Entscheidungen, oder sobald Fragen voneinander abhängen
(`dep`). Bei einer oder zwei Fragen ist AskUserQuestion schneller.

---

## Modus 1: Sheet schreiben

Vorab nichts nachsehen — kein `ls`, kein `mkdir`: `Write` legt `.decisions/` mit an,
`<skill>` ist der Pfad aus der Zeile "Base directory for this skill" über dieser SKILL.md.

`.decisions/<slug>.jsonl` per `Write` anlegen — ein JSON-Objekt pro Zeile, erste Zeile Header:

```
{"v":1,"sheet":"ticketsystem-v2","title":"Ticketsystem v2","ctx":"docs/RFC-20260727-001.md"}
{"id":"id-vergabe","q":"IDs per Counter oder Timestamp?","t":"pick","o":["Counter","Timestamp","beides"],"d":"Counter","why":"kollisionsfrei, braucht aber Lockfile"}
{"id":"prefix-registry","q":"Prefix-Registry global lassen?","t":"yn","d":"y"}
{"id":"archiv-name","q":"Wie soll der Archiv-Ordner heissen?","t":"text","d":"archiv"}
{"id":"schreibrechte","q":"Welche Agents dürfen Tickets schreiben?","t":"multi","o":["claude","codex","gemini","vibe"],"d":["claude","codex"]}
{"id":"lock-mechanismus","q":"Lock via flock oder mkdir?","t":"pick","o":["flock","mkdir"],"dep":["id-vergabe","Counter"]}
{"id":"migration-modus","q":"Migrations-Skript synchron oder als Job?","t":"pick","o":["synchron","Job"],"ctx":"Bestand: 40k Zeilen in ticket_legacy, Migration lief 2025 zuletzt bei ticket_archive - synchron dauerte dort 6min und blockte den Import."}
```

Dateiname und das `sheet`-Feld im Header müssen identisch sein
(`.decisions/ticketsystem-v2.jsonl` zu `"sheet":"ticketsystem-v2"`): `render_sheet.py`
nimmt den Slug aus `head['sheet']`, der Dateiname ist nur Fallback, während
`fetch_answers.py --slug` nach dem Dateinamen der Antwort-Datei sucht — laufen beide
auseinander, findet der Rückweg nichts.

Danach **immer** diesen einen Befehl — unabhängig davon, ob die Hooks eingerichtet
sind, und egal ob du die Datei per Write, Edit oder Bash-Heredoc angelegt hast:

```bash
python3 <skill>/scripts/render_sheet.py .decisions/<slug>.jsonl
```

Das Script validiert das Sheet (JSON pro Zeile, doppelte ids, fehlende Optionen,
kaputte oder zyklische `dep`-Verweise) und nennt dabei **alle** Verstöße auf einmal,
nicht nur den ersten. Danach baut es die HTML und öffnet das Fenster — immer, ob
Hooks eingerichtet sind oder nicht. Der Stop-Hook merkt das und hält sich raus.

Bricht es mit einer Fehlermeldung ab: Sheet korrigieren, nicht das Script umgehen.
Der Aufruf ist auch mit Hook kein Leerlauf — er ist deine einzige Rückmeldung, dass
das Sheet überhaupt valide ist, bevor dein Turn endet.

Liegt der Skill nicht im Projekt, tut es der globale Spiegel:
`python3 ~/ai-shared/izg-decision-sheet/render_sheet.py …` — identisches Script.

Gilt es doch mal nicht: der Skill im Projekt kam mit deinem Pull, der Spiegel vom
letzten Setup-Lauf auf dieser Maschine. Das Script nimmt deshalb die Kopie neben sich
und meldet eine Zeile, wenn der Spiegel davon abweicht — dann `setup_global_conventions.sh`
erneut laufen lassen, sonst arbeitet der Hook weiter mit dem alten Stand.

### Feldreferenz

<!-- Erzeugt aus scripts/sheet_spec.py: python3 scripts/sheet_spec.py --fields
     Nicht von Hand ändern — ein Test vergleicht diesen Abschnitt mit der Spec. -->

| Feld | Pflicht | Bedeutung |
|------|---------|-----------|
| `id` | ja | Sprechender Kurzname, eindeutig — keine durchlaufende Zahl (Einschub wuerde sonst alle folgenden IDs verschieben) |
| `q` | ja | Fragetext, eine Zeile |
| `t` | nein | `pick` (default) · `multi` · `yn` · `text` |
| `o` | bei `pick`/`multi` | Optionen. Bei `yn` implizit ja/nein |
| `d` | nein | **Empfehlung** — im Renderer vorausgewählt und als `EMPF` markiert. Bei `yn`: `"y"`/`"n"`. Bei `multi`: Array |
| `why` | nein | Eine Zeile Begründung/Trade-off unter der Frage |
| `ctx` | nein | Längerer Hintergrund zu **dieser einen Frage** — im Renderer eingeklappt hinter „+ Kontext", nicht standardmäßig sichtbar. Nicht zu verwechseln mit dem Header-`ctx` (Dokumentverweis fürs ganze Sheet) |
| `dep` | nein | `[id, wert]` oder `[id, [wert1, wert2]]` — Frage wird nur aktiv, wenn die andere so beantwortet ist |

Header: `v` (Format-Version, aktuell 1), `sheet` (Slug = Dateiname ohne Endung), `title` (Überschrift im Renderer), `ctx` (Pfad zum Dokument mit dem Hintergrund, optional).

### Regeln beim Schreiben

1. **Immer ein `d` setzen, wenn du eine Meinung hast.** Das ist der Punkt des
   Formats: der User bestätigt schweigend und beantwortet nur, wo er abweicht.
   Nur bei echt offenen Fragen (Namen, Zahlen, Präferenzen) `d` weglassen — die
   markiert der Renderer als „offen".
2. **`why` nur wenn es das Trade-off wirklich klärt.** Eine Zeile, kein Absatz.
   Braucht eine Frage mehr Hintergrund als eine Zeile — z.B. weil sie ohne
   Projekt-Detail (Bestandsgröße, letzter Vorfall, betroffene Komponente) nicht
   beantwortbar ist — gehört der in das Fragen-`ctx`, nicht in `why` gequetscht.
   Frage bleibt dadurch kurz, der Kontext ist trotzdem einen Klick entfernt statt
   in `why` aufgebläht oder ganz weggelassen.
3. **Eine Zeile pro Frage, kein Pretty-Print.** Zeilenumbrüche im JSON zerstören
   den Parser (ein Objekt = eine Zeile ist die einzige Regel des Formats).
4. **Fragen sortieren:** grundlegende zuerst, Folgefragen per `dep` dahinter.
   Nie eine `dep`-Frage vor der Frage, an der sie hängt. IDs tragen selbst keine
   Reihenfolge — ein nachträglicher Einschub ist ein Edit von einer Zeile, nie
   ein Neuschreiben des ganzen Sheets.
5. **Keine Fragen stellen, die du selbst entscheiden kannst.** Ein Sheet mit 20
   Trivialitäten ist schlimmer als drei gute Fragen in der CLI.
6. **Bei `multi` keine „keine/reicht so"-Pseudo-Option.** Nichts ausgewählt bedeutet
   dort schon „keine" — eine zusätzliche Verneinungs-Option macht das Ergebnis
   zweideutig (leeres Array vs. abgewählte Pseudo-Option lesen sich gleich).
   Wenn „nichts davon" eine echte Antwort ist, gehört die Frage als `yn` davor.

---

## Modus 2: Antworten lesen

Der User tippt `#answers` im Chat. Der `UserPromptSubmit`-Hook holt die neueste
`*.answers.json` aus dem Download-Ordner, verschiebt sie nach `.decisions/` und
legt den Inhalt in den Kontext. Optional mit Slug: `#answers ticketsystem-v2`.

**Kommt auf `#answers` nichts zurück**, ist der Hook auf diesem System nicht
eingerichtet. Dann holst du die Datei selbst — dasselbe Script, das der Hook aufruft:

```bash
python3 <skill>/scripts/fetch_answers.py [--slug <slug>]
```

Sag dem User in dem Fall einmal, dass er dir nach dem Export kurz Bescheid gibt,
statt auf die Automatik zu warten.

### Interpretation: nicht selbst rechnen

Die Antwort-Datei ist komprimiert (übernommene Empfehlungen fehlen, `dep`-gefilterte
Fragen tauchen nie auf) und **ohne das Sheet bedeutungslos** — beides zusammenführen
tut `resolve_answers.py`, nicht du:

```bash
python3 <skill>/scripts/resolve_answers.py .decisions/<slug>.jsonl .decisions/<slug>.answers.json
```

`fetch_answers.py` (und damit auch `#answers`) ruft es selbst auf, sobald das Sheet
danebenliegt — du bekommst die aufgelöste Tabelle statt der Rohdatei. Jede Zeile nennt
Frage, effektiven Wert und Herkunft (`Empfehlung` / `gewaehlt` / `offen`); mit `!`
markierte Zeilen und die `ACHTUNG`-Zeile am Ende sind Fragen ohne belastbare
Entscheidung — die klärst du, **bevor** du sie umsetzt. Notizen sind verbindlich, nicht
Deko.

Nur wenn das Sheet fehlt, kommt die Rohdatei — dann gilt: Key fehlt = Empfehlung `d`
übernommen, `{"a":wert,"n":…}` = Antwort plus Notiz, `{"a":null,"n":…}` = keine Auswahl,
nur eine Rückfrage, `[]` bei `multi` = echtes „nichts davon". Das Sheet liegt in dem Fall
meist trotzdem als `.decisions/<slug>.jsonl` da — lesen statt raten.

Die Notiz steht bewusst im Objekt und nicht als `[wert, notiz]`-Tupel: eine
`multi`-Antwort mit zwei Optionen (`["claude","codex"]`) wäre sonst nicht von
Antwort-plus-Notiz zu unterscheiden. Antwort-Dateien tragen bewusst keine Fragetexte
mit, damit der Rückweg billig bleibt.

Nach dem Umsetzen: `.decisions/` gehört in die `.gitignore` des Projekts, die Sheets
sind Wegwerf-Artefakte. Was dauerhaft gilt, gehört als ADR (`doc-ids`) oder in
`CONTEXT.md` — nicht ins Sheet.

---

## Setup

**Pro Projekt: nichts.** `.decisions/` wird beim ersten Sheet angelegt und gehört in
die `.gitignore`. Der Skill-Pull bringt alles mit, was der Ablauf braucht —
`assets/index.html`, `scripts/`, `hooks/`.

**Global: optional, aber empfohlen.** Ein Schritt, einmal pro Maschine:

```bash
bash ~/Dokumente/AI/ai-SKILL-set/scripts/setup_global_conventions.sh ~/.claude
```

Spiegelt Renderer und Scripts nach `~/ai-shared/izg-decision-sheet/` und registriert zwei
Hooks in `~/.claude/settings.json`:

| Hook | Event | Wirkung |
|------|-------|---------|
| `izg-decision-sheet-open.sh` | `Stop` | Sheet, das an `render_sheet.py` vorbei entstanden ist, geht trotzdem auf |
| `izg-decision-answers.sh` | `UserPromptSubmit` | `#answers` holt die Antworten zurück |

Was die Hooks bringen: der Hinweg funktioniert auch dann, wenn das Sheet an
`render_sheet.py` vorbei entstanden ist, und der Rückweg kostet dich keinen Tool-Call.
Ohne sie läuft alles gleich, nur mit zwei bis drei Aufrufen mehr pro Sheet.

### Ohne globales Setup — und andere Agents (Codex, Vibe, Gemini)

Die Hooks sind Claude-spezifisch, das Format und die Scripts sind es nicht. Der
komplette Ablauf funktioniert aus dem gepullten Skill heraus:

`<skill>` löst sich agent-abhängig auf: bei Claude ist das der Pfad aus der Zeile
"Base directory for this skill" über dieser SKILL.md. Codex, Vibe und Gemini haben
diese Zeile nicht — dort gilt `.claude/skills/izg-decision-sheet/scripts/...` im
Projekt, sonst der globale Spiegel `~/ai-shared/izg-decision-sheet/...`, wo die
Scripts flach ohne `scripts`-Unterordner liegen.

1. Sheet nach `.decisions/<slug>.jsonl` schreiben (identisches Format).
2. `python3 <skill>/scripts/render_sheet.py .decisions/<slug>.jsonl` — öffnet das
   Fenster selbst — mit der `assets/index.html` neben sich, der globale Spiegel
   spielt dabei keine Rolle.
3. Nach dem Export `python3 <skill>/scripts/fetch_answers.py` — holt die Datei aus
   dem Download-Ordner nach `.decisions/` und gibt die aufgelöste Entscheidungstabelle
   aus (`resolve_answers.py` läuft mit, ohne Hook und ohne Zutun).

## Wenn etwas nicht funktioniert

| Symptom | Ursache |
|---------|---------|
| Script meldet „gerendert", aber es geht kein Fenster auf | `xdg-open` fehlt oder hat keinen Browser zugeordnet — der Pfad steht in der Meldung, manuell öffnen |
| Sheet geschrieben, gar nichts passiert | `render_sheet.py` nicht aufgerufen — genau dafür ist der Aufruf Pflicht, auch mit Hook |
| `render_sheet.py`: keine index.html gefunden | Skill unvollständig gepullt (`assets/` fehlt) und kein globaler Spiegel da |
| „globaler Spiegel weicht vom Skill ab" | Skill-Kopie und `~/ai-shared/izg-decision-sheet/` sind auseinandergelaufen — gerendert wird korrekt mit der Skill-Kopie, aber der Hook nutzt den alten Spiegel: `setup_global_conventions.sh ~/.claude` erneut laufen lassen |
| Hook meldet „Spiegel unvollständig" | Im Spiegel fehlt eine Datei (typisch nach einem Setup-Lauf vor einer neuen Script-Datei) — dasselbe Kommando behebt es |
| Renderer zeigt Dropzone statt Fragen | Sheet defekt — Fehlermeldung steht in der Box darunter |
| `#answers` bringt nichts | Hook nicht eingerichtet → `fetch_answers.py` selbst aufrufen; oder Export noch nicht gespeichert (nur „Kopieren" gedrückt) |
| Export ist `{"sheet":…,"a":{}}` | Kein Fehler — der User hat alle Empfehlungen übernommen |
| `fetch_answers.py` gibt die Rohdatei statt der Tabelle aus | Das Sheet liegt nicht als `.decisions/<slug>.jsonl` daneben (anderer Slug?) — `resolve_answers.py` mit beiden Pfaden selbst aufrufen |

Letzter Ausweg ganz ohne Scripts: `assets/index.html` im Browser öffnen, Sheet
reinziehen, exportierte Datei selbst nach `.decisions/` legen und den Pfad nennen.

