5769c1cd22
- Auto-Registration-Bug behoben (register_project/get_project_id/resolve_project Trennung) - 25 Tests gruen (Pytest) - block_compactor-Tool refactored (Option B: Soft-Check statt Hard-Block) - 4 Restbaustellen gefixt - DB-Schema: plugin_settings-Tabelle hinzugefuegt - 3 Schattenprojekte aus DB geloescht - Plan v3 + Refactor-Plan + Worklog dokumentiert
537 lines
22 KiB
Python
537 lines
22 KiB
Python
"""
|
||
A0 Software Orchestrator – Schema-Migrationen
|
||
Verwaltet inkrementelle Updates der patterns.db.
|
||
"""
|
||
|
||
from typing import TYPE_CHECKING
|
||
|
||
if TYPE_CHECKING:
|
||
from .db import PatternDB
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Migrations-Definitionen
|
||
# ---------------------------------------------------------------------------
|
||
# Key = Versionsnummer, Value = SQL-Statement
|
||
# Migrationen werden in aufsteigender Reihenfolge ausgeführt.
|
||
|
||
MIGRATIONS = {
|
||
1: """
|
||
-- Version 1: Initiales Schema (siehe schema.sql)
|
||
-- Diese Migration ist nur ein Marker – das vollständige Schema
|
||
-- wird in schema.sql verwaltet und bei neuen DBs ausgeführt.
|
||
SELECT 1; -- No-Op, Marker
|
||
""",
|
||
2: """
|
||
-- Version 2: Normalisiertes Projekt-Registry-Schema
|
||
-- Teilt projects_registry in drei Tabellen:
|
||
-- projects = stabile Identität (name, git_url, description, tech_stack)
|
||
-- project_state = laufender Zustand (phase, status, tasks, errors, …)
|
||
-- project_snapshots = Historie (state_json, trigger, commit_hash)
|
||
|
||
-- 2a. Neue Tabellen anlegen
|
||
CREATE TABLE IF NOT EXISTS projects (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
name TEXT UNIQUE NOT NULL,
|
||
git_url TEXT,
|
||
description TEXT DEFAULT '',
|
||
tech_stack TEXT DEFAULT '{}',
|
||
created_at TEXT DEFAULT (datetime('now'))
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS project_state (
|
||
project_id INTEGER PRIMARY KEY,
|
||
status TEXT DEFAULT 'active',
|
||
phase TEXT DEFAULT 'intake',
|
||
plan_mode TEXT,
|
||
total_tasks INTEGER DEFAULT 0,
|
||
completed_tasks INTEGER DEFAULT 0,
|
||
open_errors INTEGER DEFAULT 0,
|
||
last_active_at TEXT,
|
||
completed_at TEXT,
|
||
updated_at TEXT DEFAULT (datetime('now')),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id)
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS project_snapshots (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
snapshot_at TEXT DEFAULT (datetime('now')),
|
||
state_json TEXT NOT NULL,
|
||
trigger TEXT,
|
||
commit_hash TEXT,
|
||
FOREIGN KEY (project_id) REFERENCES projects(id)
|
||
);
|
||
|
||
-- 2b. Daten aus projects_registry migrieren
|
||
INSERT INTO projects (name, git_url, description, tech_stack, created_at)
|
||
SELECT project_name, git_url,
|
||
COALESCE(description, ''),
|
||
COALESCE(tech_stack, '{}'),
|
||
COALESCE(created_at, datetime('now'))
|
||
FROM projects_registry
|
||
WHERE project_name IS NOT NULL;
|
||
|
||
INSERT INTO project_state (project_id, status, phase, plan_mode, total_tasks,
|
||
completed_tasks, open_errors, last_active_at, completed_at)
|
||
SELECT p.id, r.status, r.phase, r.plan_mode,
|
||
COALESCE(r.total_tasks, 0), COALESCE(r.completed_tasks, 0),
|
||
COALESCE(r.open_errors, 0), r.last_active_at, r.completed_at
|
||
FROM projects_registry r
|
||
JOIN projects p ON r.project_name = p.name;
|
||
|
||
-- 2c. Alte Tabelle umbenennen (Sicherheit – kein DROP)
|
||
ALTER TABLE projects_registry RENAME TO projects_registry_legacy;
|
||
""",
|
||
3: """
|
||
-- Version 3: Orchestrator-State-Tabellen (orch_*)
|
||
-- Migration: .a0/*.json/.md und .a0proj/handover/*.md → patterns.db
|
||
-- Ersetzt file-basierten State durch DB-Tabellen, project-scoped.
|
||
|
||
-- 3a. Generischer Key/Value Store (JSON-Values)
|
||
-- ersetzt: project_state.json, orchestrator_mode.json,
|
||
-- task_graph.json, tool_capabilities.json,
|
||
-- tool_registry.json, manifests/*.json
|
||
CREATE TABLE IF NOT EXISTS orch_kv (
|
||
project_id INTEGER NOT NULL,
|
||
key TEXT NOT NULL,
|
||
value_json TEXT NOT NULL,
|
||
updated_at TEXT DEFAULT (datetime('now')),
|
||
PRIMARY KEY (project_id, key),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_kv_project ON orch_kv(project_id);
|
||
|
||
-- 3b. Append-only Worklog
|
||
-- ersetzt: .a0/worklog.md
|
||
CREATE TABLE IF NOT EXISTS orch_worklog (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
phase TEXT,
|
||
work_block TEXT,
|
||
agent TEXT,
|
||
summary TEXT,
|
||
details TEXT,
|
||
created_at TEXT DEFAULT (datetime('now')),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_worklog_project
|
||
ON orch_worklog(project_id, created_at DESC);
|
||
|
||
-- 3c. Todos (offene + abgeschlossene)
|
||
-- ersetzt: .a0/todo.md
|
||
CREATE TABLE IF NOT EXISTS orch_todos (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
content TEXT NOT NULL,
|
||
status TEXT DEFAULT 'open', -- open, in_progress, done, blocked, cancelled
|
||
priority INTEGER DEFAULT 0,
|
||
created_at TEXT DEFAULT (datetime('now')),
|
||
started_at TEXT,
|
||
completed_at TEXT,
|
||
notes TEXT,
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_todos_project
|
||
ON orch_todos(project_id, status);
|
||
|
||
-- 3d. Current Status (1-Zeilen-Projektstatus)
|
||
-- ersetzt: .a0/current_status.md
|
||
CREATE TABLE IF NOT EXISTS orch_status (
|
||
project_id INTEGER PRIMARY KEY,
|
||
content TEXT NOT NULL,
|
||
updated_at TEXT DEFAULT (datetime('now')),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
-- 3e. Next Steps (offene Schritte)
|
||
-- ersetzt: .a0/next_steps.md
|
||
CREATE TABLE IF NOT EXISTS orch_next_steps (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
content TEXT NOT NULL,
|
||
status TEXT DEFAULT 'pending', -- pending, in_progress, done, blocked
|
||
order_idx INTEGER DEFAULT 0,
|
||
created_at TEXT DEFAULT (datetime('now')),
|
||
completed_at TEXT,
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_next_steps_project
|
||
ON orch_next_steps(project_id, order_idx);
|
||
|
||
-- 3f. Known Errors (offene + gelöste)
|
||
-- ersetzt: .a0/known_errors.md
|
||
CREATE TABLE IF NOT EXISTS orch_errors (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
error_id TEXT,
|
||
title TEXT NOT NULL,
|
||
severity TEXT DEFAULT 'medium', -- low, medium, high, critical
|
||
status TEXT DEFAULT 'open', -- open, investigating, resolved, wontfix
|
||
context TEXT,
|
||
resolution TEXT,
|
||
discovered TEXT DEFAULT (datetime('now')),
|
||
resolved_at TEXT,
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_errors_project
|
||
ON orch_errors(project_id, status);
|
||
|
||
-- 3g. Block-Compact Marker (ersetzt .a0/resume.md + .a0/conversation_summary.md)
|
||
CREATE TABLE IF NOT EXISTS orch_block_compact (
|
||
project_id INTEGER PRIMARY KEY,
|
||
last_block_id TEXT,
|
||
last_compact_at TEXT,
|
||
next_block TEXT,
|
||
block_summary TEXT,
|
||
decisions_json TEXT, -- JSON array
|
||
key_findings_json TEXT, -- JSON array
|
||
open_questions_json TEXT, -- JSON array
|
||
resume_md TEXT,
|
||
conversation_summary_md TEXT,
|
||
context_ratio REAL,
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
-- 3h. Session Snapshots (ersetzt .a0/session_snapshots/*.json)
|
||
CREATE TABLE IF NOT EXISTS orch_snapshots (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
snapshot_at TEXT DEFAULT (datetime('now')),
|
||
trigger TEXT, -- e.g. 'block_compact', 'phase_transition', 'manual'
|
||
commit_hash TEXT,
|
||
state_json TEXT NOT NULL,
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_snapshots_project
|
||
ON orch_snapshots(project_id, snapshot_at DESC);
|
||
|
||
-- 3i. Scorecard Entries (ersetzt .a0/project_scorecard.md)
|
||
CREATE TABLE IF NOT EXISTS orch_scorecard (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
category TEXT NOT NULL,
|
||
score INTEGER NOT NULL,
|
||
max_score INTEGER DEFAULT 100,
|
||
created_at TEXT DEFAULT (datetime('now')),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_scorecard_project
|
||
ON orch_scorecard(project_id, created_at DESC);
|
||
|
||
-- 3j. Briefings (ersetzt .a0proj/handover/*.md)
|
||
-- Persistente Handover-Briefings für Persona-Modus
|
||
CREATE TABLE IF NOT EXISTS orch_briefings (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
specialist TEXT NOT NULL,
|
||
topic TEXT NOT NULL,
|
||
section_md TEXT NOT NULL,
|
||
summary TEXT,
|
||
decisions_json TEXT,
|
||
artifacts_json TEXT,
|
||
recent_turns_json TEXT,
|
||
return_condition TEXT,
|
||
handover_reason TEXT,
|
||
created_at TEXT DEFAULT (datetime('now')),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_briefings_project
|
||
ON orch_briefings(project_id, specialist, created_at DESC);
|
||
""",
|
||
4: """
|
||
-- Version 4: Quality gate runtime tables.
|
||
CREATE TABLE IF NOT EXISTS orch_quality_config (
|
||
project_id INTEGER PRIMARY KEY,
|
||
tech_stack TEXT NOT NULL DEFAULT 'unknown',
|
||
coverage_min_pct INTEGER DEFAULT 70,
|
||
strict_types INTEGER DEFAULT 1,
|
||
require_security_scan INTEGER DEFAULT 1,
|
||
require_openapi_check INTEGER DEFAULT 0,
|
||
custom_checks_json TEXT DEFAULT '[]',
|
||
updated_at TEXT DEFAULT (datetime('now')),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS orch_quality_runs (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
gate_level TEXT NOT NULL,
|
||
decision TEXT NOT NULL,
|
||
checks_json TEXT NOT NULL,
|
||
blockers_json TEXT NOT NULL,
|
||
next_action TEXT,
|
||
duration_ms INTEGER DEFAULT 0,
|
||
triggered_by TEXT DEFAULT 'manual',
|
||
created_at TEXT DEFAULT (datetime('now')),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_quality_runs_project
|
||
ON orch_quality_runs(project_id, created_at DESC);
|
||
""",
|
||
5: """
|
||
-- Version 5: Globale Plugin-Settings (kein Project-Scope)
|
||
-- Ersetzt den project_id=0-Workaround in orch_kv (plan_mode_guard,
|
||
-- siehe Bugfix-Auto-Registration §3.7). plugin_settings hat KEINE
|
||
-- FK auf projects, ist also für plugin-weite Konfiguration.
|
||
CREATE TABLE IF NOT EXISTS plugin_settings (
|
||
key TEXT PRIMARY KEY NOT NULL,
|
||
value_json TEXT NOT NULL,
|
||
updated_at TEXT DEFAULT (datetime('now'))
|
||
);
|
||
"""
|
||
}
|
||
|
||
|
||
def _table_exists(db: "PatternDB", name: str) -> bool:
|
||
row = db.conn.execute(
|
||
"SELECT 1 FROM sqlite_master WHERE type IN ('table','view') AND name = ?",
|
||
(name,),
|
||
).fetchone()
|
||
return row is not None
|
||
|
||
|
||
def _column_exists(db: "PatternDB", table: str, column: str) -> bool:
|
||
try:
|
||
return any(r[1] == column for r in db.conn.execute(f"PRAGMA table_info({table})"))
|
||
except Exception:
|
||
return False
|
||
|
||
|
||
def _add_column_if_missing(db: "PatternDB", table: str, column: str, ddl: str) -> None:
|
||
if _table_exists(db, table) and not _column_exists(db, table, column):
|
||
db.conn.execute(f"ALTER TABLE {table} ADD COLUMN {ddl}")
|
||
|
||
|
||
def ensure_runtime_schema(db: "PatternDB") -> None:
|
||
"""Idempotently enforce all runtime tables/columns regardless of migration history."""
|
||
db.conn.executescript("""
|
||
CREATE TABLE IF NOT EXISTS projects (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
name TEXT UNIQUE NOT NULL,
|
||
git_url TEXT,
|
||
description TEXT DEFAULT '',
|
||
tech_stack TEXT DEFAULT '{}',
|
||
created_at TEXT DEFAULT (datetime('now'))
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS project_state (
|
||
project_id INTEGER PRIMARY KEY,
|
||
status TEXT DEFAULT 'active',
|
||
phase TEXT DEFAULT 'intake',
|
||
plan_mode TEXT,
|
||
total_tasks INTEGER DEFAULT 0,
|
||
completed_tasks INTEGER DEFAULT 0,
|
||
open_errors INTEGER DEFAULT 0,
|
||
last_active_at TEXT,
|
||
completed_at TEXT,
|
||
updated_at TEXT DEFAULT (datetime('now')),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS project_snapshots (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
snapshot_at TEXT DEFAULT (datetime('now')),
|
||
state_json TEXT NOT NULL,
|
||
trigger TEXT,
|
||
commit_hash TEXT,
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS orch_kv (
|
||
project_id INTEGER NOT NULL,
|
||
key TEXT NOT NULL,
|
||
value_json TEXT NOT NULL,
|
||
updated_at TEXT DEFAULT (datetime('now')),
|
||
PRIMARY KEY (project_id, key),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS orch_worklog (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
phase TEXT,
|
||
work_block TEXT,
|
||
agent TEXT,
|
||
summary TEXT,
|
||
details TEXT,
|
||
created_at TEXT DEFAULT (datetime('now')),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS orch_todos (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
content TEXT NOT NULL,
|
||
status TEXT DEFAULT 'open',
|
||
priority INTEGER DEFAULT 0,
|
||
created_at TEXT DEFAULT (datetime('now')),
|
||
started_at TEXT,
|
||
completed_at TEXT,
|
||
notes TEXT,
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS orch_status (
|
||
project_id INTEGER PRIMARY KEY,
|
||
content TEXT NOT NULL,
|
||
updated_at TEXT DEFAULT (datetime('now')),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS orch_next_steps (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
content TEXT NOT NULL,
|
||
status TEXT DEFAULT 'pending',
|
||
order_idx INTEGER DEFAULT 0,
|
||
created_at TEXT DEFAULT (datetime('now')),
|
||
completed_at TEXT,
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS orch_errors (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
error_id TEXT,
|
||
title TEXT NOT NULL,
|
||
severity TEXT DEFAULT 'medium',
|
||
status TEXT DEFAULT 'open',
|
||
context TEXT,
|
||
resolution TEXT,
|
||
discovered TEXT DEFAULT (datetime('now')),
|
||
resolved_at TEXT,
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS orch_block_compact (
|
||
project_id INTEGER PRIMARY KEY,
|
||
last_block_id TEXT,
|
||
last_compact_at TEXT,
|
||
next_block TEXT,
|
||
block_summary TEXT,
|
||
decisions_json TEXT,
|
||
key_findings_json TEXT,
|
||
open_questions_json TEXT,
|
||
resume_md TEXT,
|
||
conversation_summary_md TEXT,
|
||
context_ratio REAL,
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS orch_snapshots (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
snapshot_at TEXT DEFAULT (datetime('now')),
|
||
trigger TEXT,
|
||
commit_hash TEXT,
|
||
state_json TEXT NOT NULL,
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS orch_scorecard (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
category TEXT NOT NULL,
|
||
score INTEGER NOT NULL,
|
||
max_score INTEGER DEFAULT 100,
|
||
created_at TEXT DEFAULT (datetime('now')),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS orch_briefings (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
specialist TEXT NOT NULL,
|
||
topic TEXT NOT NULL,
|
||
section_md TEXT NOT NULL,
|
||
summary TEXT,
|
||
decisions_json TEXT,
|
||
artifacts_json TEXT,
|
||
recent_turns_json TEXT,
|
||
return_condition TEXT,
|
||
handover_reason TEXT,
|
||
created_at TEXT DEFAULT (datetime('now')),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS orch_quality_config (
|
||
project_id INTEGER PRIMARY KEY,
|
||
tech_stack TEXT NOT NULL DEFAULT 'unknown',
|
||
coverage_min_pct INTEGER DEFAULT 70,
|
||
strict_types INTEGER DEFAULT 1,
|
||
require_security_scan INTEGER DEFAULT 1,
|
||
require_openapi_check INTEGER DEFAULT 0,
|
||
custom_checks_json TEXT DEFAULT '[]',
|
||
updated_at TEXT DEFAULT (datetime('now')),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS orch_quality_runs (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id INTEGER NOT NULL,
|
||
gate_level TEXT NOT NULL,
|
||
decision TEXT NOT NULL,
|
||
checks_json TEXT NOT NULL,
|
||
blockers_json TEXT NOT NULL,
|
||
next_action TEXT,
|
||
duration_ms INTEGER DEFAULT 0,
|
||
triggered_by TEXT DEFAULT 'manual',
|
||
created_at TEXT DEFAULT (datetime('now')),
|
||
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
||
);
|
||
""")
|
||
for ddl in [
|
||
"project_path TEXT",
|
||
"patterns_extracted INTEGER DEFAULT 0",
|
||
"last_extraction_at TEXT",
|
||
]:
|
||
col = ddl.split()[0]
|
||
_add_column_if_missing(db, "projects", col, ddl)
|
||
db.conn.executescript("""
|
||
CREATE INDEX IF NOT EXISTS idx_projects_name ON projects(name);
|
||
CREATE INDEX IF NOT EXISTS idx_project_state_status ON project_state(status, phase);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_worklog_project ON orch_worklog(project_id, created_at DESC);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_todos_project ON orch_todos(project_id, status);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_next_steps_project ON orch_next_steps(project_id, order_idx);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_errors_project ON orch_errors(project_id, status);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_snapshots_project ON orch_snapshots(project_id, snapshot_at DESC);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_scorecard_project ON orch_scorecard(project_id, created_at DESC);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_briefings_project ON orch_briefings(project_id, specialist, created_at DESC);
|
||
CREATE INDEX IF NOT EXISTS idx_orch_quality_runs_project ON orch_quality_runs(project_id, created_at DESC);
|
||
""")
|
||
db.conn.commit()
|
||
|
||
|
||
def run_migrations(db: "PatternDB"):
|
||
"""
|
||
Führt ausstehende Schema-Migrationen aus.
|
||
|
||
Args:
|
||
db: PatternDB-Instanz
|
||
"""
|
||
# Aktuelle Schema-Version ermitteln
|
||
cursor = db.conn.execute(
|
||
"SELECT MAX(version) FROM schema_version"
|
||
)
|
||
current = cursor.fetchone()[0] or 0
|
||
|
||
# Fehlende Migrationen in Reihenfolge ausführen
|
||
for version in sorted(MIGRATIONS.keys()):
|
||
if version <= current:
|
||
continue
|
||
|
||
try:
|
||
db.conn.executescript(MIGRATIONS[version])
|
||
db.conn.execute(
|
||
"INSERT INTO schema_version (version, description) VALUES (?, ?)",
|
||
(version, f"Migration {version}")
|
||
)
|
||
db.conn.commit()
|
||
print(f"[PatternDB] ✅ Migration {version} ausgeführt")
|
||
except Exception as e:
|
||
print(f"[PatternDB] ❌ Migration {version} fehlgeschlagen: {e}")
|
||
raise
|
||
|
||
ensure_runtime_schema(db)
|