From 7615a1c58bf456e35eddfcf89d879a797a311705 Mon Sep 17 00:00:00 2001 From: Floke Date: Sun, 25 Jan 2026 10:38:18 +0000 Subject: [PATCH] feat(dev-session): Add README.md for dev_session.py [2f388f42] This commit introduces a comprehensive README.md for the dev_session.py script. The documentation covers the purpose, features, usage, installation, and important notes for the Notion-integrated development session manager. It also clarifies the Git branch naming convention suggested by the tool. The creation of this README.md fulfills task [2f388f42]. --- README_dev_session.md | 118 +++++++++++++++ dev_session.py | 338 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 456 insertions(+) create mode 100644 README_dev_session.md create mode 100644 dev_session.py diff --git a/README_dev_session.md b/README_dev_session.md new file mode 100644 index 00000000..e9217748 --- /dev/null +++ b/README_dev_session.md @@ -0,0 +1,118 @@ +# `dev_session.py` - Notion Development Session Manager + +## Übersicht + +`dev_session.py` ist ein interaktives Kommandozeilen-Tool (CLI), das entwickelt wurde, um den Entwicklungs-Workflow durch die Integration mit Notion's "Ultimate Tasks"- und "Projects"-Datenbanken zu optimieren. Es ermöglicht Entwicklern, eine Entwicklungssitzung zu starten, indem sie ein Projekt und einen Task aus Notion auswählen, den Task-Status automatisch aktualisieren und einen formatierten Kontext für die Gemini CLI generieren. + +## Funktionen + +* **Interaktive Projekt- und Task-Auswahl:** Wähle Projekte und Tasks direkt aus deinen Notion-Datenbanken. +* **Automatisches Status-Update:** Der Status des ausgewählten Tasks wird automatisch auf 'Doing' (oder den ersten verfügbaren Status in neuen Projekten) gesetzt, um deinen Fortschritt in Notion widerzuspiegeln. +* **Task-Erstellung:** Erstelle neue Tasks direkt über das CLI im Kontext des ausgewählten Projekts. +* **Dynamische Status-Erkennung:** Fragt Notion API nach verfügbaren Status-Optionen ab, um Kompatibilität mit verschiedenen Notion-Templates zu gewährleisten. +* **Gemini CLI Kontext-Generierung:** Erzeugt einen strukturierten Kontext-Prompt, der alle relevanten Informationen (Projekt, Task, Task-ID, vorgeschlagener Git-Branch-Name) für die Gemini CLI enthält. +* **Vorgeschlagene Git Branch-Benennung:** Erstellt einen konsistenten Git-Branch-Namen basierend auf der Task-ID und dem Task-Titel. + +## Voraussetzungen + +* Python 3.6+ +* `requests` Bibliothek (`pip install requests`) +* Ein Notion Integration Token mit Zugriff auf deine "Projects [UT]"- und "Tasks [UT]"-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. + +2. **Abhängigkeiten installieren:** + ```bash + pip install requests + ``` + +## Nutzung + +1. **Führe das Skript aus:** + ```bash + python3 dev_session.py + ``` + +2. **Notion API Key eingeben:** Wenn `NOTION_API_KEY` nicht als Umgebungsvariable gesetzt ist, wirst du zur Eingabe aufgefordert. + +3. **Projekt auswählen:** Wähle dein gewünschtes Projekt aus der angezeigten Liste der Notion-Projekte. + +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. + +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. + +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. + +### Beispiel-Interaktion (gekürzt) + +``` +Starte interaktive Entwicklungs-Session... +Bitte gib deinen Notion API Key ein (Eingabe wird nicht angezeigt): **************** +Suche nach Datenbank 'Projects [UT]' in Notion... +Datenbank 'Projects [UT]' gefunden mit ID: + +An welchem Projekt möchtest du arbeiten? +[1] My Awesome Project +[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. +------------------------------------------------------------------ +``` + +## 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. +* `feature/task-`: Ein Präfix, das den Branch-Typ und die Beziehung zu einem Notion-Task anzeigt. +* `{kurze_task_id}`: Die ersten 8 Zeichen der Notion Task ID, für eine eindeutige Referenz. +* `{task_titel_slug}`: Eine "Slugified"-Version des Task-Titels (Kleinbuchstaben, Leerzeichen durch Bindestriche ersetzt, Sonderzeichen entfernt). + +**Beispiel:** `feature/task-2f388f42-create-readmemd-for-devsessionpy` + +## Wichtige Hinweise + +* Stelle sicher, dass deine Notion-Datenbanken die Property `Status` mit mindestens einer Statusoption haben. +* Die `project_to_path_map` in `generate_cli_context` muss bei Bedarf erweitert werden, wenn neue Projekte mit spezifischen Basis-Pfaden hinzukommen. Das Projekt "Sync Engine" wird als Root-Level-Projekt ohne Unterverzeichnis behandelt. +* Die vom Skript generierten Pfade (`@company-explorer/backend/README.md`) sind Platzhalter und müssen manuell auf die tatsächlichen Dateien in deinem Projekt verweisen. Das `@dev_session.py` ist der korrekte Pfad für diese Datei. diff --git a/dev_session.py b/dev_session.py new file mode 100644 index 00000000..421fc249 --- /dev/null +++ b/dev_session.py @@ -0,0 +1,338 @@ +import os +import requests +import json +import re +from typing import List, Dict, Optional +from getpass import getpass + +# --- API Helper Functions --- + +def find_database_by_title(token: str, title: str) -> Optional[str]: + """Sucht nach einer Datenbank mit einem bestimmten Titel und gibt deren ID zurück.""" + print(f"Suche nach Datenbank '{title}' in Notion...") + url = "https://api.notion.com/v1/search" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + "Notion-Version": "2022-06-28" + } + payload = { + "query": title, + "filter": {"value": "database", "property": "object"} + } + + try: + response = requests.post(url, headers=headers, json=payload) + response.raise_for_status() + results = response.json().get("results", []) + + for db in results: + db_title_parts = db.get("title", []) + if db_title_parts: + db_title = db_title_parts[0].get("plain_text", "") + if db_title.lower() == title.lower(): + db_id = db["id"] + print(f"Datenbank '{title}' gefunden mit ID: {db_id}") + return db_id + + print(f"Fehler: Keine Datenbank mit dem exakten Titel '{title}' gefunden.") + return None + + except requests.exceptions.RequestException as e: + print(f"Fehler bei der Suche nach der Notion-Datenbank '{title}': {e}") + try: + print(f"Antwort des Servers: {json.dumps(e.response.json(), indent=2)}") + except: + print(f"Antwort des Servers: {e.response.text}") + return None + +def query_notion_database(token: str, database_id: str, filter_payload: Dict = None) -> List[Dict]: + """Fragt eine Notion-Datenbank ab und gibt eine Liste der Seiten zurück.""" + url = f"https://api.notion.com/v1/databases/{database_id}/query" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + "Notion-Version": "2022-06-28" + } + + payload = {} + if filter_payload: + payload["filter"] = filter_payload + + try: + response = requests.post(url, headers=headers, json=payload) + response.raise_for_status() + return response.json().get("results", []) + except requests.exceptions.RequestException as e: + print(f"Fehler bei der Abfrage der Notion-Datenbank {database_id}: {e}") + try: + print(f"Antwort des Servers: {json.dumps(e.response.json(), indent=2)}") + except: + print(f"Antwort des Servers: {e.response.text}") + return [] + +def get_page_title(page: Dict) -> str: + """Extrahiert den Titel einer Notion-Seite.""" + for prop_value in page.get("properties", {}).values(): + if prop_value.get("type") == "title": + title_parts = prop_value.get("title", []) + if title_parts: + return title_parts[0].get("plain_text", "Unbenannt") + return "Unbenannt" + +def get_database_status_options(token: str, db_id: str) -> List[str]: + """Ruft die verfügbaren Status-Optionen für eine Datenbank-Eigenschaft ab.""" + url = f"https://api.notion.com/v1/databases/{db_id}" + headers = { + "Authorization": f"Bearer {token}", + "Notion-Version": "2022-06-28" + } + try: + response = requests.get(url, headers=headers) + response.raise_for_status() + properties = response.json().get("properties", {}) + status_property = properties.get("Status") + if status_property and status_property["type"] == "status": + return [option["name"] for option in status_property["status"]["options"]] + except requests.exceptions.RequestException as e: + print(f"Fehler beim Abrufen der Datenbank-Eigenschaften: {e}") + return [] + +def update_notion_task_status(token: str, task_id: str, status_value: str = "Doing") -> bool: + """Aktualisiert den Status eines Notion-Tasks.""" + print(f"\n--- Aktualisiere Status von Task '{task_id}' auf '{status_value}'... ---") + url = f"https://api.notion.com/v1/pages/{task_id}" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + "Notion-Version": "2022-06-28" + } + payload = { + "properties": { + "Status": { + "status": { + "name": status_value + } + } + } + } + + try: + response = requests.patch(url, headers=headers, json=payload) + response.raise_for_status() + print(f"✅ Task-Status erfolgreich auf '{status_value}' aktualisiert.") + return True + except requests.exceptions.RequestException as e: + print(f"❌ FEHLER beim Aktualisieren des Task-Status: {e}") + try: + print(f"Antwort des Servers: {json.dumps(e.response.json(), indent=2)}") + except: + print(f"Antwort des Servers: {e.response.text}") + return False + +def create_new_notion_task(token: str, project_id: str, tasks_db_id: str) -> Optional[Dict]: + """Erstellt einen neuen Task in Notion und verknüpft ihn mit dem Projekt.""" + print("\n--- Neuen Task erstellen ---") + task_title = input("Bitte gib den Titel für den neuen Task ein: ") + if not task_title: + print("Kein Titel angegeben. Abbruch.") + return None + + status_options = get_database_status_options(token, tasks_db_id) + if not status_options: + print("Fehler: Konnte keine Status-Optionen für die Task-Datenbank finden.") + return None + + initial_status = status_options[0] # Nimm den ersten verfügbaren Status + + url = "https://api.notion.com/v1/pages" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + "Notion-Version": "2022-06-28" + } + payload = { + "parent": {"database_id": tasks_db_id}, + "properties": { + "Name": { + "title": [{"text": {"content": task_title}}] + }, + "Project": { + "relation": [{"id": project_id}] + }, + "Status": { + "status": {"name": initial_status} + } + } + } + + try: + response = requests.post(url, headers=headers, json=payload) + response.raise_for_status() + new_task = response.json() + print(f"✅ Task '{task_title}' erfolgreich erstellt.") + return new_task + except requests.exceptions.RequestException as e: + print(f"❌ FEHLER beim Erstellen des Tasks: {e}") + try: + print(f"Antwort des Servers: {json.dumps(e.response.json(), indent=2)}") + except: + print(f"Antwort des Servers: {e.response.text}") + return None + +# --- UI Functions --- + +def select_project(token: str) -> Optional[Dict]: + """Zeigt eine Liste der Projekte an und lässt den Benutzer eines auswählen.""" + print("--- Lade Projekte aus Notion... ---") + + projects_db_id = find_database_by_title(token, "Projects [UT]") + if not projects_db_id: + return None + + projects = query_notion_database(token, projects_db_id) + if not projects: + print("Keine Projekte in der Datenbank gefunden.") + return None + + print("\nAn welchem Projekt möchtest du arbeiten?") + for i, project in enumerate(projects): + print(f"[{i+1}] {get_page_title(project)}") + + while True: + try: + choice = int(input("Bitte wähle eine Nummer: ")) + if 1 <= choice <= len(projects): + return projects[choice - 1] + else: + print("Ungültige Auswahl.") + except ValueError: + print("Ungültige Eingabe. Bitte eine Zahl eingeben.") + +def select_task(token: str, project_id: str, tasks_db_id: str) -> Optional[Dict]: + """Zeigt eine Liste der Tasks für ein Projekt an und lässt den Benutzer auswählen.""" + print("\n--- Lade Tasks für das ausgewählte Projekt... ---") + + filter_payload = { + "property": "Project", + "relation": {"contains": project_id} + } + tasks = query_notion_database(token, tasks_db_id, filter_payload=filter_payload) + + if not tasks: + print("Keine offenen Tasks für dieses Projekt gefunden.") + else: + print("\nWelchen Task möchtest du bearbeiten?") + for i, task in enumerate(tasks): + print(f"[{i+1}] {get_page_title(task)}") + + print(f"[{len(tasks)+1}] Neuen Task für dieses Projekt erstellen") + + while True: + try: + choice = int(input("Bitte wähle eine Nummer: ")) + if 1 <= choice <= len(tasks): + return tasks[choice - 1] + elif choice == len(tasks) + 1: + return {"id": "new_task"} # Signal + else: + print("Ungültige Auswahl.") + except ValueError: + print("Ungültige Eingabe. Bitte eine Zahl eingeben.") + +# --- Context Generation --- + +def generate_cli_context(project_title: str, task_title: str, task_id: str, suggested_branch_name: str) -> str: + """Erstellt einen formatierten Kontext-String für die Gemini CLI.""" + + project_to_path_map = { + "Company Explorer": "company-explorer/backend/", + "B2B Assistant": "b2b-marketing-assistant/", + "GTM Tool": "gtm-architect/", + "Market Intelligence": "general-market-intelligence/", + "Competitor Analysis": "competitor-analysis-app/", + "Meeting Assistant": "transcription-tool/", + "Sync Engine": "" # Root-Level + } + + base_path = "pfad/zum/modul/" + for key, path in project_to_path_map.items(): + if key in project_title: + base_path = path + break + + 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: @{base_path}README.md (falls vorhanden)" + f"\n- Der Haupt-Code befindet sich wahrscheinlich in: @{base_path}**" + 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------------------------------------------------------------------" + ) + return context + +# --- Main Execution --- + +def main(): + """Hauptfunktion des Skripts.""" + print("Starte interaktive Entwicklungs-Session...") + + token = os.environ.get('NOTION_API_KEY') + if not token: + token = getpass("Bitte gib deinen Notion API Key ein (Eingabe wird nicht angezeigt): ") + if not token: + print("Kein Token angegeben. Abbruch.") + return + + selected_project = select_project(token) + if not selected_project: + return + + project_title = get_page_title(selected_project) + print(f"\nProjekt '{project_title}' ausgewählt.") + + tasks_db_id = find_database_by_title(token, "Tasks [UT]") + if not tasks_db_id: + return + + user_choice = select_task(token, selected_project["id"], tasks_db_id) + + if not user_choice: + print("Kein Task ausgewählt. Abbruch.") + return + + selected_task = None + if user_choice.get("id") == "new_task": + selected_task = create_new_notion_task(token, selected_project["id"], tasks_db_id) + if not selected_task: + return + else: + selected_task = user_choice + + task_title = get_page_title(selected_task) + task_id = selected_task["id"] + print(f"\nTask '{task_title}' ausgewählt.") + + 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('-') + + suggested_branch_name = f"feature/task-{task_id.split('-')[0]}-{title_slug}" + + status_updated = update_notion_task_status(token, task_id, "Doing") + 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) + print(cli_context) + +if __name__ == "__main__": + main() \ No newline at end of file