Compare commits
7 Commits
a01a6e5c7d
...
feature/ta
| Author | SHA1 | Date | |
|---|---|---|---|
| e57aa374ea | |||
| eb3f77f092 | |||
| b396e54080 | |||
| c4baf68595 | |||
| 0a1be647c4 | |||
| 01e5ae8b5c | |||
| adafab61ae |
@@ -1 +1 @@
|
|||||||
{"task_id": "2f488f42-8544-81ac-a9f8-e373c4c18115", "token": "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", "session_start_time": "2026-01-26T18:39:11.157549"}
|
{"task_id": "2f488f42-8544-819a-8407-f29748b3e0b8", "token": "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"}
|
||||||
170
dev_session.py
170
dev_session.py
@@ -1,14 +1,10 @@
|
|||||||
import re
|
|
||||||
from typing import List, Dict, Optional, Tuple, Any
|
|
||||||
from getpass import getpass
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
import subprocess
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
import argparse
|
import re
|
||||||
import shutil
|
from typing import List, Dict, Optional, Tuple
|
||||||
|
from getpass import getpass
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -87,48 +83,18 @@ def get_page_title(page: Dict) -> str:
|
|||||||
return title_parts[0].get("plain_text", "Unbenannt")
|
return title_parts[0].get("plain_text", "Unbenannt")
|
||||||
return "Unbenannt"
|
return "Unbenannt"
|
||||||
|
|
||||||
def get_page_rich_text_property(page: Dict, prop_name: str) -> Optional[str]:
|
def get_page_property(page: Dict, prop_name: str, prop_type: str = "rich_text") -> Optional[str]:
|
||||||
"""Extrahiert den Inhalt einer bestimmten Eigenschaft (Property) von einer Seite."""
|
"""Extrahiert den Inhalt einer bestimmten Eigenschaft (Property) von einer Seite."""
|
||||||
prop = page.get("properties", {}).get(prop_name)
|
prop = page.get("properties", {}).get(prop_name)
|
||||||
if not prop:
|
if not prop:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if prop.get("type") == "rich_text":
|
if prop_type == "rich_text" and prop.get("type") == "rich_text":
|
||||||
text_parts = prop.get("rich_text", [])
|
text_parts = prop.get("rich_text", [])
|
||||||
if text_parts:
|
if text_parts:
|
||||||
return text_parts[0].get("plain_text")
|
return text_parts[0].get("plain_text")
|
||||||
|
|
||||||
return None
|
# Hier könnten weitere Typen wie 'select', 'number' etc. behandelt werden
|
||||||
|
|
||||||
def get_page_number_property(page: Dict, prop_name: str) -> Optional[float]:
|
|
||||||
"""Extrahiert den Inhalt einer Number-Eigenschaft von einer Seite."""
|
|
||||||
prop = page.get("properties", {}).get(prop_name)
|
|
||||||
if not prop:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if prop.get("type") == "number":
|
|
||||||
return prop.get("number")
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_page_date_property(page: Dict, prop_name: str) -> Optional[datetime]:
|
|
||||||
"""Extrahiert den Inhalt einer Date-Eigenschaft von einer Seite."""
|
|
||||||
prop = page.get("properties", {}).get(prop_name)
|
|
||||||
if not prop:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if prop.get("type") == "date":
|
|
||||||
date_info = prop.get("date")
|
|
||||||
if date_info and date_info.get("start"):
|
|
||||||
try:
|
|
||||||
# Notion gibt Datumszeiten im ISO 8601 Format zurück (z.B. '2023-10-27T10:00:00.000+00:00')
|
|
||||||
# oder nur Datum (z.B. '2023-10-27')
|
|
||||||
# datetime.fromisoformat kann beides verarbeiten, aber Zeitzonen können komplex sein.
|
|
||||||
# Für unsere Zwecke reicht es, wenn es als UTC betrachtet wird und wir die Dauer berechnen.
|
|
||||||
return datetime.fromisoformat(date_info["start"].replace('Z', '+00:00'))
|
|
||||||
except ValueError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_page_content(token: str, page_id: str) -> str:
|
def get_page_content(token: str, page_id: str) -> str:
|
||||||
@@ -227,42 +193,32 @@ def get_database_status_options(token: str, db_id: str) -> List[str]:
|
|||||||
print(f"Fehler beim Abrufen der Datenbank-Eigenschaften: {e}")
|
print(f"Fehler beim Abrufen der Datenbank-Eigenschaften: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def update_notion_task_property(token: str, task_id: str, prop_name: str, prop_value: Any, prop_type: str) -> bool:
|
def update_notion_task_status(token: str, task_id: str, status_value: str = "Doing") -> bool:
|
||||||
"""Aktualisiert eine bestimmte Eigenschaft (Property) eines Notion-Tasks."""
|
"""Aktualisiert den Status eines Notion-Tasks."""
|
||||||
print(f"\n--- Aktualisiere Task '{task_id}', Eigenschaft '{prop_name}' ({prop_type}) mit Wert '{prop_value}'... ---")
|
print(f"\n--- Aktualisiere Status von Task '{task_id}' auf '{status_value}'... ---")
|
||||||
url = f"https://api.notion.com/v1/pages/{task_id}"
|
url = f"https://api.notion.com/v1/pages/{task_id}"
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {token}",
|
"Authorization": f"Bearer {token}",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Notion-Version": "2022-06-28"
|
"Notion-Version": "2022-06-28"
|
||||||
}
|
}
|
||||||
|
payload = {
|
||||||
payload_properties = {}
|
"properties": {
|
||||||
if prop_type == "status":
|
"Status": {
|
||||||
payload_properties[prop_name] = {"status": {"name": prop_value}}
|
"status": {
|
||||||
elif prop_type == "number":
|
"name": status_value
|
||||||
payload_properties[prop_name] = {"number": prop_value}
|
}
|
||||||
elif prop_type == "date":
|
}
|
||||||
# Notion erwartet Datum im ISO 8601 Format
|
}
|
||||||
if isinstance(prop_value, datetime):
|
}
|
||||||
payload_properties[prop_name] = {"date": {"start": prop_value.isoformat()}}
|
|
||||||
else:
|
|
||||||
print(f"❌ FEHLER: Ungültiges Datumformat für '{prop_name}'. Erwartet datetime-Objekt.")
|
|
||||||
return False
|
|
||||||
# Weitere Typen können hier hinzugefügt werden (z.B. rich_text, multi_select etc.)
|
|
||||||
else:
|
|
||||||
print(f"❌ FEHLER: Nicht unterstützter Eigenschaftstyp '{prop_type}' für Update.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
payload = {"properties": payload_properties}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.patch(url, headers=headers, json=payload)
|
response = requests.patch(url, headers=headers, json=payload)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
print(f"✅ Task-Eigenschaft '{prop_name}' erfolgreich aktualisiert.")
|
print(f"✅ Task-Status erfolgreich auf '{status_value}' aktualisiert.")
|
||||||
return True
|
return True
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
print(f"❌ FEHLER beim Aktualisieren der Task-Eigenschaft '{prop_name}': {e}")
|
print(f"❌ FEHLER beim Aktualisieren des Task-Status: {e}")
|
||||||
try:
|
try:
|
||||||
print(f"Antwort des Servers: {json.dumps(e.response.json(), indent=2)}")
|
print(f"Antwort des Servers: {json.dumps(e.response.json(), indent=2)}")
|
||||||
except:
|
except:
|
||||||
@@ -439,7 +395,7 @@ def select_project(token: str) -> Optional[Tuple[Dict, Optional[str]]]:
|
|||||||
choice = int(input("Bitte wähle eine Nummer: "))
|
choice = int(input("Bitte wähle eine Nummer: "))
|
||||||
if 1 <= choice <= len(projects):
|
if 1 <= choice <= len(projects):
|
||||||
selected_project = projects[choice - 1]
|
selected_project = projects[choice - 1]
|
||||||
readme_path = get_page_rich_text_property(selected_project, "Readme Path")
|
readme_path = get_page_property(selected_project, "Readme Path")
|
||||||
return selected_project, readme_path
|
return selected_project, readme_path
|
||||||
else:
|
else:
|
||||||
print("Ungültige Auswahl.")
|
print("Ungültige Auswahl.")
|
||||||
@@ -535,68 +491,13 @@ def report_status_to_notion(
|
|||||||
session_data = json.load(f)
|
session_data = json.load(f)
|
||||||
task_id = session_data.get("task_id")
|
task_id = session_data.get("task_id")
|
||||||
token = session_data.get("token")
|
token = session_data.get("token")
|
||||||
session_start_time_str = session_data.get("session_start_time")
|
|
||||||
|
|
||||||
if not (task_id and token and session_start_time_str):
|
if not (task_id and token):
|
||||||
print("❌ FEHLER: Session-Daten unvollständig oder Startzeit fehlt. Kann keinen Statusbericht erstellen.")
|
print("❌ FEHLER: Session-Daten unvollständig. Kann keinen Statusbericht erstellen.")
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
session_start_time = datetime.fromisoformat(session_start_time_str)
|
|
||||||
except ValueError:
|
|
||||||
print(f"❌ FEHLER: Ungültiges Startzeitformat in Session-Daten: {session_start_time_str}")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f"--- Erstelle Statusbericht für Task {task_id} ---")
|
print(f"--- Erstelle Statusbericht für Task {task_id} ---")
|
||||||
|
|
||||||
tasks_db_id = find_database_by_title(token, "Tasks [UT]")
|
|
||||||
if not tasks_db_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
# 1. Aktuelles Task-Objekt abrufen, um die vorhandene Dauer zu lesen
|
|
||||||
page_url = f"https://api.notion.com/v1/pages/{task_id}"
|
|
||||||
headers = {
|
|
||||||
"Authorization": f"Bearer {token}",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Notion-Version": "2022-06-28"
|
|
||||||
}
|
|
||||||
current_task_page = None
|
|
||||||
try:
|
|
||||||
response = requests.get(page_url, headers=headers)
|
|
||||||
response.raise_for_status()
|
|
||||||
current_task_page = response.json()
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"❌ FEHLER beim Abrufen der Task-Seite {task_id}: {e}")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not current_task_page:
|
|
||||||
print(f"❌ FEHLER: Konnte den Task {task_id} in Notion nicht finden. Zeiterfassung wird übersprungen.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 2. Bestehende Total Duration (h) abrufen
|
|
||||||
existing_duration = get_page_number_property(current_task_page, "Total Duration (h)")
|
|
||||||
if existing_duration is None:
|
|
||||||
existing_duration = 0.0
|
|
||||||
|
|
||||||
# 3. Dauer der aktuellen Arbeitseinheit berechnen
|
|
||||||
current_end_time = datetime.now()
|
|
||||||
time_spent = (current_end_time - session_start_time).total_seconds() / 3600.0 # Dauer in Stunden
|
|
||||||
|
|
||||||
# 4. Neue Gesamtdauer berechnen
|
|
||||||
new_total_duration = existing_duration + time_spent
|
|
||||||
|
|
||||||
# 5. Total Duration (h) in Notion aktualisieren
|
|
||||||
update_notion_task_property(token, task_id, "Total Duration (h)", round(new_total_duration, 2), "number")
|
|
||||||
|
|
||||||
# 6. session_start_time in SESSION_INFO für die nächste Arbeitseinheit aktualisieren
|
|
||||||
# Hier speichern wir die Endzeit als neue Startzeit für die nächste mögliche Einheit
|
|
||||||
# oder löschen sie, wenn die Session als beendet betrachtet wird.
|
|
||||||
# Entsprechend des Plans setzen wir sie zurück (auf die aktuelle Endzeit der Arbeitseinheit).
|
|
||||||
session_data["session_start_time"] = current_end_time.isoformat()
|
|
||||||
with open(SESSION_FILE_PATH, "w") as f:
|
|
||||||
json.dump(session_data, f)
|
|
||||||
|
|
||||||
|
|
||||||
# Git-Zusammenfassung generieren (immer, wenn nicht explizit überschrieben)
|
# Git-Zusammenfassung generieren (immer, wenn nicht explizit überschrieben)
|
||||||
actual_git_changes = git_changes_override
|
actual_git_changes = git_changes_override
|
||||||
actual_commit_messages = commit_messages_override
|
actual_commit_messages = commit_messages_override
|
||||||
@@ -700,7 +601,7 @@ def report_status_to_notion(
|
|||||||
|
|
||||||
# Notion aktualisieren
|
# Notion aktualisieren
|
||||||
append_blocks_to_notion_page(token, task_id, notion_blocks)
|
append_blocks_to_notion_page(token, task_id, notion_blocks)
|
||||||
update_notion_task_property(token, task_id, "Status", actual_status, "status")
|
update_notion_task_status(token, task_id, actual_status)
|
||||||
|
|
||||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||||
print(f"❌ FEHLER beim Lesen der Session-Informationen für Statusbericht: {e}")
|
print(f"❌ FEHLER beim Lesen der Session-Informationen für Statusbericht: {e}")
|
||||||
@@ -740,13 +641,20 @@ def generate_cli_context(project_title: str, task_title: str, task_id: str, read
|
|||||||
|
|
||||||
# Die start_gemini_cli Funktion wird entfernt, da das aufrufende Skript jetzt die Gemini CLI startet.
|
# Die start_gemini_cli Funktion wird entfernt, da das aufrufende Skript jetzt die Gemini CLI startet.
|
||||||
|
|
||||||
def save_session_info(task_id: str, token: str, session_start_time: datetime):
|
import shutil
|
||||||
"""Speichert die Task-ID, den Token und die Startzeit der Session."""
|
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)
|
os.makedirs(SESSION_DIR, exist_ok=True)
|
||||||
session_data = {
|
session_data = {
|
||||||
"task_id": task_id,
|
"task_id": task_id,
|
||||||
"token": token,
|
"token": token
|
||||||
"session_start_time": session_start_time.isoformat() # Speichern als ISO-Format String
|
|
||||||
}
|
}
|
||||||
with open(SESSION_FILE_PATH, "w") as f:
|
with open(SESSION_FILE_PATH, "w") as f:
|
||||||
json.dump(session_data, f)
|
json.dump(session_data, f)
|
||||||
@@ -805,7 +713,7 @@ def complete_session():
|
|||||||
status_options = get_database_status_options(token, tasks_db_id)
|
status_options = get_database_status_options(token, tasks_db_id)
|
||||||
if status_options:
|
if status_options:
|
||||||
done_status = status_options[-1]
|
done_status = status_options[-1]
|
||||||
update_notion_task_property(token, task_id, "Status", done_status, "status")
|
update_notion_task_status(token, task_id, done_status)
|
||||||
|
|
||||||
except (FileNotFoundError, json.JSONDecodeError):
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
print("Fehler beim Lesen der Session-Informationen.")
|
print("Fehler beim Lesen der Session-Informationen.")
|
||||||
@@ -863,7 +771,7 @@ def start_interactive_session():
|
|||||||
task_description = get_page_content(token, task_id)
|
task_description = get_page_content(token, task_id)
|
||||||
|
|
||||||
# Session-Informationen für den Git-Hook speichern
|
# Session-Informationen für den Git-Hook speichern
|
||||||
save_session_info(task_id, token, datetime.now())
|
save_session_info(task_id, token)
|
||||||
|
|
||||||
# Git-Hook installieren, der die Session-Infos nutzt
|
# Git-Hook installieren, der die Session-Infos nutzt
|
||||||
install_git_hook()
|
install_git_hook()
|
||||||
@@ -874,7 +782,7 @@ def start_interactive_session():
|
|||||||
|
|
||||||
suggested_branch_name = f"feature/task-{task_id.split('-')[0]}-{title_slug}"
|
suggested_branch_name = f"feature/task-{task_id.split('-')[0]}-{title_slug}"
|
||||||
|
|
||||||
status_updated = update_notion_task_property(token, task_id, "Status", "Doing", "status")
|
status_updated = update_notion_task_status(token, task_id, "Doing")
|
||||||
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.")
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user