236 lines
7.4 KiB
Python
236 lines
7.4 KiB
Python
"""
|
||
Kin — SQLite database schema and connection management.
|
||
All tables from DESIGN.md section 3.5 State Management.
|
||
"""
|
||
|
||
import sqlite3
|
||
from pathlib import Path
|
||
|
||
DB_PATH = Path(__file__).parent.parent / "kin.db"
|
||
|
||
SCHEMA = """
|
||
-- Проекты (центральный реестр)
|
||
CREATE TABLE IF NOT EXISTS projects (
|
||
id TEXT PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
path TEXT NOT NULL,
|
||
tech_stack JSON,
|
||
status TEXT DEFAULT 'active',
|
||
priority INTEGER DEFAULT 5,
|
||
pm_prompt TEXT,
|
||
claude_md_path TEXT,
|
||
forgejo_repo TEXT,
|
||
language TEXT DEFAULT 'ru',
|
||
execution_mode TEXT NOT NULL DEFAULT 'review',
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- Задачи (привязаны к проекту)
|
||
CREATE TABLE IF NOT EXISTS tasks (
|
||
id TEXT PRIMARY KEY,
|
||
project_id TEXT NOT NULL REFERENCES projects(id),
|
||
title TEXT NOT NULL,
|
||
status TEXT DEFAULT 'pending',
|
||
priority INTEGER DEFAULT 5,
|
||
assigned_role TEXT,
|
||
parent_task_id TEXT REFERENCES tasks(id),
|
||
brief JSON,
|
||
spec JSON,
|
||
review JSON,
|
||
test_result JSON,
|
||
security_result JSON,
|
||
forgejo_issue_id INTEGER,
|
||
execution_mode TEXT,
|
||
blocked_reason TEXT,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- Решения и грабли (внешняя память PM-агента)
|
||
CREATE TABLE IF NOT EXISTS decisions (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id TEXT NOT NULL REFERENCES projects(id),
|
||
task_id TEXT REFERENCES tasks(id),
|
||
type TEXT NOT NULL,
|
||
category TEXT,
|
||
title TEXT NOT NULL,
|
||
description TEXT NOT NULL,
|
||
tags JSON,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- Логи агентов (дебаг, обучение, cost tracking)
|
||
CREATE TABLE IF NOT EXISTS agent_logs (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id TEXT NOT NULL REFERENCES projects(id),
|
||
task_id TEXT REFERENCES tasks(id),
|
||
agent_role TEXT NOT NULL,
|
||
session_id TEXT,
|
||
action TEXT NOT NULL,
|
||
input_summary TEXT,
|
||
output_summary TEXT,
|
||
tokens_used INTEGER,
|
||
model TEXT,
|
||
cost_usd REAL,
|
||
success BOOLEAN,
|
||
error_message TEXT,
|
||
duration_seconds INTEGER,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- Модули проекта (карта для PM)
|
||
CREATE TABLE IF NOT EXISTS modules (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id TEXT NOT NULL REFERENCES projects(id),
|
||
name TEXT NOT NULL,
|
||
type TEXT NOT NULL,
|
||
path TEXT NOT NULL,
|
||
description TEXT,
|
||
owner_role TEXT,
|
||
dependencies JSON,
|
||
UNIQUE(project_id, name)
|
||
);
|
||
|
||
-- Pipelines (история запусков)
|
||
CREATE TABLE IF NOT EXISTS pipelines (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
task_id TEXT NOT NULL REFERENCES tasks(id),
|
||
project_id TEXT NOT NULL REFERENCES projects(id),
|
||
route_type TEXT NOT NULL,
|
||
steps JSON NOT NULL,
|
||
status TEXT DEFAULT 'running',
|
||
total_cost_usd REAL,
|
||
total_tokens INTEGER,
|
||
total_duration_seconds INTEGER,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
completed_at DATETIME
|
||
);
|
||
|
||
-- Post-pipeline хуки
|
||
CREATE TABLE IF NOT EXISTS hooks (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id TEXT NOT NULL REFERENCES projects(id),
|
||
name TEXT NOT NULL,
|
||
event TEXT NOT NULL,
|
||
trigger_module_path TEXT,
|
||
trigger_module_type TEXT,
|
||
command TEXT NOT NULL,
|
||
working_dir TEXT,
|
||
timeout_seconds INTEGER DEFAULT 120,
|
||
enabled INTEGER DEFAULT 1,
|
||
created_at TEXT DEFAULT (datetime('now'))
|
||
);
|
||
|
||
-- Лог выполнений хуков
|
||
CREATE TABLE IF NOT EXISTS hook_logs (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
hook_id INTEGER NOT NULL REFERENCES hooks(id),
|
||
project_id TEXT NOT NULL REFERENCES projects(id),
|
||
task_id TEXT,
|
||
success INTEGER NOT NULL,
|
||
exit_code INTEGER,
|
||
output TEXT,
|
||
error TEXT,
|
||
duration_seconds REAL,
|
||
created_at TEXT DEFAULT (datetime('now'))
|
||
);
|
||
|
||
-- Кросс-проектные зависимости
|
||
CREATE TABLE IF NOT EXISTS project_links (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
from_project TEXT NOT NULL REFERENCES projects(id),
|
||
to_project TEXT NOT NULL REFERENCES projects(id),
|
||
type TEXT NOT NULL,
|
||
description TEXT,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- Тикеты от пользователей
|
||
CREATE TABLE IF NOT EXISTS support_tickets (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
project_id TEXT NOT NULL REFERENCES projects(id),
|
||
source TEXT NOT NULL,
|
||
client_id TEXT,
|
||
client_message TEXT NOT NULL,
|
||
classification TEXT,
|
||
guard_result TEXT,
|
||
guard_reason TEXT,
|
||
anamnesis JSON,
|
||
task_id TEXT REFERENCES tasks(id),
|
||
response TEXT,
|
||
response_approved BOOLEAN DEFAULT FALSE,
|
||
status TEXT DEFAULT 'new',
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
resolved_at DATETIME
|
||
);
|
||
|
||
-- Настройки бота для каждого проекта
|
||
CREATE TABLE IF NOT EXISTS support_bot_config (
|
||
project_id TEXT PRIMARY KEY REFERENCES projects(id),
|
||
telegram_bot_token TEXT,
|
||
welcome_message TEXT,
|
||
faq JSON,
|
||
auto_reply BOOLEAN DEFAULT FALSE,
|
||
require_approval BOOLEAN DEFAULT TRUE,
|
||
brand_voice TEXT,
|
||
forbidden_topics JSON,
|
||
escalation_keywords JSON
|
||
);
|
||
|
||
-- Индексы
|
||
CREATE INDEX IF NOT EXISTS idx_tasks_project_status ON tasks(project_id, status);
|
||
CREATE INDEX IF NOT EXISTS idx_decisions_project ON decisions(project_id);
|
||
CREATE INDEX IF NOT EXISTS idx_decisions_tags ON decisions(tags);
|
||
CREATE INDEX IF NOT EXISTS idx_agent_logs_project ON agent_logs(project_id, created_at);
|
||
CREATE INDEX IF NOT EXISTS idx_agent_logs_cost ON agent_logs(project_id, cost_usd);
|
||
CREATE INDEX IF NOT EXISTS idx_tickets_project ON support_tickets(project_id, status);
|
||
CREATE INDEX IF NOT EXISTS idx_tickets_client ON support_tickets(client_id);
|
||
"""
|
||
|
||
|
||
def get_connection(db_path: Path = DB_PATH) -> sqlite3.Connection:
|
||
conn = sqlite3.connect(str(db_path))
|
||
conn.execute("PRAGMA journal_mode=WAL")
|
||
conn.execute("PRAGMA foreign_keys=ON")
|
||
conn.row_factory = sqlite3.Row
|
||
return conn
|
||
|
||
|
||
def _migrate(conn: sqlite3.Connection):
|
||
"""Run migrations for existing databases."""
|
||
# Check if language column exists on projects
|
||
proj_cols = {r[1] for r in conn.execute("PRAGMA table_info(projects)").fetchall()}
|
||
if "language" not in proj_cols:
|
||
conn.execute("ALTER TABLE projects ADD COLUMN language TEXT DEFAULT 'ru'")
|
||
conn.commit()
|
||
if "execution_mode" not in proj_cols:
|
||
conn.execute("ALTER TABLE projects ADD COLUMN execution_mode TEXT NOT NULL DEFAULT 'review'")
|
||
conn.commit()
|
||
|
||
# Check if execution_mode column exists on tasks
|
||
task_cols = {r[1] for r in conn.execute("PRAGMA table_info(tasks)").fetchall()}
|
||
if "execution_mode" not in task_cols:
|
||
conn.execute("ALTER TABLE tasks ADD COLUMN execution_mode TEXT")
|
||
conn.commit()
|
||
if "blocked_reason" not in task_cols:
|
||
conn.execute("ALTER TABLE tasks ADD COLUMN blocked_reason TEXT")
|
||
conn.commit()
|
||
|
||
|
||
def init_db(db_path: Path = DB_PATH) -> sqlite3.Connection:
|
||
conn = get_connection(db_path)
|
||
conn.executescript(SCHEMA)
|
||
conn.commit()
|
||
_migrate(conn)
|
||
return conn
|
||
|
||
|
||
if __name__ == "__main__":
|
||
conn = init_db()
|
||
tables = conn.execute(
|
||
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
|
||
).fetchall()
|
||
print(f"Initialized {len(tables)} tables:")
|
||
for t in tables:
|
||
print(f" - {t['name']}")
|
||
conn.close()
|