brain/docs/superpowers/specs/2026-05-06-meeting-end-auto...

11 KiB

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: <Name> — 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
Sprecher-Mapping Transkript-Sprecher gegen 00 Kontext/Personen/ matchen, Wikilinks setzen

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 <eventId> \
  [--include-transcript] [--include-recap] \
  [--out <file.json>]

# 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:
    {
      "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):

  1. Auto-Import: a. Identifiziere Meeting-Notiz (current_note / Args). b. Lies o365_id aus Frontmatter. c. Falls vorhanden → fetch-meeting-artifacts.js --o365-id <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?"

  2. 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.
  3. 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
  4. 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.

  5. Ausführung (wie bisheriger jour-fixe-Skill): Historie schreiben, Archivieren, Postponed verschieben, neue Topics zu Dauerläufern.

  6. Status: status: abgeschlossen.

  7. Bericht: "Meeting [Serie] vom [Datum] abgeschlossen. Recap: ✓/✗. Transkript: ✓. X Topics aktualisiert (auto), Y archiviert (auto), Z postponed (auto), W brauchten Bestätigung."

4. Sprecher-Mapping (scripts/lib/speaker-matcher.js)

Input: Sprecher-Liste aus Transkript (Display-Name + E-Mail aus Graph-Metadaten).

Match-Strategie (analog person-matcher.js):

  1. E-Mail-Match: Suche email im Frontmatter aller 00 Kontext/Personen/*.md.
  2. Name-Fallback: Vergleiche Display-Name mit vorname + nachname.
  3. Kein Match: Display-Name als Plain Text behalten, in offene Fragen aufnehmen ("Person X anlegen?").

Output: Map { "Christian Kauer" → "[[00 Kontext/Personen/Christian Kauer (KRAH)]]", "Frank Herberg" → "[[00 Kontext/Personen/Frank Herberg]]", ... }.

Verwendung:

  • In Notizen-Merge: Sprecher-Erwähnungen durch Wikilinks ersetzen ("Frank hat berichtet..." → "Frank Herberg hat berichtet...").
  • In Aufgaben: Owner-Namen zu Wikilinks.
  • Bestehende person-matcher.js Logik wiederverwenden.

Tests:

  • E-Mail-Match
  • Name-Match mit Kurzformen (Christian → Christian Kauer (KRAH))
  • Kein Match → Plain Text + Hinweis

5. 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

Bestätigt

  • Graph-Permissions: User ergänzt im Azure Portal.
  • Teams Premium / Copilot-Lizenz vorhanden → aiInsights sollte funktionieren.
  • Sprecher-Mapping ist Teil des Scopes.

Backfill o365_series_id

Gezielt für IT Team und LANdata beim Aufsetzen eintragen (manuell via Skript-Aufruf oder beim ersten Test). Andere Serien: lazy beim nächsten /meeting der jeweiligen Serie automatisch eintragen.

Falls Aufzeichnung verfügbar (/users/{id}/onlineMeetings/{id}/recordings):

  • URL als recording_url im Frontmatter der Meeting-Notiz speichern.
  • Kein Download, nur Verlinkung — Zugriff erfolgt via Teams.