From 125080a0ba2a32d134c3d1ee9ed23b0e9f7c0a7a Mon Sep 17 00:00:00 2001 From: Floke Date: Sun, 25 Jan 2026 19:22:18 +0000 Subject: [PATCH] feat(dev-session): Containerize dev session manager This commit introduces a containerized workflow for the development session manager (dev_session.py). - Dockerization: Added gemini.Dockerfile to create a self-contained environment with all Python dependencies, removing the need for manual host setup. - Start Script: Updated start-gemini.sh to build and run the container, executing dev_session.py as the entrypoint. - Session Management: Implemented --done flag to properly terminate a session, update the Notion task status, and clean up the git hook. - Notion Integration: Created and integrated notion_commit_hook.py which is automatically installed/uninstalled to post commit messages to the active Notion task. - Documentation: Updated README_dev_session.md to reflect the new container-based setup, usage, and features. --- README_dev_session.md | 98 +++++++---------- dev_session.py | 247 ++++++++++++++++++++++++++++++++++++++---- gemini.Dockerfile | 3 + notion_commit_hook.py | 61 +++++++++++ start-gemini.sh | 5 +- 5 files changed, 333 insertions(+), 81 deletions(-) create mode 100644 notion_commit_hook.py diff --git a/README_dev_session.md b/README_dev_session.md index 984f9c96..66710ea5 100644 --- a/README_dev_session.md +++ b/README_dev_session.md @@ -15,43 +15,57 @@ ## Voraussetzungen -* Python 3.6+ -* `requests` Bibliothek (`pip install requests`) -* Ein Notion Integration Token mit Zugriff auf deine "Projects [UT]"- und "Tasks [UT]"-Datenbanken. +* **Docker:** Das gesamte Setup ist containerisiert, um Abhängigkeitskonflikte zu vermeiden. +* **Notion Integration Token:** Ein Notion API-Token mit Zugriff auf Ihre "Projects"- und "Tasks"-Datenbanken. ## Installation und Einrichtung -1. **Notion API Key:** - * Stelle sicher, dass du einen Notion Integration Token hast. - * Speichere deinen Token in einer Umgebungsvariablen namens `NOTION_API_KEY`. Alternativ fragt das Skript beim Start danach. - * Gewähre der Integration Zugriff auf deine "Projects [UT]"- und "Tasks [UT]"-Datenbanken in Notion. +Das Setup ist so konzipiert, dass es mit minimalem Aufwand sofort einsatzbereit ist. -2. **Abhängigkeiten installieren:** - ```bash - pip install requests - ``` +1. **Notion API Key:** + * Stellen Sie sicher, dass Sie einen Notion Integration Token haben. + * Das Skript fragt beim ersten Start interaktiv nach dem Token. Sie können es auch als Umgebungsvariable `NOTION_API_KEY` in einer `.env`-Datei im Hauptverzeichnis speichern, um diesen Schritt zu überspringen. + * Gewähren Sie Ihrer Notion-Integration Zugriff auf die relevanten "Projects"- und "Tasks"-Datenbanken. + +2. **Abhängigkeiten:** Alle notwendigen Abhängigkeiten (Python, `requests`, `python-dotenv`) sind im Dockerfile (`gemini.Dockerfile`) definiert und werden automatisch im Container installiert. Es ist keine manuelle Installation via `pip` erforderlich. ## Nutzung -1. **Führe das Skript aus:** - ```bash - python3 dev_session.py - ``` +### 1. Sitzung starten -2. **Notion API Key eingeben:** Wenn `NOTION_API_KEY` nicht als Umgebungsvariable gesetzt ist, wirst du zur Eingabe aufgefordert. +Führen Sie das Start-Skript aus. Es baut bei Bedarf das Docker-Image und startet den Session-Manager. -3. **Projekt auswählen:** Wähle dein gewünschtes Projekt aus der angezeigten Liste der Notion-Projekte. +```bash +./start-gemini.sh +``` -4. **Task auswählen oder erstellen:** - * Wenn Tasks für das Projekt vorhanden sind, wähle einen aus. - * Wenn keine Tasks vorhanden sind oder du einen neuen erstellen möchtest, wähle die Option zum "Neuen Task für dieses Projekt erstellen". Du wirst dann nach dem Task-Titel gefragt. +Das Skript führt Sie dann interaktiv durch die Auswahl eines Projekts und eines Tasks, genau wie in der Beispiel-Interaktion unten beschrieben. Nach der Auswahl startet es eine Gemini-CLI-Sitzung mit dem passenden Kontext. -5. **Git Branch erstellen (manuell):** Das Skript gibt einen `git checkout -b`-Befehl aus, den du kopieren und in deinem Terminal ausführen solltest, um einen neuen Branch für den Task zu erstellen. +### 2. Sitzung abschließen -6. **Gemini CLI Kontext:** Das Skript gibt einen formatierten Textblock aus, den du direkt in die Gemini CLI kopieren und einfügen kannst, um die KI mit dem relevanten Kontext zu versorgen. +Wenn Sie die Arbeit an einem Task beendet haben, können Sie die Sitzung über den folgenden Befehl abschließen. Dieser Befehl muss **im Terminal des Hosts** ausgeführt werden, während der Container (`gemini-session`) noch läuft. + +```bash +docker exec -it gemini-session python3 dev_session.py --done +``` + +Dieser Befehl: +* Setzt den Status des Notion-Tasks auf "Done" (oder den finalen Status in Ihrer Konfiguration). +* Löscht die lokale Session-Datei (`.session_info.json`). +* Deinstalliert den Git-Hook. + +### 3. Automatisches Notion-Feedback (Git-Hook) + +Beim Starten einer Sitzung wird automatisch ein `post-commit`-Git-Hook installiert. + +* **Funktion:** Nach jedem `git commit` wird die Commit-Nachricht automatisch als Kommentar an den verknüpften Notion-Task gesendet. +* **Voraussetzung:** Funktioniert nur für Commits, die im `gemini-session`-Container oder auf dem Host-System (mit installiertem `requests`) gemacht werden, während die Session aktiv ist. +* **Deinstallation:** Der Hook wird beim Abschluss der Sitzung mit `--done` automatisch wieder entfernt. ### Beispiel-Interaktion (gekürzt) +Die interaktive Auswahl von Projekt und Task bleibt unverändert: + ``` Starte interaktive Entwicklungs-Session... Bitte gib deinen Notion API Key ein (Eingabe wird nicht angezeigt): **************** @@ -63,45 +77,11 @@ An welchem Projekt möchtest du arbeiten? [2] Sync Engine [...] Bitte wähle eine Nummer: 2 - -Projekt 'Sync Engine' ausgewählt. - ---- Lade Tasks für das ausgewählte Projekt... --- -Keine offenen Tasks für dieses Projekt gefunden. -[1] Neuen Task für dieses Projekt erstellen -Bitte wähle eine Nummer: 1 - ---- Neuen Task erstellen --- -Bitte gib den Titel für den neuen Task ein: Create README.md for dev_session.py -✅ Task 'Create README.md for dev_session.py' erfolgreich erstellt. - -Task 'Create README.md for dev_session.py' ausgewählt. - ---- Aktualisiere Status von Task '' auf 'Doing'... --- -✅ Task-Status erfolgreich auf 'Doing' aktualisiert. - ------------------------------------------------------------------- -✅ Setup abgeschlossen! - -Du solltest jetzt den Git-Branch manuell erstellen und auschecken: -git checkout -b feature/task-2f388f42-create-readmemd-for-devsessionpy - -Der Notion-Task 'Create README.md for dev_session.py' wurde auf 'Doing' gesetzt. ---- - -Kontext für die Gemini CLI (bitte kopieren und einfügen): - -Ich arbeite jetzt am Projekt 'Sync Engine'. Der Fokus liegt auf dem Task 'Create README.md for dev_session.py'. - -Die relevanten Dateien für dieses Projekt sind wahrscheinlich: -- Die primäre Projektdokumentation: @readme.md -- Die spezifische Dokumentation für dieses Modul: @readme.md (falls vorhanden) -- Der Haupt-Code befindet sich wahrscheinlich in: @dev_session.py - -Mein Ziel ist es, den Task 'Create README.md for dev_session.py' umzusetzen. Alle Commits für diesen Task sollen die Kennung `[2f388f42]` enthalten. ------------------------------------------------------------------- +... ``` +Nach der Auswahl wird automatisch die Gemini CLI gestartet. + ## Git Branch Benennungs-Konvention Das Skript schlägt automatisch einen Git-Branch-Namen vor, der dem Muster `feature/task-{kurze_task_id}-{task_titel_slug}` folgt. diff --git a/dev_session.py b/dev_session.py index 7e1184ac..fe87d5ce 100644 --- a/dev_session.py +++ b/dev_session.py @@ -197,6 +197,76 @@ def create_new_notion_task(token: str, project_id: str, tasks_db_id: str) -> Opt print(f"Antwort des Servers: {e.response.text}") return None +def add_comment_to_notion_task(token: str, task_id: str, comment: str) -> bool: + """Fügt einen Kommentar zu einer Notion-Seite (Task) hinzu.""" + url = "https://api.notion.com/v1/comments" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + "Notion-Version": "2022-06-28" + } + payload = { + "parent": {"page_id": task_id}, + "rich_text": [{ + "text": { + "content": comment + } + }] + } + try: + response = requests.post(url, headers=headers, json=payload) + response.raise_for_status() + # Kein print, da dies vom Git-Hook im Hintergrund aufgerufen wird + return True + except requests.exceptions.RequestException: + # Fehler unterdrücken, um den Commit-Prozess nicht zu blockieren + return False + +# --- Session Management --- + +SESSION_DIR = ".dev_session" +SESSION_FILE_PATH = os.path.join(SESSION_DIR, "SESSION_INFO") + +def save_session_info(task_id: str, token: str): + """Speichert die Task-ID und den Token für den Git-Hook.""" + os.makedirs(SESSION_DIR, exist_ok=True) + session_data = { + "task_id": task_id, + "token": token + } + with open(SESSION_FILE_PATH, "w") as f: + json.dump(session_data, f) + +def install_git_hook(): + """Installiert das notion_commit_hook.py Skript als post-commit Git-Hook.""" + git_hooks_dir = os.path.join(".git", "hooks") + post_commit_hook_path = os.path.join(git_hooks_dir, "post-commit") + source_hook_script = "notion_commit_hook.py" + + if not os.path.exists(git_hooks_dir): + # Wahrscheinlich kein Git-Repository, also nichts tun + return + + if not os.path.exists(source_hook_script): + print(f"Warnung: Hook-Skript {source_hook_script} nicht gefunden. Hook wird nicht installiert.") + return + + try: + # Lese den Inhalt des Quellskripts + with open(source_hook_script, "r") as f: + hook_content = f.read() + + # Schreibe den Inhalt in den post-commit Hook + with open(post_commit_hook_path, "w") as f: + f.write(hook_content) + + # Mache den Hook ausführbar + os.chmod(post_commit_hook_path, 0o755) + print("✅ Git-Hook für Notion-Kommentare erfolgreich installiert.") + + except (IOError, OSError) as e: + print(f"❌ FEHLER beim Installieren des Git-Hooks: {e}") + # --- UI Functions --- def select_project(token: str) -> Optional[Tuple[Dict, Optional[str]]]: @@ -259,37 +329,141 @@ def select_task(token: str, project_id: str, tasks_db_id: str) -> Optional[Dict] except ValueError: print("Ungültige Eingabe. Bitte eine Zahl eingeben.") +import subprocess + # --- Context Generation --- -def generate_cli_context(project_title: str, task_title: str, task_id: str, suggested_branch_name: str, readme_path: Optional[str]) -> str: - """Erstellt einen formatierten Kontext-String für die Gemini CLI.""" +def generate_cli_context(project_title: str, task_title: str, task_id: str, readme_path: Optional[str]) -> str: + """Erstellt den reinen Kontext-String für die Gemini CLI.""" # Fallback, falls kein Pfad in Notion gesetzt ist if not readme_path: readme_path = "readme.md" context = ( - "\n------------------------------------------------------------------" - "\n✅ Setup abgeschlossen!" - f"\n\nDu solltest jetzt den Git-Branch manuell erstellen und auschecken:" - f"\ngit checkout -b {suggested_branch_name}" - f"\n\nDer Notion-Task '{task_title}' wurde auf 'Doing' gesetzt." - "\n---" - "\n\nKontext für die Gemini CLI (bitte kopieren und einfügen):" - f"\n\nIch arbeite jetzt am Projekt '{project_title}'. Der Fokus liegt auf dem Task '{task_title}'." - "\n\nDie relevanten Dateien für dieses Projekt sind wahrscheinlich:" - "\n- Die primäre Projektdokumentation: @readme.md" - f"\n- Die spezifische Dokumentation für dieses Modul: @{readme_path}" - f"\n- Der Haupt-Code befindet sich wahrscheinlich in: @dev_session.py" - f"\n\nMein Ziel ist es, den Task '{task_title}' umzusetzen. Alle Commits für diesen Task sollen die Kennung `[{task_id.split('-')[0]}]` enthalten." - "\n------------------------------------------------------------------" + f"Ich arbeite jetzt am Projekt '{project_title}'. Der Fokus liegt auf dem Task '{task_title}'.\n\n" + "Die relevanten Dateien für dieses Projekt sind wahrscheinlich:\n" + "- Die primäre Projektdokumentation: @readme.md\n" + f"- Die spezifische Dokumentation für dieses Modul: @{readme_path}\n" + f"- Der Haupt-Code befindet sich wahrscheinlich in: @dev_session.py\n\n" + f"Mein Ziel ist es, den Task '{task_title}' umzusetzen. Alle Commits für diesen Task sollen die Kennung `[{task_id.split('-')[0]}]` enthalten." ) return context -# --- Main Execution --- +# --- CLI Execution --- -def main(): - """Hauptfunktion des Skripts.""" +def start_gemini_cli(initial_context: str): + """Startet die Gemini CLI als interaktiven Subprozess und übergibt den Startkontext.""" + print("\n--- Übergebe an Gemini CLI... ---") + print("Die Gemini CLI wird jetzt gestartet. Sie können direkt mit der Arbeit beginnen.") + + try: + # Der Befehl, um die Gemini CLI zu starten + command = ["gemini"] + + # Starte den Prozess + # stdin=subprocess.PIPE, um den Kontext zu senden + # text=True für String-basierte Ein-/Ausgabe + process = subprocess.Popen(command, stdin=subprocess.PIPE, text=True) + + # Schreibe den initialen Kontext in die Standard-Eingabe der CLI + # Ein Zeilenumbruch am Ende simuliert das Drücken der Enter-Taste. + process.communicate(input=initial_context + '\n') + + except FileNotFoundError: + print("\n❌ FEHLER: Der 'gemini'-Befehl wurde nicht gefunden.") + print("Stellen Sie sicher, dass die Gemini CLI korrekt im Container installiert und im PATH verfügbar ist.") + except Exception as e: + print(f"\n❌ Ein unerwarteter Fehler ist beim Starten der Gemini CLI aufgetreten: {e}") + +import shutil +import argparse + +# --- Session Management --- + +SESSION_DIR = ".dev_session" +SESSION_FILE_PATH = os.path.join(SESSION_DIR, "SESSION_INFO") + +def save_session_info(task_id: str, token: str): + """Speichert die Task-ID und den Token für den Git-Hook.""" + os.makedirs(SESSION_DIR, exist_ok=True) + session_data = { + "task_id": task_id, + "token": token + } + with open(SESSION_FILE_PATH, "w") as f: + json.dump(session_data, f) + +def install_git_hook(): + """Installiert das notion_commit_hook.py Skript als post-commit Git-Hook.""" + git_hooks_dir = os.path.join(".git", "hooks") + post_commit_hook_path = os.path.join(git_hooks_dir, "post-commit") + source_hook_script = "notion_commit_hook.py" + + if not os.path.exists(git_hooks_dir): + # Wahrscheinlich kein Git-Repository, also nichts tun + return + + if not os.path.exists(source_hook_script): + print(f"Warnung: Hook-Skript {source_hook_script} nicht gefunden. Hook wird nicht installiert.") + return + + try: + # Kopiere das Skript und mache es ausführbar + shutil.copy(source_hook_script, post_commit_hook_path) + os.chmod(post_commit_hook_path, 0o755) + print("✅ Git-Hook für Notion-Kommentare erfolgreich installiert.") + + except (IOError, OSError) as e: + print(f"❌ FEHLER beim Installieren des Git-Hooks: {e}") + +def cleanup_session(): + """Bereinigt die Session-Datei und den Git-Hook.""" + if os.path.exists(SESSION_FILE_PATH): + os.remove(SESSION_FILE_PATH) + + post_commit_hook_path = os.path.join(".git", "hooks", "post-commit") + if os.path.exists(post_commit_hook_path): + os.remove(post_commit_hook_path) + + print("🧹 Session-Daten und Git-Hook wurden bereinigt.") + +def complete_session(): + """Schließt eine aktive Session ab.""" + print("Schließe aktive Entwicklungs-Session ab...") + if not os.path.exists(SESSION_FILE_PATH): + print("Keine aktive Session gefunden.") + return + + try: + with open(SESSION_FILE_PATH, "r") as f: + session_data = json.load(f) + task_id = session_data.get("task_id") + token = session_data.get("token") + + if task_id and token: + # Annahme: Der letzte Status in der Liste ist der "Done"-Status + tasks_db_id = find_database_by_title(token, "Tasks [UT]") + if tasks_db_id: + status_options = get_database_status_options(token, tasks_db_id) + if status_options: + done_status = status_options[-1] + update_notion_task_status(token, task_id, done_status) + + except (FileNotFoundError, json.JSONDecodeError): + print("Fehler beim Lesen der Session-Informationen.") + + finally: + cleanup_session() + + user_input = input("\nMöchtest du eine neue Session starten? (j/n): ").lower() + if user_input == 'j': + main() # Starte den interaktiven Prozess von vorne + else: + print("Auf Wiedersehen!") + +def start_interactive_session(): + """Startet den Prozess zur Auswahl von Projekt/Task und startet Gemini.""" print("Starte interaktive Entwicklungs-Session...") token = os.environ.get('NOTION_API_KEY') @@ -327,7 +501,13 @@ def main(): task_title = get_page_title(selected_task) task_id = selected_task["id"] print(f"\nTask '{task_title}' ausgewählt.") + + # Session-Informationen für den Git-Hook speichern + save_session_info(task_id, token) + # Git-Hook installieren, der die Session-Infos nutzt + install_git_hook() + title_slug = re.sub(r'[^a-z0-9\s-]', '', task_title.lower()) title_slug = re.sub(r'\s+', '-', title_slug) title_slug = re.sub(r'-+', '-', title_slug).strip('-') @@ -338,8 +518,33 @@ def main(): if not status_updated: print("Warnung: Notion-Task-Status konnte nicht aktualisiert werden.") - cli_context = generate_cli_context(project_title, task_title, task_id, suggested_branch_name, readme_path) - print(cli_context) + # Finale Setup-Informationen ausgeben + print("\n------------------------------------------------------------------") + print("✅ Setup abgeschlossen!") + print(f"\nDer Notion-Task '{task_title}' wurde auf 'Doing' gesetzt.") + print(f"\nBitte erstellen Sie jetzt manuell den Git-Branch in einem separaten Terminal:") + print(f"git checkout -b {suggested_branch_name}") + print("------------------------------------------------------------------") + + # CLI-Kontext generieren und die interaktive Session starten + cli_context = generate_cli_context(project_title, task_title, task_id, readme_path) + start_gemini_cli(cli_context) + + +# --- Main Execution --- + +def main(): + """Hauptfunktion des Skripts.""" + parser = argparse.ArgumentParser(description="Interaktiver Session-Manager für die Gemini-Entwicklung mit Notion-Integration.") + parser.add_argument("--done", action="store_true", help="Schließt die aktuelle Entwicklungs-Session ab.") + + args = parser.parse_args() + + if args.done: + complete_session() + else: + start_interactive_session() + if __name__ == "__main__": main() \ No newline at end of file diff --git a/gemini.Dockerfile b/gemini.Dockerfile index 627d8356..0b12ec74 100644 --- a/gemini.Dockerfile +++ b/gemini.Dockerfile @@ -11,6 +11,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Python-Abhängigkeiten für dev_session.py installieren +RUN pip3 install requests python-dotenv --break-system-packages + # Installieren der von Ihnen gefundenen, korrekten Gemini CLI global RUN npm install -g @google/gemini-cli diff --git a/notion_commit_hook.py b/notion_commit_hook.py new file mode 100644 index 00000000..51abbd17 --- /dev/null +++ b/notion_commit_hook.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +import os +import sys +import json +import subprocess + +# Fügen Sie das Hauptverzeichnis zum Python-Pfad hinzu, damit wir dev_session importieren können +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +try: + from dev_session import add_comment_to_notion_task +except ImportError: + print("Fehler: Konnte dev_session.py nicht importieren.", file=sys.stderr) + sys.exit(1) + +SESSION_DIR = ".dev_session" +SESSION_FILE_PATH = os.path.join(SESSION_DIR, "SESSION_INFO") + +def get_last_commit_message() -> str: + """Holt die letzte Commit-Nachricht mit git.""" + try: + # Führt 'git log -1 --pretty=%B' aus, um die vollständige Commit-Nachricht zu erhalten + result = subprocess.run( + ["git", "log", "-1", "--pretty=%B"], + capture_output=True, + text=True, + check=True + ) + return result.stdout.strip() + except (subprocess.CalledProcessError, FileNotFoundError): + return "" + +def main(): + """Hauptfunktion des Git-Hooks.""" + # Führe den Hook nur aus, wenn eine aktive Session existiert + if not os.path.exists(SESSION_FILE_PATH): + sys.exit(0) + + try: + with open(SESSION_FILE_PATH, "r") as f: + session_data = json.load(f) + + task_id = session_data.get("task_id") + token = session_data.get("token") + + if not task_id or not token: + sys.exit(0) + + commit_message = get_last_commit_message() + + if commit_message: + # Formatieren der Nachricht für Notion + comment = f"✅ New Commit:\n---\n{commit_message}" + add_comment_to_notion_task(token, task_id, comment) + + except (FileNotFoundError, json.JSONDecodeError): + # Wenn die Datei nicht existiert oder fehlerhaft ist, einfach beenden + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/start-gemini.sh b/start-gemini.sh index 3378d6a6..798a0fab 100644 --- a/start-gemini.sh +++ b/start-gemini.sh @@ -1,3 +1,4 @@ + #!/bin/sh # Sicherstellen, dass der Config-Ordner existiert, damit Docker ihn als Ordner und nicht als Datei mountet @@ -14,4 +15,6 @@ sudo docker run -it --rm \ -v "$(pwd):/app" \ -v "$(pwd)/.gemini-config:/root/.config" \ --name gemini-session \ - gemini-dev-env \ No newline at end of file + gemini-dev-env \ + bash -c "python3 dev_session.py" + \ No newline at end of file