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.
This commit is contained in:
2026-01-25 19:22:18 +00:00
parent a353c2d23d
commit 125080a0ba
5 changed files with 333 additions and 81 deletions

View File

@@ -15,43 +15,57 @@
## Voraussetzungen ## Voraussetzungen
* Python 3.6+ * **Docker:** Das gesamte Setup ist containerisiert, um Abhängigkeitskonflikte zu vermeiden.
* `requests` Bibliothek (`pip install requests`) * **Notion Integration Token:** Ein Notion API-Token mit Zugriff auf Ihre "Projects"- und "Tasks"-Datenbanken.
* Ein Notion Integration Token mit Zugriff auf deine "Projects [UT]"- und "Tasks [UT]"-Datenbanken.
## Installation und Einrichtung ## Installation und Einrichtung
1. **Notion API Key:** Das Setup ist so konzipiert, dass es mit minimalem Aufwand sofort einsatzbereit ist.
* 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:** 1. **Notion API Key:**
```bash * Stellen Sie sicher, dass Sie einen Notion Integration Token haben.
pip install requests * 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 ## Nutzung
1. **Führe das Skript aus:** ### 1. Sitzung starten
```bash
python3 dev_session.py
```
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:** 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.
* 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. ### 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) ### Beispiel-Interaktion (gekürzt)
Die interaktive Auswahl von Projekt und Task bleibt unverändert:
``` ```
Starte interaktive Entwicklungs-Session... Starte interaktive Entwicklungs-Session...
Bitte gib deinen Notion API Key ein (Eingabe wird nicht angezeigt): **************** 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 [2] Sync Engine
[...] [...]
Bitte wähle eine Nummer: 2 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 '<TASK_ID>' 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 ## 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. Das Skript schlägt automatisch einen Git-Branch-Namen vor, der dem Muster `feature/task-{kurze_task_id}-{task_titel_slug}` folgt.

View File

@@ -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}") print(f"Antwort des Servers: {e.response.text}")
return None 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 --- # --- UI Functions ---
def select_project(token: str) -> Optional[Tuple[Dict, Optional[str]]]: 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: except ValueError:
print("Ungültige Eingabe. Bitte eine Zahl eingeben.") print("Ungültige Eingabe. Bitte eine Zahl eingeben.")
import subprocess
# --- Context Generation --- # --- Context Generation ---
def generate_cli_context(project_title: str, task_title: str, task_id: str, suggested_branch_name: str, readme_path: Optional[str]) -> str: def generate_cli_context(project_title: str, task_title: str, task_id: str, readme_path: Optional[str]) -> str:
"""Erstellt einen formatierten Kontext-String für die Gemini CLI.""" """Erstellt den reinen Kontext-String für die Gemini CLI."""
# Fallback, falls kein Pfad in Notion gesetzt ist # Fallback, falls kein Pfad in Notion gesetzt ist
if not readme_path: if not readme_path:
readme_path = "readme.md" readme_path = "readme.md"
context = ( context = (
"\n------------------------------------------------------------------" f"Ich arbeite jetzt am Projekt '{project_title}'. Der Fokus liegt auf dem Task '{task_title}'.\n\n"
"\n✅ Setup abgeschlossen!" "Die relevanten Dateien für dieses Projekt sind wahrscheinlich:\n"
f"\n\nDu solltest jetzt den Git-Branch manuell erstellen und auschecken:" "- Die primäre Projektdokumentation: @readme.md\n"
f"\ngit checkout -b {suggested_branch_name}" f"- Die spezifische Dokumentation für dieses Modul: @{readme_path}\n"
f"\n\nDer Notion-Task '{task_title}' wurde auf 'Doing' gesetzt." f"- Der Haupt-Code befindet sich wahrscheinlich in: @dev_session.py\n\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."
"\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------------------------------------------------------------------"
) )
return context return context
# --- Main Execution --- # --- CLI Execution ---
def main(): def start_gemini_cli(initial_context: str):
"""Hauptfunktion des Skripts.""" """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...") print("Starte interaktive Entwicklungs-Session...")
token = os.environ.get('NOTION_API_KEY') token = os.environ.get('NOTION_API_KEY')
@@ -327,7 +501,13 @@ def main():
task_title = get_page_title(selected_task) task_title = get_page_title(selected_task)
task_id = selected_task["id"] task_id = selected_task["id"]
print(f"\nTask '{task_title}' ausgewählt.") 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'[^a-z0-9\s-]', '', task_title.lower())
title_slug = re.sub(r'\s+', '-', title_slug) title_slug = re.sub(r'\s+', '-', title_slug)
title_slug = re.sub(r'-+', '-', title_slug).strip('-') title_slug = re.sub(r'-+', '-', title_slug).strip('-')
@@ -338,8 +518,33 @@ def main():
if not status_updated: if not status_updated:
print("Warnung: Notion-Task-Status konnte nicht aktualisiert werden.") 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) # Finale Setup-Informationen ausgeben
print(cli_context) 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__": if __name__ == "__main__":
main() main()

View File

@@ -11,6 +11,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && 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 # Installieren der von Ihnen gefundenen, korrekten Gemini CLI global
RUN npm install -g @google/gemini-cli RUN npm install -g @google/gemini-cli

61
notion_commit_hook.py Normal file
View File

@@ -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()

View File

@@ -1,3 +1,4 @@
#!/bin/sh #!/bin/sh
# Sicherstellen, dass der Config-Ordner existiert, damit Docker ihn als Ordner und nicht als Datei mountet # 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):/app" \
-v "$(pwd)/.gemini-config:/root/.config" \ -v "$(pwd)/.gemini-config:/root/.config" \
--name gemini-session \ --name gemini-session \
gemini-dev-env gemini-dev-env \
bash -c "python3 dev_session.py"