diff --git a/docs/superpowers/specs/2026-05-06-meeting-end-auto-import-design.md b/docs/superpowers/specs/2026-05-06-meeting-end-auto-import-design.md new file mode 100644 index 0000000..a896eb8 --- /dev/null +++ b/docs/superpowers/specs/2026-05-06-meeting-end-auto-import-design.md @@ -0,0 +1,232 @@ +# Meeting-End Auto-Import — Design Spec + +**Datum:** 2026-05-06 +**Status:** Draft → Review + +## Ziel + +`/meeting-end` soll Recap (Teams/Copilot) und Transkript automatisch via MS Graph holen, mit der Meeting-Notiz mergen, und die Agenda fortschreiben. Manueller Trigger, semi-automatische Agenda-Rückführung mit Konfidenz-Schwelle. + +## Kontext + +**Bestehend:** +- Slash-Command `/meeting-end` (interaktiv, ohne Auto-Import) +- Skill `jour-fixe.md` definiert Lifecycle (🆕 Neu → 📌 Dauerläufer → ✅ Archiv / ⏸️ Postponed) +- Node-Scripts `scripts/lib/o365-calendar.js` mit MSAL Client-Credentials Flow, MS Graph Client, Calendar.Read +- Meeting-Notizen tragen `o365_id` (Einzel-Event-ID) im Frontmatter +- Agenda.md trägt nur `serie: ` — kein Link zum Outlook-Serientermin + +**Neu erforderlich:** +- `o365_series_id` Frontmatter-Feld in Agenda.md (= `seriesMasterId`) +- Node-Skript `fetch-meeting-artifacts.js` für Recap + Transkript +- Erweiterung `/meeting-end` um Auto-Import-Schritt + +## Entscheidungen (aus Brainstorming) + +| Punkt | Entscheidung | +|-------|--------------| +| Quelle | Doppel-Pass: Copilot-Recap + Rohtranskript | +| Trigger | Manuell (`/meeting-end`) | +| Meeting-ID | Primär `o365_id` aus Frontmatter, Fallback Auto-Match | +| Recap-API | `aiInsights` (Beta) testen, sonst manueller Paste-Fallback | +| Agenda-Rückführung | Hybrid: eindeutig auto, Grauzone zur Bestätigung | +| Notiz-Merge | Mergen oder Skippen bei Duplikaten (Live-Notizen behalten) | +| Serien-Link | Neues Frontmatter-Feld `o365_series_id` in Agenda.md | + +## Architektur + +``` +[/meeting-end] + ↓ +[Claude] identifiziert Meeting-Notiz (current_note / Argument) + ↓ +[fetch-meeting-artifacts.js] + ├─ liest o365_id (oder Args) + ├─ MS Graph: resolve OnlineMeeting + ├─ MS Graph: GET .../transcripts (.vtt) + ├─ MS Graph (Beta): GET .../aiInsights + └─ Output JSON: { transcript, recap?, meeting } + ↓ +[Claude] Doppel-Pass-Synthese + ├─ Recap → Struktur (Topics, Entscheidungen, Tasks) + ├─ Transkript → Validierung + Detail-Lücken + └─ Merge mit Live-Notizen aus Notiz, Dedup + ↓ +[Claude] Agenda-Rückführung mit Konfidenz-Heuristik + ├─ klar erledigt → archivieren (auto) + ├─ klar offen → Historie ergänzen (auto) + ├─ klar postponed → verschieben (auto) + └─ unklar → Liste zur Bestätigung + ↓ +[Bericht] Zusammenfassung der Aktionen +``` + +## Komponenten + +### 1. `scripts/fetch-meeting-artifacts.js` (neu) + +**CLI:** +``` +node fetch-meeting-artifacts.js \ + --o365-id \ + [--include-transcript] [--include-recap] \ + [--out ] + +# Fallback ohne o365-id: +node fetch-meeting-artifacts.js \ + --date 2026-05-06 --title-regex "Jour Fixe IT Team" +``` + +**Auth:** bestehender MSAL-Client-Credentials-Flow. + +**Erforderliche Graph-Permissions** (Application): +- `Calendars.Read` (vorhanden) +- `OnlineMeetings.Read.All` (neu) — über Application Access Policy auf `AZURE_USER_EMAIL` +- `OnlineMeetingTranscript.Read.All` (neu) +- ggf. `OnlineMeetingArtifact.Read.All` für Recap + +**Ablauf:** +1. Lade Calendar-Event via `o365_id` → extrahiere `joinUrl`/`joinWebUrl` +2. Resolve OnlineMeeting via `GET /users/{userId}/onlineMeetings?$filter=JoinWebUrl eq '...'` +3. Fetch Transkripte: `GET /users/{userId}/onlineMeetings/{id}/transcripts` +4. Lade Inhalte (.vtt) → parse zu plain text mit Sprecher-Tags +5. Versuche Recap (`aiInsights` Beta-Endpoint) — bei 403/404 → `recap: null` (kein Fehler) +6. Output JSON: + ```json + { + "meeting": { "id", "subject", "start", "end", "organizer", "attendees" }, + "transcript": "speaker: text\nspeaker: text\n...", + "recap": { "decisions": [], "openQuestions": [], "topics": [...], "tasks": [] } | null + } + ``` + +**Tests** (`scripts/test/fetch-meeting-artifacts.test.js`): +- vtt-Parser: zeitstempel-zu-plain mit Sprechern +- Resolve OnlineMeeting via JoinUrl: Mock-Response +- Recap-Fallback bei 404 +- CLI-Args-Parsing + +### 2. `o365_series_id` Frontmatter in Agenda.md + +**Migration:** +- Einmal-Skript `scripts/backfill-series-ids.js` (optional): liest jede Agenda.md, sucht im Calendar nach `seriesMasterId` für die Serie (Match per `serie:`-Feld + bekanntem Titel-Pattern), trägt ein. +- Alternativ: lazy beim ersten `/meeting` einer Serie — wenn `o365_series_id` leer, Claude liest `seriesMasterId` aus aktuellem Event und schreibt es ein. + +**Verwendung bei `/meeting-end`:** +- Aus Meeting-Notiz `o365_id` → Calendar-Event lesen → `seriesMasterId` extrahieren +- Match gegen `o365_series_id` aller Agenda.md-Dateien → eindeutige Serie identifizieren + +### 3. `/meeting-end` Command (überarbeitet) + +**Neue Schritte (vor bestehender Logik):** + +0. **Auto-Import:** + a. Identifiziere Meeting-Notiz (current_note / Args). + b. Lies `o365_id` aus Frontmatter. + c. Falls vorhanden → `fetch-meeting-artifacts.js --o365-id `. + d. Falls leer → Auto-Match per Datum+Serie, sonst Frage an User. + e. Falls `recap === null` → User-Prompt: "Kein Recap aus Graph. Bitte Teams-Recap einfügen oder mit nur Transkript fortfahren?" + +1. **Doppel-Pass-Synthese:** + - Lies bestehende Meeting-Notiz (Live-Notizen). + - Aus Recap: extrahiere Topics, Entscheidungen, offene Fragen, Tasks. + - Aus Transkript: pro Recap-Aussage Validierung; ergänze Details, die im Recap fehlen aber wichtig sind (Schwelle: 2+ Sprecher zum Thema). + - Merge mit Live-Notizen: Duplikate skippen (Heuristik: gleicher Aktor + gleiche Kernaussage = Duplikat). Im Zweifel beibehalten. + - Schreibe Sektionen `## Notizen`, `## Entscheidungen`, `## Offene Fragen`, `## Aufgaben` der Meeting-Notiz. + +2. **Agenda-Rückführung mit Konfidenz-Heuristik:** + + Pro Topic im Meeting-Notiz Agenda-Block: + + | Signal im Recap/Transkript | Konfidenz | Aktion | + |---|---|---| + | "erledigt" / "abgeschlossen" / "✅" + alle Tasks done | HOCH | auto-archive | + | "verschoben" / "postponed" / "vertagt" | HOCH | auto-postpone | + | Topic erwähnt, kein Abschluss-Marker | HOCH | auto-update Historie | + | Topic nicht erwähnt | HOCH | unverändert lassen | + | Mehrdeutig | NIEDRIG | zur Bestätigung listen | + +3. **Sammel-Bestätigung:** Liste aller HOCH-Konfidenz-Aktionen + Liste der NIEDRIG-Konfidenz-Topics zur Entscheidung. User bestätigt einmal für alle HOCH + entscheidet pro NIEDRIG. + +4. **Ausführung** (wie bisheriger jour-fixe-Skill): Historie schreiben, Archivieren, Postponed verschieben, neue Topics zu Dauerläufern. + +5. **Status:** `status: abgeschlossen`. + +6. **Bericht:** "Meeting [Serie] vom [Datum] abgeschlossen. Recap: ✓/✗. Transkript: ✓. X Topics aktualisiert (auto), Y archiviert (auto), Z postponed (auto), W brauchten Bestätigung." + +### 4. Merge-Heuristik Details + +**Notizen-Merge:** +- Live-Notizen aus existierenden `## Notizen`/`## Entscheidungen`/`## Aufgaben` werden zuerst eingelesen. +- Recap+Transkript-Output wird Item-weise verglichen: + - **Skip** wenn Live-Eintrag mit gleichem Aktor + gleicher Kernaussage existiert. + - **Mergen** wenn Live-Eintrag stub ist (z.B. nur Stichwort), Recap-Detail ergänzt. + - **Anhängen** wenn neu. +- Reihenfolge erhalten: Live-Reihenfolge bleibt, neue Recap-Items hinten dran. + +**Aufgaben-Merge:** +- Match per Owner + Kernhandlung. Bei Match: bestehender Eintrag, ggf. Owner ergänzen. +- Tasks ohne Owner aus Recap → fragen oder in `(unzugeordnet)` parken. + +## Datenfluss-Beispiel + +``` +User: /meeting-end +→ current_note = 03 Bereiche/Meetings/2026-05-06 Jour Fixe IT Team.md +→ Read frontmatter: o365_id = AAMk... + +Claude → node scripts/fetch-meeting-artifacts.js --o365-id AAMk... + Output: { meeting, transcript: "...", recap: {...} } + +Claude → liest existing Notiz (Live-Notizen vorhanden in ## Notizen) + → Doppel-Pass-Synthese + → schreibt aktualisierte Notiz + +Claude → liest 03 Bereiche/Jour Fixe/IT Team/Agenda IT Team.md + → matcht Topics aus Notiz vs Agenda + → klassifiziert Konfidenz + → präsentiert Sammel-Liste: + "Auto: 5 Topics aktualisiert, 2 archiviert, 1 postponed. + Bestätigung nötig: 'NAC-Projekt' — erledigt oder offen?" + → wartet auf User-Input für unklare + → schreibt Agenda + Archiv + +Claude → setzt status: abgeschlossen + → Bericht. +``` + +## Error Handling + +| Fehler | Verhalten | +|---|---| +| MSAL-Auth fehlgeschlagen | Abbruch, klare Fehlermeldung mit Hinweis auf .env | +| `o365_id` fehlt + kein Match | User fragen: o365-id manuell eintragen oder ohne Auto-Import fortfahren | +| Transkript nicht verfügbar (Meeting läuft noch / nicht aufgezeichnet) | Wenn Recap da: nur Recap nutzen. Sonst: Hinweis + manuelle Fortführung | +| `aiInsights` 403/404 | Recap=null, weiter mit Transkript-only oder User-Paste | +| Graph-Rate-Limit | Backoff + Retry (3x, exponential) | +| Mehrere Transkripte | nimm das längste / aktuellste | +| User-Abbruch bei Sammel-Bestätigung | Notiz schon gespeichert, Agenda unverändert, Status nicht gesetzt | + +## Testplan + +**Unit:** +- vtt-Parser +- OnlineMeeting-Resolver per JoinUrl +- Konfidenz-Heuristik (Topic-Klassifikation aus Recap-Text) +- Merge-Dedup für Notizen + Aufgaben + +**Integration:** +- Echtes Meeting (read-only): fetch + parse, kein Schreiben +- Dry-Run Mode (`--dry-run`): zeigt was geschrieben würde, ohne Datei-Änderungen + +**E2E manuell:** +- Echtes Jour Fixe IT Team durchlaufen, dann `/meeting-end` +- Überprüfen: Notiz-Merge sauber, Agenda korrekt fortgeschrieben, Archiv enthält erledigte Topics + +## Offene Fragen + +- **Graph-Permissions:** Hat die bestehende Azure-App `OnlineMeetings.Read.All` + `OnlineMeetingTranscript.Read.All` und Application Access Policy auf `AZURE_USER_EMAIL`? Falls nein → Tenant-Admin Aktion erforderlich. +- **`aiInsights`-Verfügbarkeit im Tenant:** muss empirisch getestet werden (Beta-Endpoint, abhängig von Teams Premium/Copilot-Lizenz). +- **Backfill von `o365_series_id`:** Einmal-Skript bauen oder lazy beim nächsten `/meeting` jeder Serie? +- **Sprecher-Mapping:** Transkript liefert E-Mail/Display-Name — sollen wir gleich gegen `00 Kontext/Personen/` matchen und Wikilinks setzen? +- **Recordings:** sollen Aufzeichnungen ebenfalls verlinkt werden (Anhang im Frontmatter), oder reine Transkript+Recap-Lösung?