# Diff-Highlights: Plan v3 Patch vs. Original Volldiff: `/a0/usr/plugins/a0_software_orchestrator/docs/diff-v3-vs-original.patch` (1680 Zeilen, 20 Patches) ## Patch-Statistik (Top 12 nach +Lines) | Datei | + | - | Total | Bemerkung | |---|---:|---:|---:|---| | `docs/diff-v3-vs-original.patch` | 347 | 0 | 360 | | | `utils/migrate_legacy_project_names.py` | 155 | 0 | 161 | NEU | | `docs/bugfix-auto-registration.md` | 120 | 72 | 333 | | | `helpers/db_state_store.py` | 69 | 37 | 187 | | | `tools/plan_mode_guard.py` | 59 | 42 | 207 | | | `utils/migrate_a0_to_db.py` | 17 | 3 | 47 | | | `helpers/compact_protocol.py` | 11 | 7 | 56 | | | `helpers/artifact_rules.py` | 7 | 2 | 26 | | | `helpers/briefing_file.py` | 7 | 3 | 32 | | | `tools/quality_gate.py` | 6 | 4 | 28 | | | `helpers/resume_state.py` | 5 | 3 | 27 | | | `tools/block_compactor.py` | 5 | 3 | 25 | | ## Diff-Snippets (erste 50 Zeilen pro ausgewählter Datei) ### helpers/db_state_store.py (+69 -37) ```diff diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/db_state_store.py /a0/usr/plugins/a0_software_orchestrator/helpers/db_state_store.py --- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/db_state_store.py 2026-06-15 14:24:02.000000000 +0000 +++ /a0/usr/plugins/a0_software_orchestrator/helpers/db_state_store.py 2026-06-16 10:08:25.622937781 +0000 @@ -5,17 +5,25 @@ Verwendung: from usr.plugins.a0_software_orchestrator.helpers.db_state_store import ( - resolve_project, get_kv, set_kv, append_worklog, - set_status, add_next_step, add_todo, set_block_compact, - create_snapshot, append_briefing, add_score, + register_project, get_project_id, resolve_project, + get_kv, set_kv, append_worklog, set_status, add_next_step, + add_todo, set_block_compact, create_snapshot, + append_briefing, add_score, ) - pid = resolve_project("crm-system") + # Neues Projekt anlegen (explizit, mit Plausiprüfung): + pid = register_project("crm-system", git_url="https://...") set_kv(pid, "phase", "implementation") append_worklog(pid, phase="implementation", work_block="T012", agent="implementation_engineer", summary="Auth-Endpoint fertig") add_next_step(pid, "T013: Test schreiben") set_status(pid, "12/30 tasks done, Auth läuft") + + # Existierendes Projekt nachschlagen (nur lesen): + existing_pid = get_project_id("crm-system") # gibt None wenn unbekannt + + # In create-on-missing-Aufrufern (z.B. Migration): + pid = resolve_project("crm-system") # wirft LookupError wenn fehlt """ from __future__ import annotations @@ -30,6 +38,37 @@ # --------------------------------------------------------------------------- +# Project-Name Validation (Pattern + Blacklist) +# --------------------------------------------------------------------------- + +import re + +PROJECT_NAME_PATTERN = re.compile(r"^[a-z][a-z0-9-]{1,40}$") +FORBIDDEN_PROJECT_NAMES = frozenset({ + "a0", "a0-development", "workdir", "dev-projects", + "usr", "tmp", "home", "root", "skills", "plugins", + "opt", "lib", "etc", "var", "data", "venv", +}) + +def _validate_project_name(name: str) -> str: ... (137 weitere Zeilen im Volldiff) ``` ### tools/plan_mode_guard.py (+59 -42) ```diff diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/plan_mode_guard.py /a0/usr/plugins/a0_software_orchestrator/tools/plan_mode_guard.py --- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/plan_mode_guard.py 2026-06-15 21:03:27.000000000 +0000 +++ /a0/usr/plugins/a0_software_orchestrator/tools/plan_mode_guard.py 2026-06-16 10:14:34.924136247 +0000 @@ -1,21 +1,13 @@ -"""Tool: enforce Plan Mode by checking current mode (DB-backed).""" +"""Tool: enforce Plan Mode by checking current mode (DB-backed, global).""" from __future__ import annotations import json -import os from typing import Any, Dict from helpers.tool import Tool, Response -from usr.plugins.a0_software_orchestrator.helpers.db_state_store import resolve_project, get_kv, set_kv -from usr.plugins.a0_software_orchestrator.helpers.exceptions import PluginDBError - - -def _resolve_name(project_name: str = "", project_root: str = "") -> str: - if project_name: - return project_name.strip() - if project_root: - return os.path.basename(project_root.rstrip("/")) - return os.path.basename(os.getcwd()) +from usr.plugins.a0_software_orchestrator.helpers.db_state_store import ( + _db_error_handler, _db, +) _DEFAULT_MODE = { @@ -38,6 +30,11 @@ "maintenance_allowed": {"allowed_actions": ["code", "test", "commit", "deploy"], "blocked_actions": ["destroy"]}, } +# Plan-Mode ist plugin-weit (Bug 7 Fix). Der Key lebt in orch_kv mit +# project_id=0 als globalem Platzhalter, NICHT pro Projekt. +_PLUGIN_MODE_PROJECT_ID = 0 +_PLUGIN_MODE_KEY = "orchestrator_mode" + def _normalize_mode(mode_data: Any) -> Dict[str, Any]: """Stellt sicher, dass das gelesene Mode-Dict die erwarteten Keys hat.""" @@ -52,33 +49,57 @@ return out +@_db_error_handler +def _get_plugin_mode() -> Dict[str, Any]: + """Liest den globalen Plan-Mode aus orch_kv (project_id=0). + + Returns _DEFAULT_MODE wenn kein Eintrag existiert. ... (157 weitere Zeilen im Volldiff) ``` ### tools/next_step.py (+5 -3) ```diff diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/next_step.py /a0/usr/plugins/a0_software_orchestrator/tools/next_step.py --- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/next_step.py 2026-06-15 14:24:02.000000000 +0000 +++ /a0/usr/plugins/a0_software_orchestrator/tools/next_step.py 2026-06-16 10:09:29.231450211 +0000 @@ -5,7 +5,7 @@ from helpers.tool import Tool, Response from usr.plugins.a0_software_orchestrator.helpers.db_state_store import ( - resolve_project, + get_project_id, add_next_step, complete_next_step, list_next_steps, @@ -46,8 +46,10 @@ return Response(message="ERROR: 'action' required. Supported: add, list, complete", break_loop=False) name = _resolve_name(project_name, project_root) try: - pid = resolve_project(name) - except ValueError as e: + pid = get_project_id(name) + if pid is None: + raise LookupError(f"project '{name}' is not registered") + except (ValueError, LookupError) as e: return Response(message=f"ERROR: {e}", break_loop=False) except PluginDBError as e: return Response(message=f"Database error: {e}", break_loop=False) ``` ### utils/migrate_legacy_project_names.py (+155 -0) ```diff diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/utils/migrate_legacy_project_names.py /a0/usr/plugins/a0_software_orchestrator/utils/migrate_legacy_project_names.py --- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/utils/migrate_legacy_project_names.py 1970-01-01 00:00:00.000000000 +0000 +++ /a0/usr/plugins/a0_software_orchestrator/utils/migrate_legacy_project_names.py 2026-06-16 10:15:07.578452765 +0000 @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""migrate_legacy_project_names – archiviert Projekte mit ungültigen Namen. + +Bug 1+2 Begleit-Skript (Plan v3 §3.6): +- Iteriert über alle Projekte in der DB. +- Prüft jeden Namen gegen PROJECT_NAME_PATTERN + FORBIDDEN_PROJECT_NAMES. +- Markiert ungültige Namen mit status='archived' in project_state (idempotent). +- Bestehende 'active'-Projekte mit ungültigem Namen werden zu 'archived'. +- Bereits archivierte werden NICHT doppelt verändert (idempotent). + +Aufruf: + python3 utils/migrate_legacy_project_names.py [--dry-run] [--verbose] + +Defaults: +- --dry-run: nur Report, keine DB-Schreibzugriffe. +- --verbose: zusätzlich pro Projekt-Status ausgeben. + +Exit-Code: +- 0: Erfolg (inkl. 0 archivierte Projekte). +- 1: DB-Fehler. +""" +from __future__ import annotations + +import argparse +import os +import sys +from typing import List, Tuple + +PLUGIN_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# Run from /a0 to have usr.plugins.* resolvable. +from usr.plugins.a0_software_orchestrator.helpers.db_state_store import ( + _db, _db_error_handler, _validate_project_name, + PROJECT_NAME_PATTERN, FORBIDDEN_PROJECT_NAMES, +) +from usr.plugins.a0_software_orchestrator.helpers.exceptions import PluginDBError + + +@_db_error_handler +def list_projects_with_states() -> List[Tuple[int, str, str]]: + """Returns Liste von (project_id, project_name, current_status). + + Liest aus projects JOIN project_state. Projekte ohne project_state-Eintrag + bekommen den Status ''. + """ + db = _db() ... (111 weitere Zeilen im Volldiff) ``` ### api/project_state_get.py (+4 -1) ```diff diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/api/project_state_get.py /a0/usr/plugins/a0_software_orchestrator/api/project_state_get.py --- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/api/project_state_get.py 2026-06-15 14:24:02.000000000 +0000 +++ /a0/usr/plugins/a0_software_orchestrator/api/project_state_get.py 2026-06-16 10:14:06.905148563 +0000 @@ -13,7 +13,10 @@ class ProjectStateGet(ApiHandler): async def process(self, input: dict, request: Request) -> dict | Response: project_root = input.get("project_root", os.getcwd()) - project_name = input.get("project_name") or os.path.basename(project_root.rstrip("/")) + # Bug 4 Fix: basename-Fallback entfernt. project_name ist Pflicht. + project_name = input.get("project_name") + if not project_name: + return {"ok": False, "error": "project_name is required (basename fallback removed)"} try: pid = get_project_id(project_name) if pid is None: Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/api/__pycache__/project_state_get.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/api/__pycache__/project_state_get.cpython-313.pyc differ ``` ### helpers/artifact_rules.py (+7 -2) ```diff diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/artifact_rules.py /a0/usr/plugins/a0_software_orchestrator/helpers/artifact_rules.py --- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/artifact_rules.py 2026-06-15 14:24:02.000000000 +0000 +++ /a0/usr/plugins/a0_software_orchestrator/helpers/artifact_rules.py 2026-06-16 10:11:38.560624907 +0000 @@ -42,7 +42,7 @@ def _check_db_artifacts(project_name: str) -> Dict[str, bool]: """Prüft die 5 DB-backed Artefakte via db_state_store.""" from .db_state_store import ( - resolve_project, + get_project_id, get_kv, get_status, list_worklog, @@ -50,7 +50,12 @@ list_next_steps, ) - pid = resolve_project(project_name) + pid = get_project_id(project_name) + if pid is None: + return { + "worklog": False, "todo": False, "project_state": False, + "current_status": False, "next_steps": False, + } return { "worklog": len(list_worklog(pid, limit=1)) > 0, ```