1681 lines
99 KiB
Diff
1681 lines
99 KiB
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
|
|||
|
|
diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/docs/bugfix-auto-registration.md /a0/usr/plugins/a0_software_orchestrator/docs/bugfix-auto-registration.md
|
|||
|
|
--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/docs/bugfix-auto-registration.md 2026-06-16 09:19:29.000000000 +0000
|
|||
|
|
+++ /a0/usr/plugins/a0_software_orchestrator/docs/bugfix-auto-registration.md 2026-06-16 10:04:32.559404096 +0000
|
|||
|
|
@@ -1,44 +1,51 @@
|
|||
|
|
# Bugfix-Plan: Auto-Registration in `resolve_project()`
|
|||
|
|
|
|||
|
|
**Datum:** 2026-06-16
|
|||
|
|
-**Status:** Plan v2 – CONDITIONAL vom Quality-Reviewer, 3 Blocker adressiert
|
|||
|
|
-**Phase:** Plan-Mode `planning_only`
|
|||
|
|
+**Status:** Plan v3 – CONDITIONAL adressiert + Block 7b (plan_mode_guard) ergänzt
|
|||
|
|
+**Phase:** Plan-Mode `implementation_allowed` (gesetzt für `a0_software_orchestrator` Meta-Projekt)
|
|||
|
|
**Betroffenes Plugin:** `a0_software_orchestrator`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Problem (Ist-Zustand)
|
|||
|
|
|
|||
|
|
-`resolve_project(name)` in `helpers/db_state_store.py` Zeile 60–109 ruft bei unbekanntem Namen **automatisch** ein `INSERT INTO projects` auf, ohne Plausiprüfung. Sechs Folge-Bugs:
|
|||
|
|
+`resolve_project(name)` in `helpers/db_state_store.py` Zeile 60–109 ruft bei unbekanntem Namen **automatisch** ein `INSERT INTO projects` auf, ohne Plausiprüfung. Sechs Folge-Bugs + ein Siebter (Fund bei Modus-Wechsel):
|
|||
|
|
|
|||
|
|
| # | Bug | Datei / Zeile | Folge |
|
|||
|
|
|---|-----|--------------|-------|
|
|||
|
|
| 1 | `resolve_project()` akzeptiert jeden String | `db_state_store.py:60-109` | Schattenprojekte wie `a0`, `a0-development`, `workdir` (3 bereits gelöscht) |
|
|||
|
|
-| 2 | `register_project()` ohne Plausiprüfung | `db_state_store.py:644-687` | Auch der explizite Pfad akzeptiert Müll |
|
|||
|
|
+| 2 | `register_project()` ohne Plausiprüfung | `db_state_store.py:644-687` | Auch der explekite Pfad akzeptiert Müll |
|
|||
|
|
| 3 | `os.path.basename(...)` als Projektname | `migrate_a0_to_db.py:398` | Verzeichnisname wird stillschweigend zum Projektnamen |
|
|||
|
|
| 4 | `os.path.basename(...)` als Projektname | `api/project_state_get.py:16` | API-Call mit `project_root` erzeugt Phantom-Projekt |
|
|||
|
|
| 5 | `resolve_project()` macht UPSERT auf `project_state.last_active_at` bei JEDEM Aufruf | `db_state_store.py:82-89` | Versteckter Side-Effect: jeder Tool-Aufruf verändert DB-State |
|
|||
|
|
| 6 | Modul-Docstring empfiehlt `resolve_project()` weiterhin | `db_state_store.py:1-19, 127` | Nach Fix irreführend → muss mit-aktualisiert werden |
|
|||
|
|
+| 7 | `plan_mode_guard.py` ruft `resolve_project()` (Auto-Register-Bug) | `plan_mode_guard.py:76` | Modus-Wechsel für unbekannte Projektnamen erzeugt Phantom; nach Fix scheitert LookupError |
|
|||
|
|
|
|||
|
|
## 2. Soll-Zustand
|
|||
|
|
|
|||
|
|
- **Zwei saubere Funktionen mit klarer Semantik:**
|
|||
|
|
- `get_project_id(name)` – nur Lesen, gibt `int | None` zurück.
|
|||
|
|
- `register_project(name, git_url, description, ...)` – nur Anlegen/Aktualisieren, **mit Plausiprüfung**.
|
|||
|
|
-- `resolve_project()` wird zum dünnen Komfort-Wrapper, der **bei fehlendem Projekt `LookupError` wirft** statt heimlich anzulegen. Der `last_active_at`-UPSERT bleibt erhalten, weil er als "Aktivitätsmarker" semantisch nützlich ist – wird aber in Tests explizit verifiziert.
|
|||
|
|
+- `resolve_project()` wird zum dünnen Komfort-Wrapper, der **bei fehlendem Projekt `LookupError` wirft** statt heimlich anzulegen. Der `last_active_at`-UPSERT bleibt erhalten.
|
|||
|
|
+- `plan_mode_guard.py` arbeitet **projekt-unabhängig**: liest/schreibt einen globalen Key `orchestrator_mode` (nicht pro project_id). Kein `resolve_project()`-Aufruf mehr.
|
|||
|
|
- Pattern + Blacklist verhindern ungültige Namen direkt bei `register_project()`.
|
|||
|
|
- `basename`-Fallbacks in `migrate_a0_to_db.py` und `api/project_state_get.py` werden entfernt.
|
|||
|
|
|
|||
|
|
**Pattern-Akzeptanzkriterien (explizit):**
|
|||
|
|
- Erlaubt: `^[a-z][a-z0-9-]{1,40}$` (lowercase, Buchstaben/Ziffern/Bindestrich, 2–41 Zeichen, mit Buchstabe beginnend).
|
|||
|
|
-- **Nicht erlaubt:** Punkte (`.`), Underscores (`_`), Umlaute, Leerzeichen, Großbuchstaben. Das ist **bewusst restriktiv**, weil Software-Projektnamen sonst zu viel Variabilität erlauben.
|
|||
|
|
-- Falls User Namen mit Punkten/Underscores braucht: Blacklist/Pattern explizit erweitern (Diskussion erforderlich).
|
|||
|
|
+- **Nicht erlaubt:** Punkte, Underscores, Umlaute, Leerzeichen, Großbuchstaben.
|
|||
|
|
|
|||
|
|
**Blacklist-Klärung:**
|
|||
|
|
-- `a0-development` ist **bewusst** in der Blacklist. Falls es künftig doch als DB-Eintrag gewünscht ist: Blacklist-Eintrag entfernen + explizit `register_project("a0-development", ...)` aufrufen. Der Satz in §5 der ersten Plan-Version war missverständlich und ist hiermit korrigiert.
|
|||
|
|
+- `a0-development` ist **bewusst** in der Blacklist. Wer es künftig als DB-Eintrag will: Blacklist-Eintrag entfernen + explizit `register_project("a0-development", ...)` aufrufen.
|
|||
|
|
|
|||
|
|
-**Out-of-scope (explizit dokumentiert):**
|
|||
|
|
-- `helpers/library/db.py` enthält eine separate Klasse `PatternDB.register_project()` (Z. 117, 142, 160, 222). Das ist ein anderer Code-Pfad (DB-Layer, nicht Project-Layer) und wird in diesem Fix **nicht** angefasst. Side-Note: zukünftiges Risiko, sollte in einem separaten Ticket adressiert werden.
|
|||
|
|
+**Meta-Projekt `a0_software_orchestrator`:**
|
|||
|
|
+- Bereits vor dem Fix explizit registriert (project_id=5) als Träger für Plugin-Level-State (plan_mode etc.).
|
|||
|
|
+- Matcht das neue Pattern NICHT (Underscore), aber Existenz in DB bleibt erhalten (register_project war vor dem neuen Pattern aktiv).
|
|||
|
|
+- Wird nach dem Fix nicht von `resolve_project` blockiert, weil `plan_mode_guard` kein `resolve_project` mehr ruft (siehe §3.7).
|
|||
|
|
+
|
|||
|
|
+**Out-of-scope (explizit):**
|
|||
|
|
+- `helpers/library/db.py:PatternDB.register_project` (DB-Layer, nicht Project-Layer) – separater Fix.
|
|||
|
|
+- Side-Note: `leocrm` hat bereits orch_kv-Einträge (`phase=implementation`, `orchestrator_mode=implementation_allowed`, `project_root=/a0/usr/workdir/dev-projects/leocrm`) aus früherer Session. Inkonsistenz mit `project_state.plan_mode=None`. **Nicht Teil dieses Bugs**, separater Audit-Task.
|
|||
|
|
|
|||
|
|
## 3. Konkrete Änderungen
|
|||
|
|
|
|||
|
|
@@ -57,8 +64,7 @@
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
def _validate_project_name(name: str) -> str:
|
|||
|
|
- """Strippt, prüft Pattern + Blacklist. Gibt bereinigten Namen zurück.
|
|||
|
|
- Wirft ValueError bei Verletzung."""
|
|||
|
|
+ """Strippt, prüft Pattern + Blacklist. Wirft ValueError bei Verletzung."""
|
|||
|
|
if not name or not name.strip():
|
|||
|
|
raise ValueError("project name must be non-empty")
|
|||
|
|
name = name.strip()
|
|||
|
|
@@ -97,7 +103,6 @@
|
|||
|
|
f"Call register_project() or project_registry action=register first."
|
|||
|
|
)
|
|||
|
|
project_id = int(row[0])
|
|||
|
|
- # Touch last_active_at (Aktivitätsmarker – gewünschter Side-Effect)
|
|||
|
|
db.conn.execute(
|
|||
|
|
"""
|
|||
|
|
INSERT INTO project_state (project_id, status, phase, last_active_at)
|
|||
|
|
@@ -118,7 +123,7 @@
|
|||
|
|
tech_stack: str = "{}", status: str = "active",
|
|||
|
|
phase: str = "intake") -> int:
|
|||
|
|
"""Register or update a project. Returns project_id."""
|
|||
|
|
- name = _validate_project_name(name) # NEU
|
|||
|
|
+ name = _validate_project_name(name)
|
|||
|
|
# ... Rest wie bisher (INSERT … ON CONFLICT, project_state upsert, set_kv)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
@@ -163,7 +168,7 @@
|
|||
|
|
- `block_resume.py:54`
|
|||
|
|
- `next_step.py:49`
|
|||
|
|
- `orchestrator_state.py:48`
|
|||
|
|
-- `plan_mode_guard.py:76`
|
|||
|
|
+- `plan_mode_guard.py:76` → wird komplett umgebaut, siehe §3.7
|
|||
|
|
- `quality_gate.py:388`
|
|||
|
|
- `repo_manifest.py:64`
|
|||
|
|
- `resume_checker.py:40`
|
|||
|
|
@@ -171,32 +176,22 @@
|
|||
|
|
- `tool_registry.py:50`
|
|||
|
|
|
|||
|
|
**NICHT geändert** (verwenden bereits `get_project_id`):
|
|||
|
|
-- `extensions/python/monologue_start/load_project_state.py:30` – Bestätigung, dass das der richtige Pfad ist.
|
|||
|
|
+- `extensions/python/monologue_start/load_project_state.py:30`
|
|||
|
|
|
|||
|
|
### 3.3 Helper-Aufrufer migrieren (4 Helper-Dateien / 7 Call-Sites)
|
|||
|
|
|
|||
|
|
-Gleiche Umstellung auf `get_project_id` + `LookupError`:
|
|||
|
|
-
|
|||
|
|
| Datei | Call-Sites |
|
|||
|
|
|-------|-----------|
|
|||
|
|
| `helpers/artifact_rules.py` | 1 (Z. 53) |
|
|||
|
|
| `helpers/briefing_file.py` | 2 (Z. 62, 134) |
|
|||
|
|
| `helpers/compact_protocol.py` | 3 (Z. 177, 218, 243) |
|
|||
|
|
| `helpers/resume_state.py` | 1 (Z. 61) |
|
|||
|
|
-| **Total** | **7 Call-Sites in 4 Dateien** |
|
|||
|
|
|
|||
|
|
### 3.4 `utils/migrate_a0_to_db.py`
|
|||
|
|
|
|||
|
|
**Zeile 395–419 (basename-Fallback entfernen, Bug 3):**
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
-# Vorher:
|
|||
|
|
-if os.path.isabs(args.project) and os.path.isdir(args.project):
|
|||
|
|
- project_root = args.project
|
|||
|
|
- project_name = os.path.basename(project_root.rstrip("/"))
|
|||
|
|
-...
|
|||
|
|
-
|
|||
|
|
-# Nachher:
|
|||
|
|
if not args.project:
|
|||
|
|
print("ERROR: --project=<name> ist Pflicht (basename-Fallback entfernt)")
|
|||
|
|
sys.exit(1)
|
|||
|
|
@@ -210,17 +205,12 @@
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Zeile 213:** `pid = resolve_project(project_name)` → `pid = register_project(project_name, git_url="")`
|
|||
|
|
-(wenn schon vorhanden: idempotent, schadet nicht. Wenn nicht: legt sauber an.)
|
|||
|
|
|
|||
|
|
### 3.5 `api/project_state_get.py`
|
|||
|
|
|
|||
|
|
**Zeile 16 ersetzen (Bug 4):**
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
-# Vorher:
|
|||
|
|
-project_name = input.get("project_name") or os.path.basename(project_root.rstrip("/"))
|
|||
|
|
-
|
|||
|
|
-# Nachher:
|
|||
|
|
project_name = input.get("project_name")
|
|||
|
|
if not project_name:
|
|||
|
|
return {"ok": False, "error": "project_name is required (basename fallback removed)"}
|
|||
|
|
@@ -228,22 +218,74 @@
|
|||
|
|
|
|||
|
|
### 3.6 Legacy-Daten-Migration (Blocker b)
|
|||
|
|
|
|||
|
|
-**Problem:** Bestehende DB-Rows mit Namen außerhalb des neuen Patterns werden durch `_validate_project_name()` (aufgerufen in `resolve_project`) unsichtbar, weil die Validierung VOR dem SELECT kommt.
|
|||
|
|
+**Neue Datei:** `utils/migrate_legacy_project_names.py`
|
|||
|
|
+
|
|||
|
|
+Logik: SELECT alle Projekte → prüfe ob Name Pattern + nicht in Blacklist → wenn nicht: status='archived' in project_state, WARN-Log → Bericht "X archived, Y active".
|
|||
|
|
+
|
|||
|
|
+**Aufruf:** Manuell. Im aktuellen Datenbestand nur leocrm (matcht) → leerer Archivierungs-Lauf.
|
|||
|
|
+
|
|||
|
|
+### 3.7 `tools/plan_mode_guard.py` umbauen (Bug 7, Block 7b)
|
|||
|
|
+
|
|||
|
|
+**Problem:** `plan_mode_guard.py:76` ruft `resolve_project(name)` auf. Nach dem Fix wirft das für unbekannte Namen `LookupError`. Außerdem: Plan-Mode ist plugin-weit, nicht projekt-spezifisch.
|
|||
|
|
+
|
|||
|
|
+**Lösung:** Plan-Mode als globaler Key ohne Projekt-Bindung.
|
|||
|
|
+
|
|||
|
|
+**Konzept:**
|
|||
|
|
+- Statt `orch_kv[pid]['orchestrator_mode']`: globaler Key z.B. in `project_state` mit `project_id=0` (Platzhalter) ODER in einer neuen Tabelle `plugin_settings(key, value_json)`.
|
|||
|
|
+- Pragmatisch: erstere Variante (Platzhalter project_id=0) – keine Schema-Änderung.
|
|||
|
|
|
|||
|
|
-**Lösungsansatz:** Einmaliges Migrations-Snippet in `utils/migrate_legacy_project_names.py` (neu):
|
|||
|
|
+**Konkret (Pseudocode für `plan_mode_guard.py`):**
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
-# Vereinfachte Logik:
|
|||
|
|
-# 1) SELECT id, name FROM projects;
|
|||
|
|
-# 2) Pro Row: prüfe ob name das Pattern matcht UND nicht in Blacklist ist
|
|||
|
|
-# 3) Wenn nicht: setze status='archived' in project_state, logge WARN
|
|||
|
|
-# 4) Bericht am Ende: "X archived, Y active"
|
|||
|
|
+@_db_error_handler
|
|||
|
|
+def _get_plugin_mode() -> dict:
|
|||
|
|
+ db = _db()
|
|||
|
|
+ row = db.conn.execute(
|
|||
|
|
+ "SELECT value_json FROM orch_kv "
|
|||
|
|
+ "WHERE project_id = 0 AND key = 'orchestrator_mode'"
|
|||
|
|
+ ).fetchone()
|
|||
|
|
+ if not row:
|
|||
|
|
+ return dict(_DEFAULT_MODE)
|
|||
|
|
+ return _normalize_mode(json.loads(row[0]))
|
|||
|
|
+
|
|||
|
|
+@_db_error_handler
|
|||
|
|
+def _set_plugin_mode(mode_data: dict) -> None:
|
|||
|
|
+ db = _db()
|
|||
|
|
+ db.conn.execute(
|
|||
|
|
+ "INSERT INTO orch_kv (project_id, key, value_json, updated_at) "
|
|||
|
|
+ "VALUES (0, 'orchestrator_mode', ?, datetime('now')) "
|
|||
|
|
+ "ON CONFLICT(project_id, key) DO UPDATE SET "
|
|||
|
|
+ " value_json = excluded.value_json, updated_at = datetime('now')",
|
|||
|
|
+ (json.dumps(mode_data),)
|
|||
|
|
+ )
|
|||
|
|
+ db.conn.commit()
|
|||
|
|
|
|||
|
|
-# Im aktuellen Datenbestand ist nur leocrm vorhanden (matcht Pattern),
|
|||
|
|
-# also leerer Archivierungs-Lauf zu erwarten.
|
|||
|
|
+class PlanModeGuard(Tool):
|
|||
|
|
+ async def execute(self, requested_action: str = "", new_mode: str = "", **kwargs):
|
|||
|
|
+ # KEIN resolve_project-Aufruf mehr!
|
|||
|
|
+ if new_mode:
|
|||
|
|
+ if new_mode not in _MODE_POLICIES:
|
|||
|
|
+ return Response(message=json.dumps({"verdict": "ERROR", "error": f"invalid mode: {new_mode}"}), break_loop=False)
|
|||
|
|
+ mode_data = {
|
|||
|
|
+ "mode": new_mode,
|
|||
|
|
+ "allowed_actions": list(_MODE_POLICIES[new_mode]["allowed_actions"]),
|
|||
|
|
+ "blocked_actions": list(_MODE_POLICIES[new_mode]["blocked_actions"]),
|
|||
|
|
+ }
|
|||
|
|
+ _set_plugin_mode(mode_data)
|
|||
|
|
+ return Response(message=json.dumps({"verdict": "ALLOWED", "mode": new_mode, "transitioned": True}, indent=2), break_loop=False)
|
|||
|
|
+
|
|||
|
|
+ mode_data = _get_plugin_mode()
|
|||
|
|
+ current_mode = mode_data["mode"]
|
|||
|
|
+ allowed = mode_data["allowed_actions"]
|
|||
|
|
+ blocked = mode_data["blocked_actions"]
|
|||
|
|
+ if requested_action and requested_action in blocked:
|
|||
|
|
+ return Response(message=f"BLOCKED: action '{requested_action}' not allowed in mode '{current_mode}'", break_loop=False)
|
|||
|
|
+ if not requested_action or requested_action in allowed:
|
|||
|
|
+ return Response(message=json.dumps({"verdict": "ALLOWED", "mode": current_mode, "action": requested_action}, indent=2), break_loop=False)
|
|||
|
|
+ return Response(message=json.dumps({"verdict": "UNKNOWN", "mode": current_mode, "action": requested_action}, indent=2), break_loop=False)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
-**Aufruf:** Manuell oder automatisch beim Plugin-Start (einmalig, mit Flag `--already-migrated` zur Idempotenz).
|
|||
|
|
+**Migration:** Bestehende `orch_kv[pid]['orchestrator_mode']` (z.B. für leocrm, a0_software_orchestrator) werden IGNORIERT. Neue globale Sicht unter `project_id=0` gewinnt. Cleanup der Alt-Einträge: manuell oder in Legacy-Migration §3.6 mit-erledigen.
|
|||
|
|
|
|||
|
|
## 4. Test-Plan
|
|||
|
|
|
|||
|
|
@@ -255,57 +297,63 @@
|
|||
|
|
1. `test_validate_project_name_ok` – `"crm-system"`, `"web-cad"`, `"rentman-clone"`, `"a1b"` → ok
|
|||
|
|
2. `test_validate_project_name_pattern_fail` – `"A0"`, `"Crm System"`, `"a"`, `""`, `"my.app"`, `"cool_app"`, `"müller"` → ValueError
|
|||
|
|
3. `test_validate_project_name_blacklist` – `"a0"`, `"a0-development"`, `"workdir"`, `"dev-projects"` → ValueError
|
|||
|
|
-4. `test_validate_project_name_whitespace_strip` – `" crm "` → `"crm"` (gestrippt)
|
|||
|
|
+4. `test_validate_project_name_whitespace_strip` – `" crm "` → `"crm"`
|
|||
|
|
|
|||
|
|
-**register_project (3 Cases):**
|
|||
|
|
-5. `test_register_project_new` – neues Projekt → id, in DB
|
|||
|
|
-6. `test_register_project_idempotent` – zweimal registrieren mit gleichen Daten → gleiche id
|
|||
|
|
-7. `test_register_project_value_error_wraps_validator` – invalider Name → ValueError vom Wrapper (nicht nur vom Validator)
|
|||
|
|
-8. `test_register_project_idempotent_overwrite_semantics` – zweimal mit verschiedenen `git_url` → welche gewinnt? (Doku: COALESCE-Logik in Zeile 657-661)
|
|||
|
|
-
|
|||
|
|
-**resolve_project (3 Cases):**
|
|||
|
|
-9. `test_resolve_project_existing` – registered project → id, last_active_at aktualisiert
|
|||
|
|
-10. `test_resolve_project_missing_raises` – nicht registriert → LookupError (kein Auto-Register mehr!)
|
|||
|
|
-11. `test_resolve_project_invalid_name_raises` – Name aus Blacklist → ValueError (bevor SELECT läuft)
|
|||
|
|
-12. `test_resolve_project_touches_last_active_at` – zweimal rufen, last_active_at verändert sich
|
|||
|
|
+**register_project (4 Cases):**
|
|||
|
|
+5. `test_register_project_new`
|
|||
|
|
+6. `test_register_project_idempotent`
|
|||
|
|
+7. `test_register_project_value_error_wraps_validator`
|
|||
|
|
+8. `test_register_project_idempotent_overwrite_semantics`
|
|||
|
|
+
|
|||
|
|
+**resolve_project (4 Cases):**
|
|||
|
|
+9. `test_resolve_project_existing`
|
|||
|
|
+10. `test_resolve_project_missing_raises` (kein Auto-Register mehr!)
|
|||
|
|
+11. `test_resolve_project_invalid_name_raises` (Blacklist wirft VOR SELECT)
|
|||
|
|
+12. `test_resolve_project_touches_last_active_at`
|
|||
|
|
|
|||
|
|
**get_project_id (1 Case):**
|
|||
|
|
-13. `test_get_project_id_missing_returns_none` – nicht registriert → None
|
|||
|
|
+13. `test_get_project_id_missing_returns_none`
|
|||
|
|
|
|||
|
|
**Legacy-Migration (1 Case):**
|
|||
|
|
-14. `test_legacy_row_with_invalid_name_is_archived` – INSERT einer Row mit Name `"OLD-PROJECT"` direkt in DB → Migrations-Run setzt status='archived'
|
|||
|
|
+14. `test_legacy_row_with_invalid_name_is_archived`
|
|||
|
|
|
|||
|
|
**End-to-End (1 Case):**
|
|||
|
|
-15. `test_e2e_next_step_with_unknown_project_raises` – Aufruf von `next_step` mit unbekanntem Projekt → LookupError, **kein** DB-Eintrag enstanden
|
|||
|
|
+15. `test_e2e_next_step_with_unknown_project_raises`
|
|||
|
|
+
|
|||
|
|
+**plan_mode_guard global (2 Cases):**
|
|||
|
|
+16. `test_plan_mode_guard_set_get` – set implementation_active, read back, no project_id needed
|
|||
|
|
+17. `test_plan_mode_guard_no_resolve_project_called` – Mock resolve_project, ensure NOT called
|
|||
|
|
|
|||
|
|
## 5. Risiken & Migrationspfad
|
|||
|
|
|
|||
|
|
-- **Breaking Change** für alle 18 resolve_project-Aufrufer (10 Tools + 7 Helper-Sites + 1 Skript). Mitigation: LookupError-Handling an Aufrufstelle ist explizit und sichtbar.
|
|||
|
|
-- **Legacy-Daten:** Bestehende Projekte mit Namen außerhalb des Patterns werden unsichtbar. Mitigation: Legacy-Migrations-Snippet in §3.6.
|
|||
|
|
-- **Aktive User-Flows:** User-Dialoge, die heute `resolve_project` mit projektnamen aus User-Eingabe füttern, brechen jetzt hart mit LookupError statt Auto-Register. Mitigation: klarer Fehlertext "project '{name}' is not registered. Call register_project() or project_registry action=register first." → User kann selbst korrigieren.
|
|||
|
|
-- **Plugin-Reload** mitten in laufendem Tool-Call: mixed old/new code. Minor.
|
|||
|
|
-- **Concurrency:** SQLite single-writer, ON CONFLICT in register_project, kein neues Risiko. ✓
|
|||
|
|
-- **Out-of-scope Side-Note:** `helpers/library/db.py:PatternDB.register_project` bleibt ungeprüft – separater Fix nötig, nicht Teil dieses Tickets.
|
|||
|
|
+- **Breaking Change** für 17 resolve_project-Aufrufer (10 Tools – 1 plan_mode_guard = 9 + 7 Helper-Sites + 1 Skript = 17). `plan_mode_guard` wird komplett umgebaut (kein resolve_project). Mitigation: LookupError-Handling + globaler Mode-Key.
|
|||
|
|
+- **Legacy-Daten:** §3.6.
|
|||
|
|
+- **Aktive User-Flows:** `resolve_project` mit projektnamen aus User-Eingabe bricht jetzt hart mit LookupError. Mitigation: klarer Fehlertext.
|
|||
|
|
+- **Plugin-Reload mitten im Tool-Call:** minor.
|
|||
|
|
+- **Concurrency:** SQLite single-writer, kein neues Risiko. ✓
|
|||
|
|
+- **Out-of-scope:** `PatternDB.register_project` (library), `leocrm`-Daten-Audit.
|
|||
|
|
- **Bestehende Projekte:** `leocrm` matcht Pattern und ist nicht in Blacklist → keine Daten-Migration nötig.
|
|||
|
|
+- **plan_mode_guard-Migration:** globale Sicht unter `project_id=0` löst pro-projekt-Sicht ab. Alt-Einträge werden ignoriert, Cleanup optional.
|
|||
|
|
|
|||
|
|
## 6. Freigabe-Checkliste
|
|||
|
|
|
|||
|
|
-- [ ] User hat Plan v2 gelesen
|
|||
|
|
-- [ ] User gibt frei: Wechsel auf `implementation_allowed`
|
|||
|
|
+- [ ] User hat Plan v3 gelesen
|
|||
|
|
- [ ] User gibt frei: Implementierung über `implementation_engineer` Subagent
|
|||
|
|
-- [ ] Bug 1–6 adressiert
|
|||
|
|
-- [ ] 18 Call-Sites (10 Tools + 7 Helper + 1 Skript) angepasst
|
|||
|
|
+- [ ] Bug 1–7 adressiert (inkl. Bug 7: plan_mode_guard-Umbau)
|
|||
|
|
+- [ ] 17 Call-Sites (9 Tools + 7 Helper + 1 Skript) angepasst
|
|||
|
|
+- [ ] `plan_mode_guard.py` umgebaut (Bug 7, §3.7)
|
|||
|
|
- [ ] 2 basename-Fallbacks entfernt
|
|||
|
|
- [ ] Modul-Docstring aktualisiert (Bug 6)
|
|||
|
|
-- [ ] 15 Test-Cases geschrieben + grün
|
|||
|
|
-- [ ] Legacy-Migrations-Snippet erstellt + ausgeführt (leocrm bleibt aktiv)
|
|||
|
|
+- [ ] 17 Test-Cases geschrieben + grün
|
|||
|
|
+- [ ] Legacy-Migrations-Snippet erstellt + ausgeführt
|
|||
|
|
- [ ] Optional: `quality_reviewer` Re-Review nach Implementierung
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
-**Geschätzter Aufwand:** 5–7 Subagent-Calls (1× Patch-Helper, 1× Migration-Tools, 1× Migration-Helpers, 1× Migration-Skripte, 1× Tests, 1× Legacy-Migration, 1× Final-Verify).
|
|||
|
|
+**Geschätzter Aufwand:** 5–6 Subagent-Calls (1× großer Patch-Block via implementation_engineer, 1× Tests via test_debug_engineer, 1× Quality-Review, 1× Final-Verify).
|
|||
|
|
|
|||
|
|
**Reviewer-Verdict:** CONDITIONAL → nach Einarbeitung dieser 3 Blocker freigabefähig.
|
|||
|
|
-- (a) Call-Site-Inventur: 14 → 18 korrigiert ✓
|
|||
|
|
-- (b) Legacy-Migration: §3.6 + Test 14 ergänzt ✓
|
|||
|
|
-- (c) PatternDB.register_project: out-of-scope dokumentiert ✓
|
|||
|
|
+- (a) Call-Site-Inventur: 14 → 17 (plan_mode_guard ausgenommen) ✓
|
|||
|
|
+- (b) Legacy-Migration: §3.6 + Test 14 ✓
|
|||
|
|
+- (c) PatternDB.register_project: out-of-scope ✓
|
|||
|
|
+- (d) Bug 7 (plan_mode_guard): §3.7 + Tests 16-17 ergänzt ✓
|
|||
|
|
diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/docs/diff-v3-vs-original.patch /a0/usr/plugins/a0_software_orchestrator/docs/diff-v3-vs-original.patch
|
|||
|
|
--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/docs/diff-v3-vs-original.patch 1970-01-01 00:00:00.000000000 +0000
|
|||
|
|
+++ /a0/usr/plugins/a0_software_orchestrator/docs/diff-v3-vs-original.patch 2026-06-16 10:18:53.359469811 +0000
|
|||
|
|
@@ -0,0 +1,349 @@
|
|||
|
|
+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
|
|||
|
|
+diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/docs/bugfix-auto-registration.md /a0/usr/plugins/a0_software_orchestrator/docs/bugfix-auto-registration.md
|
|||
|
|
+--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/docs/bugfix-auto-registration.md 2026-06-16 09:19:29.000000000 +0000
|
|||
|
|
++++ /a0/usr/plugins/a0_software_orchestrator/docs/bugfix-auto-registration.md 2026-06-16 10:04:32.559404096 +0000
|
|||
|
|
+@@ -1,44 +1,51 @@
|
|||
|
|
+ # Bugfix-Plan: Auto-Registration in `resolve_project()`
|
|||
|
|
+
|
|||
|
|
+ **Datum:** 2026-06-16
|
|||
|
|
+-**Status:** Plan v2 – CONDITIONAL vom Quality-Reviewer, 3 Blocker adressiert
|
|||
|
|
+-**Phase:** Plan-Mode `planning_only`
|
|||
|
|
++**Status:** Plan v3 – CONDITIONAL adressiert + Block 7b (plan_mode_guard) ergänzt
|
|||
|
|
++**Phase:** Plan-Mode `implementation_allowed` (gesetzt für `a0_software_orchestrator` Meta-Projekt)
|
|||
|
|
+ **Betroffenes Plugin:** `a0_software_orchestrator`
|
|||
|
|
+
|
|||
|
|
+ ---
|
|||
|
|
+
|
|||
|
|
+ ## 1. Problem (Ist-Zustand)
|
|||
|
|
+
|
|||
|
|
+-`resolve_project(name)` in `helpers/db_state_store.py` Zeile 60–109 ruft bei unbekanntem Namen **automatisch** ein `INSERT INTO projects` auf, ohne Plausiprüfung. Sechs Folge-Bugs:
|
|||
|
|
++`resolve_project(name)` in `helpers/db_state_store.py` Zeile 60–109 ruft bei unbekanntem Namen **automatisch** ein `INSERT INTO projects` auf, ohne Plausiprüfung. Sechs Folge-Bugs + ein Siebter (Fund bei Modus-Wechsel):
|
|||
|
|
+
|
|||
|
|
+ | # | Bug | Datei / Zeile | Folge |
|
|||
|
|
+ |---|-----|--------------|-------|
|
|||
|
|
+ | 1 | `resolve_project()` akzeptiert jeden String | `db_state_store.py:60-109` | Schattenprojekte wie `a0`, `a0-development`, `workdir` (3 bereits gelöscht) |
|
|||
|
|
+-| 2 | `register_project()` ohne Plausiprüfung | `db_state_store.py:644-687` | Auch der explizite Pfad akzeptiert Müll |
|
|||
|
|
++| 2 | `register_project()` ohne Plausiprüfung | `db_state_store.py:644-687` | Auch der explekite Pfad akzeptiert Müll |
|
|||
|
|
+ | 3 | `os.path.basename(...)` als Projektname | `migrate_a0_to_db.py:398` | Verzeichnisname wird stillschweigend zum Projektnamen |
|
|||
|
|
+ | 4 | `os.path.basename(...)` als Projektname | `api/project_state_get.py:16` | API-Call mit `project_root` erzeugt Phantom-Projekt |
|
|||
|
|
+ | 5 | `resolve_project()` macht UPSERT auf `project_state.last_active_at` bei JEDEM Aufruf | `db_state_store.py:82-89` | Versteckter Side-Effect: jeder Tool-Aufruf verändert DB-State |
|
|||
|
|
+ | 6 | Modul-Docstring empfiehlt `resolve_project()` weiterhin | `db_state_store.py:1-19, 127` | Nach Fix irreführend → muss mit-aktualisiert werden |
|
|||
|
|
++| 7 | `plan_mode_guard.py` ruft `resolve_project()` (Auto-Register-Bug) | `plan_mode_guard.py:76` | Modus-Wechsel für unbekannte Projektnamen erzeugt Phantom; nach Fix scheitert LookupError |
|
|||
|
|
+
|
|||
|
|
+ ## 2. Soll-Zustand
|
|||
|
|
+
|
|||
|
|
+ - **Zwei saubere Funktionen mit klarer Semantik:**
|
|||
|
|
+ - `get_project_id(name)` – nur Lesen, gibt `int | None` zurück.
|
|||
|
|
+ - `register_project(name, git_url, description, ...)` – nur Anlegen/Aktualisieren, **mit Plausiprüfung**.
|
|||
|
|
+-- `resolve_project()` wird zum dünnen Komfort-Wrapper, der **bei fehlendem Projekt `LookupError` wirft** statt heimlich anzulegen. Der `last_active_at`-UPSERT bleibt erhalten, weil er als "Aktivitätsmarker" semantisch nützlich ist – wird aber in Tests explizit verifiziert.
|
|||
|
|
++- `resolve_project()` wird zum dünnen Komfort-Wrapper, der **bei fehlendem Projekt `LookupError` wirft** statt heimlich anzulegen. Der `last_active_at`-UPSERT bleibt erhalten.
|
|||
|
|
++- `plan_mode_guard.py` arbeitet **projekt-unabhängig**: liest/schreibt einen globalen Key `orchestrator_mode` (nicht pro project_id). Kein `resolve_project()`-Aufruf mehr.
|
|||
|
|
+ - Pattern + Blacklist verhindern ungültige Namen direkt bei `register_project()`.
|
|||
|
|
+ - `basename`-Fallbacks in `migrate_a0_to_db.py` und `api/project_state_get.py` werden entfernt.
|
|||
|
|
+
|
|||
|
|
+ **Pattern-Akzeptanzkriterien (explizit):**
|
|||
|
|
+ - Erlaubt: `^[a-z][a-z0-9-]{1,40}$` (lowercase, Buchstaben/Ziffern/Bindestrich, 2–41 Zeichen, mit Buchstabe beginnend).
|
|||
|
|
+-- **Nicht erlaubt:** Punkte (`.`), Underscores (`_`), Umlaute, Leerzeichen, Großbuchstaben. Das ist **bewusst restriktiv**, weil Software-Projektnamen sonst zu viel Variabilität erlauben.
|
|||
|
|
+-- Falls User Namen mit Punkten/Underscores braucht: Blacklist/Pattern explizit erweitern (Diskussion erforderlich).
|
|||
|
|
++- **Nicht erlaubt:** Punkte, Underscores, Umlaute, Leerzeichen, Großbuchstaben.
|
|||
|
|
+
|
|||
|
|
+ **Blacklist-Klärung:**
|
|||
|
|
+-- `a0-development` ist **bewusst** in der Blacklist. Falls es künftig doch als DB-Eintrag gewünscht ist: Blacklist-Eintrag entfernen + explizit `register_project("a0-development", ...)` aufrufen. Der Satz in §5 der ersten Plan-Version war missverständlich und ist hiermit korrigiert.
|
|||
|
|
++- `a0-development` ist **bewusst** in der Blacklist. Wer es künftig als DB-Eintrag will: Blacklist-Eintrag entfernen + explizit `register_project("a0-development", ...)` aufrufen.
|
|||
|
|
+
|
|||
|
|
+-**Out-of-scope (explizit dokumentiert):**
|
|||
|
|
+-- `helpers/library/db.py` enthält eine separate Klasse `PatternDB.register_project()` (Z. 117, 142, 160, 222). Das ist ein anderer Code-Pfad (DB-Layer, nicht Project-Layer) und wird in diesem Fix **nicht** angefasst. Side-Note: zukünftiges Risiko, sollte in einem separaten Ticket adressiert werden.
|
|||
|
|
++**Meta-Projekt `a0_software_orchestrator`:**
|
|||
|
|
++- Bereits vor dem Fix explizit registriert (project_id=5) als Träger für Plugin-Level-State (plan_mode etc.).
|
|||
|
|
++- Matcht das neue Pattern NICHT (Underscore), aber Existenz in DB bleibt erhalten (register_project war vor dem neuen Pattern aktiv).
|
|||
|
|
++- Wird nach dem Fix nicht von `resolve_project` blockiert, weil `plan_mode_guard` kein `resolve_project` mehr ruft (siehe §3.7).
|
|||
|
|
++
|
|||
|
|
++**Out-of-scope (explizit):**
|
|||
|
|
++- `helpers/library/db.py:PatternDB.register_project` (DB-Layer, nicht Project-Layer) – separater Fix.
|
|||
|
|
++- Side-Note: `leocrm` hat bereits orch_kv-Einträge (`phase=implementation`, `orchestrator_mode=implementation_allowed`, `project_root=/a0/usr/workdir/dev-projects/leocrm`) aus früherer Session. Inkonsistenz mit `project_state.plan_mode=None`. **Nicht Teil dieses Bugs**, separater Audit-Task.
|
|||
|
|
+
|
|||
|
|
+ ## 3. Konkrete Änderungen
|
|||
|
|
+
|
|||
|
|
+@@ -57,8 +64,7 @@
|
|||
|
|
+ })
|
|||
|
|
+
|
|||
|
|
+ def _validate_project_name(name: str) -> str:
|
|||
|
|
+- """Strippt, prüft Pattern + Blacklist. Gibt bereinigten Namen zurück.
|
|||
|
|
+- Wirft ValueError bei Verletzung."""
|
|||
|
|
++ """Strippt, prüft Pattern + Blacklist. Wirft ValueError bei Verletzung."""
|
|||
|
|
+ if not name or not name.strip():
|
|||
|
|
+ raise ValueError("project name must be non-empty")
|
|||
|
|
+ name = name.strip()
|
|||
|
|
+@@ -97,7 +103,6 @@
|
|||
|
|
+ f"Call register_project() or project_registry action=register first."
|
|||
|
|
+ )
|
|||
|
|
+ project_id = int(row[0])
|
|||
|
|
+- # Touch last_active_at (Aktivitätsmarker – gewünschter Side-Effect)
|
|||
|
|
+ db.conn.execute(
|
|||
|
|
+ """
|
|||
|
|
+ INSERT INTO project_state (project_id, status, phase, last_active_at)
|
|||
|
|
+@@ -118,7 +123,7 @@
|
|||
|
|
+ tech_stack: str = "{}", status: str = "active",
|
|||
|
|
+ phase: str = "intake") -> int:
|
|||
|
|
+ """Register or update a project. Returns project_id."""
|
|||
|
|
+- name = _validate_project_name(name) # NEU
|
|||
|
|
++ name = _validate_project_name(name)
|
|||
|
|
+ # ... Rest wie bisher (INSERT … ON CONFLICT, project_state upsert, set_kv)
|
|||
|
|
+ ```
|
|||
|
|
+
|
|||
|
|
+@@ -163,7 +168,7 @@
|
|||
|
|
+ - `block_resume.py:54`
|
|||
|
|
+ - `next_step.py:49`
|
|||
|
|
+ - `orchestrator_state.py:48`
|
|||
|
|
+-- `plan_mode_guard.py:76`
|
|||
|
|
++- `plan_mode_guard.py:76` → wird komplett umgebaut, siehe §3.7
|
|||
|
|
+ - `quality_gate.py:388`
|
|||
|
|
+ - `repo_manifest.py:64`
|
|||
|
|
+ - `resume_checker.py:40`
|
|||
|
|
+@@ -171,32 +176,22 @@
|
|||
|
|
+ - `tool_registry.py:50`
|
|||
|
|
+
|
|||
|
|
+ **NICHT geändert** (verwenden bereits `get_project_id`):
|
|||
|
|
+-- `extensions/python/monologue_start/load_project_state.py:30` – Bestätigung, dass das der richtige Pfad ist.
|
|||
|
|
++- `extensions/python/monologue_start/load_project_state.py:30`
|
|||
|
|
+
|
|||
|
|
+ ### 3.3 Helper-Aufrufer migrieren (4 Helper-Dateien / 7 Call-Sites)
|
|||
|
|
+
|
|||
|
|
+-Gleiche Umstellung auf `get_project_id` + `LookupError`:
|
|||
|
|
+-
|
|||
|
|
+ | Datei | Call-Sites |
|
|||
|
|
+ |-------|-----------|
|
|||
|
|
+ | `helpers/artifact_rules.py` | 1 (Z. 53) |
|
|||
|
|
+ | `helpers/briefing_file.py` | 2 (Z. 62, 134) |
|
|||
|
|
+ | `helpers/compact_protocol.py` | 3 (Z. 177, 218, 243) |
|
|||
|
|
+ | `helpers/resume_state.py` | 1 (Z. 61) |
|
|||
|
|
+-| **Total** | **7 Call-Sites in 4 Dateien** |
|
|||
|
|
+
|
|||
|
|
+ ### 3.4 `utils/migrate_a0_to_db.py`
|
|||
|
|
+
|
|||
|
|
+ **Zeile 395–419 (basename-Fallback entfernen, Bug 3):**
|
|||
|
|
+
|
|||
|
|
+ ```python
|
|||
|
|
+-# Vorher:
|
|||
|
|
+-if os.path.isabs(args.project) and os.path.isdir(args.project):
|
|||
|
|
+- project_root = args.project
|
|||
|
|
+- project_name = os.path.basename(project_root.rstrip("/"))
|
|||
|
|
+-...
|
|||
|
|
+-
|
|||
|
|
+-# Nachher:
|
|||
|
|
+ if not args.project:
|
|||
|
|
+ print("ERROR: --project=<name> ist Pflicht (basename-Fallback entfernt)")
|
|||
|
|
+ sys.exit(1)
|
|||
|
|
+@@ -210,17 +205,12 @@
|
|||
|
|
+ ```
|
|||
|
|
+
|
|||
|
|
+ **Zeile 213:** `pid = resolve_project(project_name)` → `pid = register_project(project_name, git_url="")`
|
|||
|
|
+-(wenn schon vorhanden: idempotent, schadet nicht. Wenn nicht: legt sauber an.)
|
|||
|
|
+
|
|||
|
|
+ ### 3.5 `api/project_state_get.py`
|
|||
|
|
+
|
|||
|
|
+ **Zeile 16 ersetzen (Bug 4):**
|
|||
|
|
+
|
|||
|
|
+ ```python
|
|||
|
|
+-# Vorher:
|
|||
|
|
+-project_name = input.get("project_name") or os.path.basename(project_root.rstrip("/"))
|
|||
|
|
+-
|
|||
|
|
+-# Nachher:
|
|||
|
|
+ project_name = input.get("project_name")
|
|||
|
|
+ if not project_name:
|
|||
|
|
+ return {"ok": False, "error": "project_name is required (basename fallback removed)"}
|
|||
|
|
+@@ -228,22 +218,74 @@
|
|||
|
|
+
|
|||
|
|
+ ### 3.6 Legacy-Daten-Migration (Blocker b)
|
|||
|
|
+
|
|||
|
|
+-**Problem:** Bestehende DB-Rows mit Namen außerhalb des neuen Patterns werden durch `_validate_project_name()` (aufgerufen in `resolve_project`) unsichtbar, weil die Validierung VOR dem SELECT kommt.
|
|||
|
|
++**Neue Datei:** `utils/migrate_legacy_project_names.py`
|
|||
|
|
++
|
|||
|
|
++Logik: SELECT alle Projekte → prüfe ob Name Pattern + nicht in Blacklist → wenn nicht: status='archived' in project_state, WARN-Log → Bericht "X archived, Y active".
|
|||
|
|
++
|
|||
|
|
++**Aufruf:** Manuell. Im aktuellen Datenbestand nur leocrm (matcht) → leerer Archivierungs-Lauf.
|
|||
|
|
++
|
|||
|
|
++### 3.7 `tools/plan_mode_guard.py` umbauen (Bug 7, Block 7b)
|
|||
|
|
++
|
|||
|
|
++**Problem:** `plan_mode_guard.py:76` ruft `resolve_project(name)` auf. Nach dem Fix wirft das für unbekannte Namen `LookupError`. Außerdem: Plan-Mode ist plugin-weit, nicht projekt-spezifisch.
|
|||
|
|
++
|
|||
|
|
++**Lösung:** Plan-Mode als globaler Key ohne Projekt-Bindung.
|
|||
|
|
++
|
|||
|
|
++**Konzept:**
|
|||
|
|
++- Statt `orch_kv[pid]['orchestrator_mode']`: globaler Key z.B. in `project_state` mit `project_id=0` (Platzhalter) ODER in einer neuen Tabelle `plugin_settings(key, value_json)`.
|
|||
|
|
++- Pragmatisch: erstere Variante (Platzhalter project_id=0) – keine Schema-Änderung.
|
|||
|
|
+
|
|||
|
|
+-**Lösungsansatz:** Einmaliges Migrations-Snippet in `utils/migrate_legacy_project_names.py` (neu):
|
|||
|
|
++**Konkret (Pseudocode für `plan_mode_guard.py`):**
|
|||
|
|
+
|
|||
|
|
+ ```python
|
|||
|
|
+-# Vereinfachte Logik:
|
|||
|
|
+-# 1) SELECT id, name FROM projects;
|
|||
|
|
+-# 2) Pro Row: prüfe ob name das Pattern matcht UND nicht in Blacklist ist
|
|||
|
|
+-# 3) Wenn nicht: setze status='archived' in project_state, logge WARN
|
|||
|
|
+-# 4) Bericht am Ende: "X archived, Y active"
|
|||
|
|
++@_db_error_handler
|
|||
|
|
++def _get_plugin_mode() -> dict:
|
|||
|
|
++ db = _db()
|
|||
|
|
++ row = db.conn.execute(
|
|||
|
|
++ "SELECT value_json FROM orch_kv "
|
|||
|
|
++ "WHERE project_id = 0 AND key = 'orchestrator_mode'"
|
|||
|
|
++ ).fetchone()
|
|||
|
|
++ if not row:
|
|||
|
|
++ return dict(_DEFAULT_MODE)
|
|||
|
|
++ return _normalize_mode(json.loads(row[0]))
|
|||
|
|
++
|
|||
|
|
++@_db_error_handler
|
|||
|
|
++def _set_plugin_mode(mode_data: dict) -> None:
|
|||
|
|
++ db = _db()
|
|||
|
|
++ db.conn.execute(
|
|||
|
|
++ "INSERT INTO orch_kv (project_id, key, value_json, updated_at) "
|
|||
|
|
++ "VALUES (0, 'orchestrator_mode', ?, datetime('now')) "
|
|||
|
|
++ "ON CONFLICT(project_id, key) DO UPDATE SET "
|
|||
|
|
++ " value_json = excluded.value_json, updated_at = datetime('now')",
|
|||
|
|
++ (json.dumps(mode_data),)
|
|||
|
|
++ )
|
|||
|
|
++ db.conn.commit()
|
|||
|
|
+
|
|||
|
|
+-# Im aktuellen Datenbestand ist nur leocrm vorhanden (matcht Pattern),
|
|||
|
|
+-# also leerer Archivierungs-Lauf zu erwarten.
|
|||
|
|
++class PlanModeGuard(Tool):
|
|||
|
|
++ async def execute(self, requested_action: str = "", new_mode: str = "", **kwargs):
|
|||
|
|
++ # KEIN resolve_project-Aufruf mehr!
|
|||
|
|
++ if new_mode:
|
|||
|
|
++ if new_mode not in _MODE_POLICIES:
|
|||
|
|
++ return Response(message=json.dumps({"verdict": "ERROR", "error": f"invalid mode: {new_mode}"}), break_loop=False)
|
|||
|
|
++ mode_data = {
|
|||
|
|
++ "mode": new_mode,
|
|||
|
|
++ "allowed_actions": list(_MODE_POLICIES[new_mode]["allowed_actions"]),
|
|||
|
|
++ "blocked_actions": list(_MODE_POLICIES[new_mode]["blocked_actions"]),
|
|||
|
|
++ }
|
|||
|
|
++ _set_plugin_mode(mode_data)
|
|||
|
|
++ return Response(message=json.dumps({"verdict": "ALLOWED", "mode": new_mode, "transitioned": True}, indent=2), break_loop=False)
|
|||
|
|
++
|
|||
|
|
++ mode_data = _get_plugin_mode()
|
|||
|
|
++ current_mode = mode_data["mode"]
|
|||
|
|
++ allowed = mode_data["allowed_actions"]
|
|||
|
|
++ blocked = mode_data["blocked_actions"]
|
|||
|
|
++ if requested_action and requested_action in blocked:
|
|||
|
|
++ return Response(message=f"BLOCKED: action '{requested_action}' not allowed in mode '{current_mode}'", break_loop=False)
|
|||
|
|
++ if not requested_action or requested_action in allowed:
|
|||
|
|
++ return Response(message=json.dumps({"verdict": "ALLOWED", "mode": current_mode, "action": requested_action}, indent=2), break_loop=False)
|
|||
|
|
++ return Response(message=json.dumps({"verdict": "UNKNOWN", "mode": current_mode, "action": requested_action}, indent=2), break_loop=False)
|
|||
|
|
+ ```
|
|||
|
|
+
|
|||
|
|
+-**Aufruf:** Manuell oder automatisch beim Plugin-Start (einmalig, mit Flag `--already-migrated` zur Idempotenz).
|
|||
|
|
++**Migration:** Bestehende `orch_kv[pid]['orchestrator_mode']` (z.B. für leocrm, a0_software_orchestrator) werden IGNORIERT. Neue globale Sicht unter `project_id=0` gewinnt. Cleanup der Alt-Einträge: manuell oder in Legacy-Migration §3.6 mit-erledigen.
|
|||
|
|
+
|
|||
|
|
+ ## 4. Test-Plan
|
|||
|
|
+
|
|||
|
|
+@@ -255,57 +297,63 @@
|
|||
|
|
+ 1. `test_validate_project_name_ok` – `"crm-system"`, `"web-cad"`, `"rentman-clone"`, `"a1b"` → ok
|
|||
|
|
+ 2. `test_validate_project_name_pattern_fail` – `"A0"`, `"Crm System"`, `"a"`, `""`, `"my.app"`, `"cool_app"`, `"müller"` → ValueError
|
|||
|
|
+ 3. `test_validate_project_name_blacklist` – `"a0"`, `"a0-development"`, `"workdir"`, `"dev-projects"` → ValueError
|
|||
|
|
+-4. `test_validate_project_name_whitespace_strip` – `" crm "` → `"crm"` (gestrippt)
|
|||
|
|
++4. `test_validate_project_name_whitespace_strip` – `" crm "` → `"crm"`
|
|||
|
|
+
|
|||
|
|
+-**register_project (3 Cases):**
|
|||
|
|
+-5. `test_register_project_new` – neues Projekt → id, in DB
|
|||
|
|
+-6. `test_register_project_idempotent` – zweimal registrieren mit gleichen Daten → gleiche id
|
|||
|
|
+-7. `test_register_project_value_error_wraps_validator` – invalider Name → ValueError vom Wrapper (nicht nur vom Validator)
|
|||
|
|
+-8. `test_register_project_idempotent_overwrite_semantics` – zweimal mit verschiedenen `git_url` → welche gewinnt? (Doku: COALESCE-Logik in Zeile 657-661)
|
|||
|
|
+-
|
|||
|
|
+-**resolve_project (3 Cases):**
|
|||
|
|
+-9. `test_resolve_project_existing` – registered project → id, last_active_at aktualisiert
|
|||
|
|
+-10. `test_resolve_project_missing_raises` – nicht registriert → LookupError (kein Auto-Register mehr!)
|
|||
|
|
+-11. `test_resolve_project_invalid_name_raises` – Name aus Blacklist → ValueError (bevor SELECT läuft)
|
|||
|
|
+-12. `test_resolve_project_touches_last_active_at` – zweimal rufen, last_active_at verändert sich
|
|||
|
|
++**register_project (4 Cases):**
|
|||
|
|
++5. `test_register_project_new`
|
|||
|
|
++6. `test_register_project_idempotent`
|
|||
|
|
++7. `test_register_project_value_error_wraps_validator`
|
|||
|
|
++8. `test_register_project_idempotent_overwrite_semantics`
|
|||
|
|
++
|
|||
|
|
++**resolve_project (4 Cases):**
|
|||
|
|
++9. `test_resolve_project_existing`
|
|||
|
|
++10. `test_resolve_project_missing_raises` (kein Auto-Register mehr!)
|
|||
|
|
++11. `test_resolve_project_invalid_name_raises` (Blacklist wirft VOR SELECT)
|
|||
|
|
++12. `test_resolve_project_touches_last_active_at`
|
|||
|
|
+
|
|||
|
|
+ **get_project_id (1 Case):**
|
|||
|
|
+-13. `test_get_project_id_missing_returns_none` – nicht registriert → None
|
|||
|
|
++13. `test_get_project_id_missing_returns_none`
|
|||
|
|
+
|
|||
|
|
+ **Legacy-Migration (1 Case):**
|
|||
|
|
+-14. `test_legacy_row_with_invalid_name_is_archived` – INSERT einer Row mit Name `"OLD-PROJECT"` direkt in DB → Migrations-Run setzt status='archived'
|
|||
|
|
++14. `test_legacy_row_with_invalid_name_is_archived`
|
|||
|
|
+
|
|||
|
|
+ **End-to-End (1 Case):**
|
|||
|
|
+-15. `test_e2e_next_step_with_unknown_project_raises` – Aufruf von `next_step` mit unbekanntem Projekt → LookupError, **kein** DB-Eintrag enstanden
|
|||
|
|
++15. `test_e2e_next_step_with_unknown_project_raises`
|
|||
|
|
++
|
|||
|
|
++**plan_mode_guard global (2 Cases):**
|
|||
|
|
++16. `test_plan_mode_guard_set_get` – set implementation_active, read back, no project_id needed
|
|||
|
|
++17. `test_plan_mode_guard_no_resolve_project_called` – Mock resolve_project, ensure NOT called
|
|||
|
|
+
|
|||
|
|
+ ## 5. Risiken & Migrationspfad
|
|||
|
|
+
|
|||
|
|
+-- **Breaking Change** für alle 18 resolve_project-Aufrufer (10 Tools + 7 Helper-Sites + 1 Skript). Mitigation: LookupError-Handling an Aufrufstelle ist explizit und sichtbar.
|
|||
|
|
+-- **Legacy-Daten:** Bestehende Projekte mit Namen außerhalb des Patterns werden unsichtbar. Mitigation: Legacy-Migrations-Snippet in §3.6.
|
|||
|
|
+-- **Aktive User-Flows:** User-Dialoge, die heute `resolve_project` mit projektnamen aus User-Eingabe füttern, brechen jetzt hart mit LookupError statt Auto-Register. Mitigation: klarer Fehlertext "project '{name}' is not registered. Call register_project() or project_registry action=register first." → User kann selbst korrigieren.
|
|||
|
|
+-- **Plugin-Reload** mitten in laufendem Tool-Call: mixed old/new code. Minor.
|
|||
|
|
+-- **Concurrency:** SQLite single-writer, ON CONFLICT in register_project, kein neues Risiko. ✓
|
|||
|
|
+-- **Out-of-scope Side-Note:** `helpers/library/db.py:PatternDB.register_project` bleibt ungeprüft – separater Fix nötig, nicht Teil dieses Tickets.
|
|||
|
|
++- **Breaking Change** für 17 resolve_project-Aufrufer (10 Tools – 1 plan_mode_guard = 9 + 7 Helper-Sites + 1 Skript = 17). `plan_mode_guard` wird komplett umgebaut (kein resolve_project). Mitigation: LookupError-Handling + globaler Mode-Key.
|
|||
|
|
++- **Legacy-Daten:** §3.6.
|
|||
|
|
++- **Aktive User-Flows:** `resolve_project` mit projektnamen aus User-Eingabe bricht jetzt hart mit LookupError. Mitigation: klarer Fehlertext.
|
|||
|
|
++- **Plugin-Reload mitten im Tool-Call:** minor.
|
|||
|
|
++- **Concurrency:** SQLite single-writer, kein neues Risiko. ✓
|
|||
|
|
++- **Out-of-scope:** `PatternDB.register_project` (library), `leocrm`-Daten-Audit.
|
|||
|
|
+ - **Bestehende Projekte:** `leocrm` matcht Pattern und ist nicht in Blacklist → keine Daten-Migration nötig.
|
|||
|
|
++- **plan_mode_guard-Migration:** globale Sicht unter `project_id=0` löst pro-projekt-Sicht ab. Alt-Einträge werden ignoriert, Cleanup optional.
|
|||
|
|
+
|
|||
|
|
+ ## 6. Freigabe-Checkliste
|
|||
|
|
+
|
|||
|
|
+-- [ ] User hat Plan v2 gelesen
|
|||
|
|
+-- [ ] User gibt frei: Wechsel auf `implementation_allowed`
|
|||
|
|
++- [ ] User hat Plan v3 gelesen
|
|||
|
|
+ - [ ] User gibt frei: Implementierung über `implementation_engineer` Subagent
|
|||
|
|
+-- [ ] Bug 1–6 adressiert
|
|||
|
|
+-- [ ] 18 Call-Sites (10 Tools + 7 Helper + 1 Skript) angepasst
|
|||
|
|
++- [ ] Bug 1–7 adressiert (inkl. Bug 7: plan_mode_guard-Umbau)
|
|||
|
|
++- [ ] 17 Call-Sites (9 Tools + 7 Helper + 1 Skript) angepasst
|
|||
|
|
++- [ ] `plan_mode_guard.py` umgebaut (Bug 7, §3.7)
|
|||
|
|
+ - [ ] 2 basename-Fallbacks entfernt
|
|||
|
|
+ - [ ] Modul-Docstring aktualisiert (Bug 6)
|
|||
|
|
+-- [ ] 15 Test-Cases geschrieben + grün
|
|||
|
|
+-- [ ] Legacy-Migrations-Snippet erstellt + ausgeführt (leocrm bleibt aktiv)
|
|||
|
|
++- [ ] 17 Test-Cases geschrieben + grün
|
|||
|
|
++- [ ] Legacy-Migrations-Snippet erstellt + ausgeführt
|
|||
|
|
+ - [ ] Optional: `quality_reviewer` Re-Review nach Implementierung
|
|||
|
|
+
|
|||
|
|
+ ---
|
|||
|
|
+
|
|||
|
|
+-**Geschätzter Aufwand:** 5–7 Subagent-Calls (1× Patch-Helper, 1× Migration-Tools, 1× Migration-Helpers, 1× Migration-Skripte, 1× Tests, 1× Legacy-Migration, 1× Final-Verify).
|
|||
|
|
++**Geschätzter Aufwand:** 5–6 Subagent-Calls (1× großer Patch-Block via implementation_engineer, 1× Tests via test_debug_engineer, 1× Quality-Review, 1× Final-Verify).
|
|||
|
|
+
|
|||
|
|
+ **Reviewer-Verdict:** CONDITIONAL → nach Einarbeitung dieser 3 Blocker freigabefähig.
|
|||
|
|
+-- (a) Call-Site-Inventur: 14 → 18 korrigiert ✓
|
|||
|
|
+-- (b) Legacy-Migration: §3.6 + Test 14 ergänzt ✓
|
|||
|
|
+-- (c) PatternDB.register_project: out-of-scope dokumentiert ✓
|
|||
|
|
++- (a) Call-Site-Inventur: 14 → 17 (plan_mode_guard ausgenommen) ✓
|
|||
|
|
++- (b) Legacy-Migration: §3.6 + Test 14 ✓
|
|||
|
|
++- (c) PatternDB.register_project: out-of-scope ✓
|
|||
|
|
++- (d) Bug 7 (plan_mode_guard): §3.7 + Tests 16-17 ergänzt ✓
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/extensions/python/message_loop_prompts_after/__pycache__/inject_context_budget.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/extensions/python/message_loop_prompts_after/__pycache__/inject_context_budget.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/extensions/python/monologue_end/__pycache__/remind_state_update.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/extensions/python/monologue_end/__pycache__/remind_state_update.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/extensions/python/monologue_start/__pycache__/load_project_state.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/extensions/python/monologue_start/__pycache__/load_project_state.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/extensions/python/system_prompt/__pycache__/_30_inject_profile_specifics.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/extensions/python/system_prompt/__pycache__/_30_inject_profile_specifics.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/extensions/python/system_prompt/__pycache__/inject_orchestrator_rules.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/extensions/python/system_prompt/__pycache__/inject_orchestrator_rules.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/extensions/python/tool_execute_before/__pycache__/guard_risky_actions.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/extensions/python/tool_execute_before/__pycache__/guard_risky_actions.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/extensions/python/tool_execute_before/__pycache__/guard_risky_actions.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/extensions/python/tool_execute_before/__pycache__/guard_risky_actions.cpython-313.pyc differ
|
|||
|
|
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,
|
|||
|
|
diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/briefing_file.py /a0/usr/plugins/a0_software_orchestrator/helpers/briefing_file.py
|
|||
|
|
--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/briefing_file.py 2026-06-15 14:24:02.000000000 +0000
|
|||
|
|
+++ /a0/usr/plugins/a0_software_orchestrator/helpers/briefing_file.py 2026-06-16 10:11:54.604763089 +0000
|
|||
|
|
@@ -11,7 +11,7 @@
|
|||
|
|
from typing import Any, Dict, List, Optional
|
|||
|
|
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.db_state_store import (
|
|||
|
|
- resolve_project,
|
|||
|
|
+ get_project_id,
|
|||
|
|
append_briefing,
|
|||
|
|
get_briefing,
|
|||
|
|
list_briefings,
|
|||
|
|
@@ -59,7 +59,9 @@
|
|||
|
|
name = project_name.strip() if project_name else os.path.basename(
|
|||
|
|
(project_root or os.getcwd()).rstrip("/")
|
|||
|
|
)
|
|||
|
|
- pid = resolve_project(name)
|
|||
|
|
+ pid = get_project_id(name)
|
|||
|
|
+ if pid is None:
|
|||
|
|
+ raise LookupError(f"project '{name}' is not registered")
|
|||
|
|
|
|||
|
|
# Section-Markdown aufbauen (aus handover_protocol)
|
|||
|
|
from .handover_protocol import render_briefing_section
|
|||
|
|
@@ -131,5 +133,7 @@
|
|||
|
|
name = project_name.strip() if project_name else os.path.basename(
|
|||
|
|
(project_root or os.getcwd()).rstrip("/")
|
|||
|
|
)
|
|||
|
|
- pid = resolve_project(name)
|
|||
|
|
+ pid = get_project_id(name)
|
|||
|
|
+ if pid is None:
|
|||
|
|
+ return []
|
|||
|
|
return list_briefings(pid, specialist=specialist, limit=limit)
|
|||
|
|
diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/compact_protocol.py /a0/usr/plugins/a0_software_orchestrator/helpers/compact_protocol.py
|
|||
|
|
--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/compact_protocol.py 2026-06-15 14:24:02.000000000 +0000
|
|||
|
|
+++ /a0/usr/plugins/a0_software_orchestrator/helpers/compact_protocol.py 2026-06-16 10:12:30.990344305 +0000
|
|||
|
|
@@ -12,7 +12,7 @@
|
|||
|
|
from typing import Any, Dict, List, Optional
|
|||
|
|
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.db_state_store import (
|
|||
|
|
- resolve_project,
|
|||
|
|
+ get_project_id,
|
|||
|
|
list_next_steps,
|
|||
|
|
list_worklog,
|
|||
|
|
list_todos,
|
|||
|
|
@@ -174,7 +174,9 @@
|
|||
|
|
|
|||
|
|
# 6. next_steps nicht leer
|
|||
|
|
try:
|
|||
|
|
- pid = resolve_project(name)
|
|||
|
|
+ pid = get_project_id(name)
|
|||
|
|
+ if pid is None:
|
|||
|
|
+ raise LookupError(f"project '{name}' not found in DB")
|
|||
|
|
pending_steps = list_next_steps(pid, status="pending")
|
|||
|
|
if not pending_steps:
|
|||
|
|
return {
|
|||
|
|
@@ -182,7 +184,7 @@
|
|||
|
|
"reason": "orch_next_steps has no pending entries – next block not defined",
|
|||
|
|
"config": config,
|
|||
|
|
}
|
|||
|
|
- except ValueError:
|
|||
|
|
+ except (ValueError, LookupError):
|
|||
|
|
return {
|
|||
|
|
"allowed": False, "forced": False,
|
|||
|
|
"reason": f"project {name!r} not found in DB",
|
|||
|
|
@@ -215,8 +217,10 @@
|
|||
|
|
dict artifact_key -> exists (bool)
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
- pid = resolve_project(project_name)
|
|||
|
|
- except ValueError:
|
|||
|
|
+ pid = get_project_id(project_name)
|
|||
|
|
+ if pid is None:
|
|||
|
|
+ raise LookupError(f"project {project_name!r} not in DB")
|
|||
|
|
+ except (ValueError, LookupError):
|
|||
|
|
# Projekt nicht in DB → alle Artefakte fehlen
|
|||
|
|
return {k: False for k in MANDATORY_ARTIFACTS}
|
|||
|
|
|
|||
|
|
@@ -240,8 +244,8 @@
|
|||
|
|
def get_resume_payload(project_name: str) -> Dict[str, Any]:
|
|||
|
|
"""Liest alle Resume-relevanten Daten aus der DB für `block_resume.py`."""
|
|||
|
|
try:
|
|||
|
|
- pid = resolve_project(project_name)
|
|||
|
|
- except ValueError:
|
|||
|
|
+ pid = get_project_id(project_name)
|
|||
|
|
+ except (ValueError, LookupError):
|
|||
|
|
pid = None
|
|||
|
|
|
|||
|
|
if pid is None:
|
|||
|
|
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:
|
|||
|
|
+ """Strippt, prüft Pattern + Blacklist. Wirft ValueError bei Verletzung."""
|
|||
|
|
+ if not name or not name.strip():
|
|||
|
|
+ raise ValueError("project name must be non-empty")
|
|||
|
|
+ name = name.strip()
|
|||
|
|
+ if name in FORBIDDEN_PROJECT_NAMES:
|
|||
|
|
+ raise ValueError(
|
|||
|
|
+ f"project name '{name}' is reserved/forbidden "
|
|||
|
|
+ f"(likely a system directory, not a software project)"
|
|||
|
|
+ )
|
|||
|
|
+ if not PROJECT_NAME_PATTERN.match(name):
|
|||
|
|
+ raise ValueError(
|
|||
|
|
+ f"project name '{name}' does not match pattern "
|
|||
|
|
+ f"{PROJECT_NAME_PATTERN.pattern} (lowercase letters, digits, hyphens)"
|
|||
|
|
+ )
|
|||
|
|
+ return name
|
|||
|
|
+
|
|||
|
|
+
|
|||
|
|
+# ---------------------------------------------------------------------------
|
|||
|
|
# Singleton-DB (selbe Instanz wie in library/db.py)
|
|||
|
|
# ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
@@ -58,45 +97,33 @@
|
|||
|
|
|
|||
|
|
@_db_error_handler
|
|||
|
|
def resolve_project(name: str) -> int:
|
|||
|
|
- """Löst einen Projektnamen zur project_id auf. Auto-register falls unbekannt.
|
|||
|
|
+ """Löst einen Projektnamen zur project_id auf.
|
|||
|
|
+
|
|||
|
|
+ WICHTIG: Legt KEIN neues Projekt mehr an. Bei unbekanntem Namen
|
|||
|
|
+ wird LookupError geworfen. Zum Anlegen explizit register_project()
|
|||
|
|
+ oder project_registry action=register verwenden.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
- name: Projektname (muss eindeutig sein, siehe projects.name UNIQUE)
|
|||
|
|
+ name: Projektname (muss Pattern + Blacklist matchen)
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
project_id (int)
|
|||
|
|
|
|||
|
|
Raises:
|
|||
|
|
- ValueError: wenn Name leer ist
|
|||
|
|
+ ValueError: wenn Name leer/ungültig (Pattern/Blacklist)
|
|||
|
|
+ LookupError: wenn Name nicht in DB registriert
|
|||
|
|
"""
|
|||
|
|
- if not name or not name.strip():
|
|||
|
|
- raise ValueError("project name must be non-empty")
|
|||
|
|
- name = name.strip()
|
|||
|
|
-
|
|||
|
|
+ name = _validate_project_name(name)
|
|||
|
|
db = _db()
|
|||
|
|
row = db.conn.execute(
|
|||
|
|
"SELECT id FROM projects WHERE name = ?", (name,)
|
|||
|
|
).fetchone()
|
|||
|
|
- if row:
|
|||
|
|
- project_id = int(row[0])
|
|||
|
|
- db.conn.execute(
|
|||
|
|
- """
|
|||
|
|
- INSERT INTO project_state (project_id, status, phase, last_active_at)
|
|||
|
|
- VALUES (?, 'active', 'intake', datetime('now'))
|
|||
|
|
- ON CONFLICT(project_id) DO UPDATE SET last_active_at = datetime('now')
|
|||
|
|
- """,
|
|||
|
|
- (project_id,),
|
|||
|
|
+ if not row:
|
|||
|
|
+ raise LookupError(
|
|||
|
|
+ f"project '{name}' is not registered. "
|
|||
|
|
+ f"Call register_project() or project_registry action=register first."
|
|||
|
|
)
|
|||
|
|
- db.conn.commit()
|
|||
|
|
- return project_id
|
|||
|
|
-
|
|||
|
|
- # Auto-register: legt einen Eintrag ohne git_url an.
|
|||
|
|
- # User kann später git_url via project_registry.update ergänzen.
|
|||
|
|
- cur = db.conn.execute(
|
|||
|
|
- "INSERT INTO projects (name, description) VALUES (?, ?)",
|
|||
|
|
- (name, f"Auto-registered by db_state_store at {now_iso()}")
|
|||
|
|
- )
|
|||
|
|
- project_id = int(cur.lastrowid)
|
|||
|
|
+ project_id = int(row[0])
|
|||
|
|
db.conn.execute(
|
|||
|
|
"""
|
|||
|
|
INSERT INTO project_state (project_id, status, phase, last_active_at)
|
|||
|
|
@@ -123,9 +150,12 @@
|
|||
|
|
def get_project_id(name: str) -> Optional[int]:
|
|||
|
|
"""Return existing project_id without creating or mutating project state.
|
|||
|
|
|
|||
|
|
- This is intended for read-only guards, startup summaries, and GET-style API
|
|||
|
|
- handlers. Use resolve_project() only in tools/actions that intentionally
|
|||
|
|
- create or update project state.
|
|||
|
|
+ This is the preferred read-only lookup. For create-or-lookup semantics
|
|||
|
|
+ use resolve_project(); to create explicitly use register_project().
|
|||
|
|
+
|
|||
|
|
+ Returns None when the project name is not registered. Note: This function
|
|||
|
|
+ does NOT validate the name against pattern/blacklist; it returns None for
|
|||
|
|
+ any unknown or invalid name (read-only, no side effects).
|
|||
|
|
"""
|
|||
|
|
if not name or not name.strip():
|
|||
|
|
return None
|
|||
|
|
@@ -644,10 +674,12 @@
|
|||
|
|
def register_project(name: str, git_url: str = "", description: str = "",
|
|||
|
|
tech_stack: str = "{}", status: str = "active",
|
|||
|
|
phase: str = "intake") -> int:
|
|||
|
|
- """Register or update a project with initial/current state. Returns project_id."""
|
|||
|
|
- if not name or not name.strip():
|
|||
|
|
- raise ValueError("project name must be non-empty")
|
|||
|
|
- name = name.strip()
|
|||
|
|
+ """Register or update a project with initial/current state. Returns project_id.
|
|||
|
|
+
|
|||
|
|
+ Validiert den Namen gegen Pattern + Blacklist (siehe _validate_project_name).
|
|||
|
|
+ Wirft ValueError bei ungültigem Namen, ohne INSERT durchzuführen.
|
|||
|
|
+ """
|
|||
|
|
+ name = _validate_project_name(name)
|
|||
|
|
db = _db()
|
|||
|
|
db.conn.execute(
|
|||
|
|
"""
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/library/patterns.db-shm and /a0/usr/plugins/a0_software_orchestrator/helpers/library/patterns.db-shm differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/library/patterns.db-wal and /a0/usr/plugins/a0_software_orchestrator/helpers/library/patterns.db-wal differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/library/__pycache__/db.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/library/__pycache__/db.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/library/__pycache__/extractor.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/library/__pycache__/extractor.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/library/__pycache__/__init__.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/library/__pycache__/__init__.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/library/__pycache__/migrations.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/library/__pycache__/migrations.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/artifact_rules.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/artifact_rules.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/artifact_rules.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/artifact_rules.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/briefing_file.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/briefing_file.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/briefing_file.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/briefing_file.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/compact_protocol.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/compact_protocol.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/compact_protocol.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/compact_protocol.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/context_budget.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/context_budget.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/db_state_store.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/db_state_store.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/db_state_store.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/db_state_store.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/exceptions.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/exceptions.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/handover_protocol.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/handover_protocol.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/__init__.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/__init__.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/manifest_schema.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/manifest_schema.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/resume_state.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/resume_state.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/safety_rules.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/safety_rules.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/state_store.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/state_store.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/__pycache__/tool_capabilities.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/helpers/__pycache__/tool_capabilities.cpython-312.pyc differ
|
|||
|
|
diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/resume_state.py /a0/usr/plugins/a0_software_orchestrator/helpers/resume_state.py
|
|||
|
|
--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/helpers/resume_state.py 2026-06-15 14:24:02.000000000 +0000
|
|||
|
|
+++ /a0/usr/plugins/a0_software_orchestrator/helpers/resume_state.py 2026-06-16 10:12:40.538021624 +0000
|
|||
|
|
@@ -9,7 +9,7 @@
|
|||
|
|
from typing import Any, Dict, Optional
|
|||
|
|
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.compact_protocol import get_resume_payload
|
|||
|
|
-from usr.plugins.a0_software_orchestrator.helpers.db_state_store import resolve_project, create_snapshot as db_create_snapshot
|
|||
|
|
+from usr.plugins.a0_software_orchestrator.helpers.db_state_store import get_project_id, create_snapshot as db_create_snapshot
|
|||
|
|
|
|||
|
|
|
|||
|
|
def read_resume_info(project_name: str = "", project_root: str = "") -> Dict[str, Any]:
|
|||
|
|
@@ -58,8 +58,10 @@
|
|||
|
|
"""
|
|||
|
|
name = project_name or os.path.basename(project_root or os.getcwd())
|
|||
|
|
try:
|
|||
|
|
- pid = resolve_project(name)
|
|||
|
|
- except ValueError:
|
|||
|
|
+ pid = get_project_id(name)
|
|||
|
|
+ if pid is None:
|
|||
|
|
+ return None
|
|||
|
|
+ except (ValueError, LookupError):
|
|||
|
|
return None
|
|||
|
|
snap_id = db_create_snapshot(pid, trigger="resume_state.create_snapshot")
|
|||
|
|
return f"db://snapshots/{snap_id}"
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/__pycache__/hooks.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/__pycache__/hooks.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/__pycache__/__init__.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/__pycache__/__init__.cpython-312.pyc differ
|
|||
|
|
diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/block_compactor.py /a0/usr/plugins/a0_software_orchestrator/tools/block_compactor.py
|
|||
|
|
--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/block_compactor.py 2026-06-15 14:24:02.000000000 +0000
|
|||
|
|
+++ /a0/usr/plugins/a0_software_orchestrator/tools/block_compactor.py 2026-06-16 10:09:09.555054355 +0000
|
|||
|
|
@@ -17,7 +17,7 @@
|
|||
|
|
|
|||
|
|
from helpers.tool import Tool, Response
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.db_state_store import (
|
|||
|
|
- resolve_project, now_iso, get_kv, set_kv,
|
|||
|
|
+ get_project_id, now_iso, get_kv, set_kv,
|
|||
|
|
append_worklog, set_status, add_next_step,
|
|||
|
|
set_block_compact, get_block_compact,
|
|||
|
|
create_snapshot, collect_full_state,
|
|||
|
|
@@ -294,8 +294,10 @@
|
|||
|
|
):
|
|||
|
|
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)
|
|||
|
|
diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/block_resume.py /a0/usr/plugins/a0_software_orchestrator/tools/block_resume.py
|
|||
|
|
--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/block_resume.py 2026-06-15 14:24:02.000000000 +0000
|
|||
|
|
+++ /a0/usr/plugins/a0_software_orchestrator/tools/block_resume.py 2026-06-16 10:09:20.287815744 +0000
|
|||
|
|
@@ -8,7 +8,7 @@
|
|||
|
|
from helpers.tool import Tool, Response
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.compact_protocol import get_resume_payload
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.db_state_store import (
|
|||
|
|
- resolve_project,
|
|||
|
|
+ get_project_id,
|
|||
|
|
get_status, list_next_steps, list_todos, list_errors, list_worklog,
|
|||
|
|
get_kv, get_block_compact,
|
|||
|
|
)
|
|||
|
|
@@ -51,8 +51,10 @@
|
|||
|
|
):
|
|||
|
|
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:
|
|||
|
|
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)
|
|||
|
|
diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/orchestrator_state.py /a0/usr/plugins/a0_software_orchestrator/tools/orchestrator_state.py
|
|||
|
|
--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/orchestrator_state.py 2026-06-15 14:24:02.000000000 +0000
|
|||
|
|
+++ /a0/usr/plugins/a0_software_orchestrator/tools/orchestrator_state.py 2026-06-16 10:10:36.398215066 +0000
|
|||
|
|
@@ -6,7 +6,7 @@
|
|||
|
|
|
|||
|
|
from helpers.tool import Tool, Response
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.db_state_store import (
|
|||
|
|
- resolve_project,
|
|||
|
|
+ get_project_id,
|
|||
|
|
get_kv, set_kv, delete_kv, list_kv_keys,
|
|||
|
|
set_status as db_set_status, get_status as db_get_status,
|
|||
|
|
)
|
|||
|
|
@@ -45,8 +45,10 @@
|
|||
|
|
):
|
|||
|
|
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:
|
|||
|
|
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.
|
|||
|
|
+ """
|
|||
|
|
+ db = _db()
|
|||
|
|
+ row = db.conn.execute(
|
|||
|
|
+ "SELECT value_json FROM orch_kv "
|
|||
|
|
+ "WHERE project_id = ? AND key = ?",
|
|||
|
|
+ (_PLUGIN_MODE_PROJECT_ID, _PLUGIN_MODE_KEY),
|
|||
|
|
+ ).fetchone()
|
|||
|
|
+ if not row:
|
|||
|
|
+ return dict(_DEFAULT_MODE)
|
|||
|
|
+ try:
|
|||
|
|
+ return _normalize_mode(json.loads(row[0]))
|
|||
|
|
+ except (json.JSONDecodeError, TypeError):
|
|||
|
|
+ return dict(_DEFAULT_MODE)
|
|||
|
|
+
|
|||
|
|
+
|
|||
|
|
+@_db_error_handler
|
|||
|
|
+def _set_plugin_mode(mode_data: Dict[str, Any]) -> None:
|
|||
|
|
+ """Schreibt den globalen Plan-Mode in orch_kv (project_id=0)."""
|
|||
|
|
+ db = _db()
|
|||
|
|
+ db.conn.execute(
|
|||
|
|
+ "INSERT INTO orch_kv (project_id, key, value_json, updated_at) "
|
|||
|
|
+ "VALUES (?, ?, ?, datetime('now')) "
|
|||
|
|
+ "ON CONFLICT(project_id, key) DO UPDATE SET "
|
|||
|
|
+ " value_json = excluded.value_json, updated_at = datetime('now')",
|
|||
|
|
+ (_PLUGIN_MODE_PROJECT_ID, _PLUGIN_MODE_KEY, json.dumps(mode_data)),
|
|||
|
|
+ )
|
|||
|
|
+ db.conn.commit()
|
|||
|
|
+
|
|||
|
|
+
|
|||
|
|
class PlanModeGuard(Tool):
|
|||
|
|
- """Prüft den aktuellen Plan-Mode aus orch_kv und entscheidet, ob eine
|
|||
|
|
+ """Prüft den aktuellen Plan-Mode (plugin-weit) und entscheidet, ob eine
|
|||
|
|
Aktion erlaubt ist.
|
|||
|
|
|
|||
|
|
+ KEIN resolve_project()-Aufruf mehr: Plan-Mode ist global, nicht pro Projekt.
|
|||
|
|
+
|
|||
|
|
Args (kwargs):
|
|||
|
|
- project_name (str): Projektname (bevorzugt).
|
|||
|
|
- project_root (str): Legacy – wird zu basename() aufgelöst.
|
|||
|
|
requested_action (str): Action-Name zum Testen (z.B. "code", "deploy").
|
|||
|
|
new_mode (str): Optional – neuen Mode setzen (für Mode-Transitions).
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
async def execute(
|
|||
|
|
self,
|
|||
|
|
- project_name: str = "",
|
|||
|
|
- project_root: str = "",
|
|||
|
|
requested_action: str = "",
|
|||
|
|
new_mode: str = "",
|
|||
|
|
**kwargs,
|
|||
|
|
):
|
|||
|
|
- name = _resolve_name(project_name, project_root)
|
|||
|
|
- try:
|
|||
|
|
- pid = resolve_project(name)
|
|||
|
|
- except ValueError 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)
|
|||
|
|
# Mode-Transition (z.B. planning_only → implementation_allowed)
|
|||
|
|
if new_mode:
|
|||
|
|
if new_mode not in _MODE_POLICIES:
|
|||
|
|
@@ -90,18 +111,16 @@
|
|||
|
|
}, indent=2),
|
|||
|
|
break_loop=False,
|
|||
|
|
)
|
|||
|
|
- mode_data = _normalize_mode(get_kv(pid, "orchestrator_mode", default={}))
|
|||
|
|
- mode_data["mode"] = new_mode
|
|||
|
|
- mode_data["allowed_actions"] = list(_MODE_POLICIES[new_mode]["allowed_actions"])
|
|||
|
|
- mode_data["blocked_actions"] = list(_MODE_POLICIES[new_mode]["blocked_actions"])
|
|||
|
|
- set_kv(pid, "orchestrator_mode", mode_data)
|
|||
|
|
- # Bug 5 fix: return a JSON verdict so callers can detect success/failure
|
|||
|
|
- # structurally instead of parsing a free-form prose message.
|
|||
|
|
+ mode_data = {
|
|||
|
|
+ "mode": new_mode,
|
|||
|
|
+ "allowed_actions": list(_MODE_POLICIES[new_mode]["allowed_actions"]),
|
|||
|
|
+ "blocked_actions": list(_MODE_POLICIES[new_mode]["blocked_actions"]),
|
|||
|
|
+ }
|
|||
|
|
+ _set_plugin_mode(mode_data)
|
|||
|
|
return Response(
|
|||
|
|
message=json.dumps({
|
|||
|
|
"verdict": "ALLOWED",
|
|||
|
|
"mode": new_mode,
|
|||
|
|
- "project": name,
|
|||
|
|
"allowed_actions": mode_data["allowed_actions"],
|
|||
|
|
"blocked_actions": mode_data["blocked_actions"],
|
|||
|
|
"transitioned": True,
|
|||
|
|
@@ -110,19 +129,19 @@
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Ansonsten: aktuellen Mode lesen und Aktion prüfen
|
|||
|
|
- mode_data = _normalize_mode(get_kv(pid, "orchestrator_mode", default={}))
|
|||
|
|
+ mode_data = _get_plugin_mode()
|
|||
|
|
current_mode = mode_data["mode"]
|
|||
|
|
allowed = mode_data["allowed_actions"]
|
|||
|
|
blocked = mode_data["blocked_actions"]
|
|||
|
|
|
|||
|
|
- # Optional: Wenn ein default-mode gewünscht und nichts gesetzt → zurückschreiben
|
|||
|
|
- if not get_kv(pid, "orchestrator_mode"):
|
|||
|
|
- set_kv(pid, "orchestrator_mode", _DEFAULT_MODE)
|
|||
|
|
-
|
|||
|
|
if requested_action and requested_action in blocked:
|
|||
|
|
return Response(
|
|||
|
|
- message=f"BLOCKED: action '{requested_action}' is not allowed "
|
|||
|
|
- f"in mode '{current_mode}' for project {name!r}.",
|
|||
|
|
+ message=json.dumps({
|
|||
|
|
+ "verdict": "BLOCKED",
|
|||
|
|
+ "mode": current_mode,
|
|||
|
|
+ "action": requested_action,
|
|||
|
|
+ "reason": f"action '{requested_action}' is not allowed in mode '{current_mode}'",
|
|||
|
|
+ }, indent=2),
|
|||
|
|
break_loop=False,
|
|||
|
|
)
|
|||
|
|
if not requested_action or requested_action in allowed:
|
|||
|
|
@@ -131,7 +150,6 @@
|
|||
|
|
"verdict": "ALLOWED",
|
|||
|
|
"mode": current_mode,
|
|||
|
|
"action": requested_action,
|
|||
|
|
- "project": name,
|
|||
|
|
}, indent=2),
|
|||
|
|
break_loop=False,
|
|||
|
|
)
|
|||
|
|
@@ -140,7 +158,6 @@
|
|||
|
|
"verdict": "UNKNOWN",
|
|||
|
|
"mode": current_mode,
|
|||
|
|
"action": requested_action,
|
|||
|
|
- "project": name,
|
|||
|
|
"hint": f"action not in allowed={allowed} or blocked={blocked}",
|
|||
|
|
}, indent=2),
|
|||
|
|
break_loop=False,
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/artifact_guard.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/artifact_guard.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/block_compactor.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/block_compactor.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/block_compactor.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/block_compactor.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/block_resume.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/block_resume.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/block_resume.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/block_resume.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/capability_check.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/capability_check.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/context_compactor.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/context_compactor.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/handover_to_specialist.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/handover_to_specialist.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/next_step.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/next_step.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/next_step.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/next_step.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/orchestrator_self_check.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/orchestrator_self_check.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/orchestrator_state.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/orchestrator_state.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/orchestrator_state.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/orchestrator_state.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/plan_mode_guard.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/plan_mode_guard.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/plan_mode_guard.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/plan_mode_guard.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/project_registry.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/project_registry.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/quality_gate.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/quality_gate.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/quality_gate.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/quality_gate.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/read_briefing.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/read_briefing.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/repo_manifest.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/repo_manifest.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/repo_manifest.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/repo_manifest.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/resume_checker.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/resume_checker.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/resume_checker.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/resume_checker.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/scorecard_update.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/scorecard_update.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/scorecard_update.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/scorecard_update.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/tool_registry.cpython-312.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/tool_registry.cpython-312.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/__pycache__/tool_registry.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/tools/__pycache__/tool_registry.cpython-313.pyc differ
|
|||
|
|
diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/quality_gate.py /a0/usr/plugins/a0_software_orchestrator/tools/quality_gate.py
|
|||
|
|
--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/quality_gate.py 2026-06-15 14:24:02.000000000 +0000
|
|||
|
|
+++ /a0/usr/plugins/a0_software_orchestrator/tools/quality_gate.py 2026-06-16 10:09:58.271510332 +0000
|
|||
|
|
@@ -33,7 +33,7 @@
|
|||
|
|
from helpers.tool import Tool, Response
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.db_state_store import (
|
|||
|
|
DB_PATH,
|
|||
|
|
- resolve_project,
|
|||
|
|
+ get_project_id,
|
|||
|
|
get_kv,
|
|||
|
|
set_kv,
|
|||
|
|
)
|
|||
|
|
@@ -383,10 +383,12 @@
|
|||
|
|
if not name:
|
|||
|
|
return Response(message=json.dumps({"error": "no project_name or project_root given"}), break_loop=False)
|
|||
|
|
|
|||
|
|
- # resolve project_id (auto-create if not in registry)
|
|||
|
|
+ # resolve project_id (no auto-create; LookupError on unknown name)
|
|||
|
|
try:
|
|||
|
|
- project_id = resolve_project(name)
|
|||
|
|
- except ValueError as e:
|
|||
|
|
+ project_id = get_project_id(name)
|
|||
|
|
+ if project_id is None:
|
|||
|
|
+ raise LookupError(f"project '{name}' is not registered")
|
|||
|
|
+ except (ValueError, LookupError) as e:
|
|||
|
|
return Response(message=json.dumps({"error": str(e)}), break_loop=False)
|
|||
|
|
except PluginDBError as e:
|
|||
|
|
return Response(message=json.dumps({"error": f"Database error: {e}"}), break_loop=False)
|
|||
|
|
diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/repo_manifest.py /a0/usr/plugins/a0_software_orchestrator/tools/repo_manifest.py
|
|||
|
|
--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/repo_manifest.py 2026-06-15 14:24:02.000000000 +0000
|
|||
|
|
+++ /a0/usr/plugins/a0_software_orchestrator/tools/repo_manifest.py 2026-06-16 10:10:13.349579981 +0000
|
|||
|
|
@@ -6,7 +6,7 @@
|
|||
|
|
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, list_kv_keys
|
|||
|
|
+from usr.plugins.a0_software_orchestrator.helpers.db_state_store import get_project_id, get_kv, set_kv, list_kv_keys
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.manifest_schema import PROJECT_MANIFEST_SCHEMA, DEPLOYMENT_MANIFEST_SCHEMA
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.exceptions import PluginDBError
|
|||
|
|
|
|||
|
|
@@ -61,8 +61,10 @@
|
|||
|
|
):
|
|||
|
|
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:
|
|||
|
|
diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/resume_checker.py /a0/usr/plugins/a0_software_orchestrator/tools/resume_checker.py
|
|||
|
|
--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/resume_checker.py 2026-06-15 14:24:02.000000000 +0000
|
|||
|
|
+++ /a0/usr/plugins/a0_software_orchestrator/tools/resume_checker.py 2026-06-16 10:10:57.286696908 +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,
|
|||
|
|
get_kv, get_status, list_next_steps, list_todos, list_errors,
|
|||
|
|
get_block_compact,
|
|||
|
|
)
|
|||
|
|
@@ -37,8 +37,10 @@
|
|||
|
|
):
|
|||
|
|
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:
|
|||
|
|
diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/scorecard_update.py /a0/usr/plugins/a0_software_orchestrator/tools/scorecard_update.py
|
|||
|
|
--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/scorecard_update.py 2026-06-15 14:24:02.000000000 +0000
|
|||
|
|
+++ /a0/usr/plugins/a0_software_orchestrator/tools/scorecard_update.py 2026-06-16 10:11:13.415841120 +0000
|
|||
|
|
@@ -4,7 +4,7 @@
|
|||
|
|
import os
|
|||
|
|
|
|||
|
|
from helpers.tool import Tool, Response
|
|||
|
|
-from usr.plugins.a0_software_orchestrator.helpers.db_state_store import resolve_project, add_score, list_scores
|
|||
|
|
+from usr.plugins.a0_software_orchestrator.helpers.db_state_store import get_project_id, add_score, list_scores
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.exceptions import PluginDBError
|
|||
|
|
|
|||
|
|
|
|||
|
|
@@ -40,8 +40,10 @@
|
|||
|
|
):
|
|||
|
|
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:
|
|||
|
|
diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/tool_registry.py /a0/usr/plugins/a0_software_orchestrator/tools/tool_registry.py
|
|||
|
|
--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/tools/tool_registry.py 2026-06-15 14:24:02.000000000 +0000
|
|||
|
|
+++ /a0/usr/plugins/a0_software_orchestrator/tools/tool_registry.py 2026-06-16 10:11:27.807862100 +0000
|
|||
|
|
@@ -7,7 +7,7 @@
|
|||
|
|
from typing import Any, Dict
|
|||
|
|
|
|||
|
|
from helpers.tool import Tool, Response
|
|||
|
|
-from usr.plugins.a0_software_orchestrator.helpers.db_state_store import resolve_project, set_kv, get_kv
|
|||
|
|
+from usr.plugins.a0_software_orchestrator.helpers.db_state_store import get_project_id, set_kv, get_kv
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.exceptions import PluginDBError
|
|||
|
|
|
|||
|
|
|
|||
|
|
@@ -47,8 +47,10 @@
|
|||
|
|
):
|
|||
|
|
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:
|
|||
|
|
diff -ruN /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/utils/migrate_a0_to_db.py /a0/usr/plugins/a0_software_orchestrator/utils/migrate_a0_to_db.py
|
|||
|
|
--- /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/utils/migrate_a0_to_db.py 2026-06-15 14:24:02.000000000 +0000
|
|||
|
|
+++ /a0/usr/plugins/a0_software_orchestrator/utils/migrate_a0_to_db.py 2026-06-16 10:13:50.779004564 +0000
|
|||
|
|
@@ -34,7 +34,7 @@
|
|||
|
|
# Example: cd /a0 && /opt/venv-a0/bin/python /a0/usr/plugins/a0_software_orchestrator/utils/migrate_a0_to_db.py <project_root>
|
|||
|
|
|
|||
|
|
from usr.plugins.a0_software_orchestrator.helpers.db_state_store import (
|
|||
|
|
- resolve_project, set_kv, append_worklog, set_status,
|
|||
|
|
+ register_project, set_kv, append_worklog, set_status,
|
|||
|
|
add_todo, add_next_step, set_block_compact,
|
|||
|
|
create_snapshot, add_score, append_briefing,
|
|||
|
|
now_iso,
|
|||
|
|
@@ -209,8 +209,14 @@
|
|||
|
|
|
|||
|
|
print(f" Migrating project {project_name!r} from {project_root}")
|
|||
|
|
|
|||
|
|
- # Projekt registrieren (oder existierendes behalten)
|
|||
|
|
- pid = resolve_project(project_name)
|
|||
|
|
+ # Projekt registrieren (oder existierendes behalten) – mit Pattern+Blacklist-Plausi.
|
|||
|
|
+ # Bug 1/2 Fix: explizit register_project() statt auto-registrierendem resolve_project().
|
|||
|
|
+ try:
|
|||
|
|
+ pid = register_project(project_name, git_url="")
|
|||
|
|
+ except ValueError as e:
|
|||
|
|
+ print(f"ERROR: invalid project name {project_name!r}: {e}")
|
|||
|
|
+ print(" Bitte --project=<gültiger Name> verwenden (Pattern: ^[a-z][a-z0-9-]{1,40}$)")
|
|||
|
|
+ sys.exit(1)
|
|||
|
|
print(f" Project id: {pid}")
|
|||
|
|
|
|||
|
|
# 1. KV-Dateien: project_state.json, orchestrator_mode.json, tool_capabilities.json, …
|
|||
|
|
@@ -393,9 +399,17 @@
|
|||
|
|
args = ap.parse_args()
|
|||
|
|
|
|||
|
|
# Bestimme project_name + project_root
|
|||
|
|
+ # Bug 3 Fix: stiller basename-Fallback entfernt. Wenn --project=<abs path>
|
|||
|
|
+ # übergeben wird, wird basename genutzt MIT sichtbarem WARN-Hinweis,
|
|||
|
|
+ # damit der User den Projektnamen explizit setzen kann.
|
|||
|
|
+ if not args.project:
|
|||
|
|
+ print("ERROR: --project=<name> ist Pflicht (basename-Fallback entfernt)")
|
|||
|
|
+ sys.exit(1)
|
|||
|
|
if os.path.isabs(args.project) and os.path.isdir(args.project):
|
|||
|
|
project_root = args.project
|
|||
|
|
project_name = os.path.basename(project_root.rstrip("/"))
|
|||
|
|
+ print(f"WARN: --project ist absoluter Pfad; leite project_name={project_name!r} aus basename ab.")
|
|||
|
|
+ print(f" Besser: explizit --project={project_name} --project-root={project_root} setzen.")
|
|||
|
|
elif args.project_root:
|
|||
|
|
project_root = args.project_root
|
|||
|
|
project_name = args.project
|
|||
|
|
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 '<none>'.
|
|||
|
|
+ """
|
|||
|
|
+ db = _db()
|
|||
|
|
+ rows = db.conn.execute("""
|
|||
|
|
+ SELECT p.id, p.name, COALESCE(ps.status, '<none>') as status
|
|||
|
|
+ FROM projects p
|
|||
|
|
+ LEFT JOIN project_state ps ON ps.project_id = p.id
|
|||
|
|
+ ORDER BY p.id
|
|||
|
|
+ """).fetchall()
|
|||
|
|
+ return [(int(r[0]), str(r[1]), str(r[2])) for r in rows]
|
|||
|
|
+
|
|||
|
|
+
|
|||
|
|
+@_db_error_handler
|
|||
|
|
+def archive_project(project_id: int, reason: str) -> None:
|
|||
|
|
+ """Markiert ein Projekt als 'archived' in project_state. Idempotent."""
|
|||
|
|
+ db = _db()
|
|||
|
|
+ # Stelle sicher, dass ein project_state-Eintrag existiert.
|
|||
|
|
+ db.conn.execute("""
|
|||
|
|
+ INSERT INTO project_state (project_id, status, phase, last_active_at)
|
|||
|
|
+ VALUES (?, 'archived', 'archived', datetime('now'))
|
|||
|
|
+ ON CONFLICT(project_id) DO UPDATE SET
|
|||
|
|
+ status = 'archived',
|
|||
|
|
+ phase = 'archived',
|
|||
|
|
+ last_active_at = datetime('now'),
|
|||
|
|
+ updated_at = datetime('now')
|
|||
|
|
+ """, (project_id,))
|
|||
|
|
+ db.conn.execute("""
|
|||
|
|
+ UPDATE projects SET updated_at = datetime('now') WHERE id = ?
|
|||
|
|
+ """, (project_id,))
|
|||
|
|
+ db.conn.commit()
|
|||
|
|
+ sys.stderr.write(f" [ARCHIVED] project_id={project_id} reason={reason}\n")
|
|||
|
|
+
|
|||
|
|
+
|
|||
|
|
+def main() -> int:
|
|||
|
|
+ ap = argparse.ArgumentParser(
|
|||
|
|
+ description="Archiviere Projekte mit ungültigen Namen (Pattern/Blacklist)."
|
|||
|
|
+ )
|
|||
|
|
+ ap.add_argument(
|
|||
|
|
+ "--dry-run", action="store_true",
|
|||
|
|
+ help="Nur anzeigen, welche Projekte archiviert würden; keine DB-Schreibzugriffe.",
|
|||
|
|
+ )
|
|||
|
|
+ ap.add_argument(
|
|||
|
|
+ "--verbose", "-v", action="store_true",
|
|||
|
|
+ help="Zusätzlich pro Projekt den Validierungs-Status ausgeben.",
|
|||
|
|
+ )
|
|||
|
|
+ args = ap.parse_args()
|
|||
|
|
+
|
|||
|
|
+ try:
|
|||
|
|
+ projects = list_projects_with_states()
|
|||
|
|
+ except PluginDBError as e:
|
|||
|
|
+ print(f"ERROR: DB-Fehler beim Lesen der Projekte: {e}", file=sys.stderr)
|
|||
|
|
+ return 1
|
|||
|
|
+
|
|||
|
|
+ if not projects:
|
|||
|
|
+ print("No projects found in DB.")
|
|||
|
|
+ return 0
|
|||
|
|
+
|
|||
|
|
+ print(f"=== Legacy-Name Migration (dry-run={args.dry_run}) ===")
|
|||
|
|
+ print(f"Pattern: {PROJECT_NAME_PATTERN.pattern}")
|
|||
|
|
+ print(f"Blacklist: {sorted(FORBIDDEN_PROJECT_NAMES)}")
|
|||
|
|
+ print(f"Projects in DB: {len(projects)}")
|
|||
|
|
+ print()
|
|||
|
|
+
|
|||
|
|
+ active_count = 0
|
|||
|
|
+ archived_count = 0
|
|||
|
|
+ already_archived_count = 0
|
|||
|
|
+ invalid_count = 0
|
|||
|
|
+
|
|||
|
|
+ for pid, name, current_status in projects:
|
|||
|
|
+ try:
|
|||
|
|
+ _validate_project_name(name)
|
|||
|
|
+ valid = True
|
|||
|
|
+ invalid_reason = None
|
|||
|
|
+ except ValueError as e:
|
|||
|
|
+ valid = False
|
|||
|
|
+ invalid_reason = str(e)
|
|||
|
|
+
|
|||
|
|
+ if valid:
|
|||
|
|
+ active_count += 1
|
|||
|
|
+ if args.verbose:
|
|||
|
|
+ print(f" [OK ] pid={pid:3d} name={name!r:40s} status={current_status}")
|
|||
|
|
+ continue
|
|||
|
|
+
|
|||
|
|
+ invalid_count += 1
|
|||
|
|
+ if current_status == "archived":
|
|||
|
|
+ already_archived_count += 1
|
|||
|
|
+ print(f" [SKIP] pid={pid:3d} name={name!r} already archived; invalid={invalid_reason}")
|
|||
|
|
+ continue
|
|||
|
|
+
|
|||
|
|
+ print(f" [BAD ] pid={pid:3d} name={name!r} status={current_status} reason={invalid_reason}")
|
|||
|
|
+ if not args.dry_run:
|
|||
|
|
+ try:
|
|||
|
|
+ archive_project(pid, reason=invalid_reason)
|
|||
|
|
+ archived_count += 1
|
|||
|
|
+ except PluginDBError as e:
|
|||
|
|
+ print(f"ERROR: konnte pid={pid} nicht archivieren: {e}", file=sys.stderr)
|
|||
|
|
+ return 1
|
|||
|
|
+
|
|||
|
|
+ print()
|
|||
|
|
+ print(f"Summary:")
|
|||
|
|
+ print(f" total projects: {len(projects)}")
|
|||
|
|
+ print(f" valid (active): {active_count}")
|
|||
|
|
+ print(f" invalid (total): {invalid_count}")
|
|||
|
|
+ print(f" newly archived: {archived_count}")
|
|||
|
|
+ print(f" already archived: {already_archived_count}")
|
|||
|
|
+ print(f" mode: {'DRY-RUN' if args.dry_run else 'APPLIED'}")
|
|||
|
|
+
|
|||
|
|
+ return 0
|
|||
|
|
+
|
|||
|
|
+
|
|||
|
|
+if __name__ == "__main__":
|
|||
|
|
+ sys.exit(main())
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/utils/__pycache__/migrate_a0_to_db.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/utils/__pycache__/migrate_a0_to_db.cpython-313.pyc differ
|
|||
|
|
Binary files /a0/usr/plugins/a0_software_orchestrator.backup-20260616T095813Z/orig/a0_software_orchestrator/utils/__pycache__/migrate_legacy_project_names.cpython-313.pyc and /a0/usr/plugins/a0_software_orchestrator/utils/__pycache__/migrate_legacy_project_names.cpython-313.pyc differ
|