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

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