Add core/db.py — SQLite schema with all 9 tables from DESIGN.md 3.5
Tables: projects, tasks, decisions, agent_logs, modules, pipelines,
project_links, support_tickets, support_bot_config.
WAL mode, foreign keys enabled, idempotent init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:12:54 +02:00
|
|
|
|
"""
|
|
|
|
|
|
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,
|
2026-03-16 09:44:31 +02:00
|
|
|
|
path TEXT CHECK (path IS NOT NULL OR project_type = 'operations'),
|
Add core/db.py — SQLite schema with all 9 tables from DESIGN.md 3.5
Tables: projects, tasks, decisions, agent_logs, modules, pipelines,
project_links, support_tickets, support_bot_config.
WAL mode, foreign keys enabled, idempotent init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:12:54 +02:00
|
|
|
|
tech_stack JSON,
|
|
|
|
|
|
status TEXT DEFAULT 'active',
|
|
|
|
|
|
priority INTEGER DEFAULT 5,
|
|
|
|
|
|
pm_prompt TEXT,
|
|
|
|
|
|
claude_md_path TEXT,
|
|
|
|
|
|
forgejo_repo TEXT,
|
2026-03-15 14:39:33 +02:00
|
|
|
|
language TEXT DEFAULT 'ru',
|
2026-03-15 20:02:01 +02:00
|
|
|
|
execution_mode TEXT NOT NULL DEFAULT 'review',
|
2026-03-16 08:21:13 +02:00
|
|
|
|
deploy_command TEXT,
|
2026-03-16 09:13:34 +02:00
|
|
|
|
project_type TEXT DEFAULT 'development',
|
|
|
|
|
|
ssh_host TEXT,
|
|
|
|
|
|
ssh_user TEXT,
|
|
|
|
|
|
ssh_key_path TEXT,
|
|
|
|
|
|
ssh_proxy_jump TEXT,
|
|
|
|
|
|
description TEXT,
|
2026-03-17 17:26:31 +02:00
|
|
|
|
deploy_host TEXT,
|
|
|
|
|
|
deploy_path TEXT,
|
|
|
|
|
|
deploy_runtime TEXT,
|
|
|
|
|
|
deploy_restart_cmd TEXT,
|
2026-03-16 09:57:14 +02:00
|
|
|
|
autocommit_enabled INTEGER DEFAULT 0,
|
|
|
|
|
|
obsidian_vault_path TEXT,
|
2026-03-16 22:35:31 +02:00
|
|
|
|
worktrees_enabled INTEGER DEFAULT 0,
|
|
|
|
|
|
auto_test_enabled INTEGER DEFAULT 0,
|
2026-03-17 15:29:19 +02:00
|
|
|
|
test_command TEXT DEFAULT 'make test',
|
Add core/db.py — SQLite schema with all 9 tables from DESIGN.md 3.5
Tables: projects, tasks, decisions, agent_logs, modules, pipelines,
project_links, support_tickets, support_bot_config.
WAL mode, foreign keys enabled, idempotent init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:12:54 +02:00
|
|
|
|
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,
|
2026-03-15 20:02:01 +02:00
|
|
|
|
execution_mode TEXT,
|
2026-03-15 23:22:49 +02:00
|
|
|
|
blocked_reason TEXT,
|
2026-03-16 09:13:34 +02:00
|
|
|
|
blocked_at DATETIME,
|
|
|
|
|
|
blocked_agent_role TEXT,
|
|
|
|
|
|
blocked_pipeline_step TEXT,
|
2026-03-16 07:13:32 +02:00
|
|
|
|
dangerously_skipped BOOLEAN DEFAULT 0,
|
|
|
|
|
|
revise_comment TEXT,
|
2026-03-16 22:35:31 +02:00
|
|
|
|
revise_count INTEGER DEFAULT 0,
|
|
|
|
|
|
revise_target_role TEXT DEFAULT NULL,
|
|
|
|
|
|
labels JSON,
|
2026-03-16 08:21:13 +02:00
|
|
|
|
category TEXT DEFAULT NULL,
|
2026-03-16 09:43:26 +02:00
|
|
|
|
telegram_sent BOOLEAN DEFAULT 0,
|
2026-03-16 09:57:14 +02:00
|
|
|
|
acceptance_criteria TEXT,
|
Add core/db.py — SQLite schema with all 9 tables from DESIGN.md 3.5
Tables: projects, tasks, decisions, agent_logs, modules, pipelines,
project_links, support_tickets, support_bot_config.
WAL mode, foreign keys enabled, idempotent init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:12:54 +02:00
|
|
|
|
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)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-03-16 09:13:34 +02:00
|
|
|
|
-- Фазы исследования нового проекта (research workflow KIN-059)
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS project_phases (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
|
|
|
|
role TEXT NOT NULL,
|
|
|
|
|
|
phase_order INTEGER NOT NULL,
|
|
|
|
|
|
status TEXT DEFAULT 'pending',
|
|
|
|
|
|
task_id TEXT REFERENCES tasks(id),
|
|
|
|
|
|
revise_count INTEGER DEFAULT 0,
|
kin: KIN-059 Workflow new_project с выбором команды. При создании нового проекта через GUI или CLI директор описывает проект свободным текстом и выбирает галочками какие этапы research нужны: ☐ Business analyst (бизнес-модель, аудитория, монетизация) ☐ Market researcher (конкуренты, ниша, отзывы, сильные/слабые стороны) ☐ Legal researcher (юрисдикция, лицензии, KYC/AML, GDPR) ☐ Tech researcher (API, ограничения, стоимость, альтернативы) ☐ UX designer (анализ UX конкурентов, user journey, wireframes) ☐ Marketer (стратегия продвижения, SEO, conversion-паттерны) ☐ Architect (blueprint на основе одобренных research'ей) — всегда последний Architect включается автоматически если выбран хотя бы один researcher. Каждый выбранный этап — отдельная задача на review. Директор одобряет, отклоняет, или просит доисследовать (Revise). Следующий этап только после approve предыдущего. GUI: форма 'New Project' с описанием + чекбоксы ролей + кнопка 'Start Research'. CLI: kin new-project 'описание' --roles 'business,market,tech,architect'
2026-03-16 09:30:00 +02:00
|
|
|
|
revise_comment TEXT,
|
2026-03-16 09:13:34 +02:00
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_phases_project ON project_phases(project_id, phase_order);
|
|
|
|
|
|
|
Add core/db.py — SQLite schema with all 9 tables from DESIGN.md 3.5
Tables: projects, tasks, decisions, agent_logs, modules, pipelines,
project_links, support_tickets, support_bot_config.
WAL mode, foreign keys enabled, idempotent init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:12:54 +02:00
|
|
|
|
-- 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,
|
2026-03-17 14:03:53 +02:00
|
|
|
|
parent_pipeline_id INTEGER REFERENCES pipelines(id),
|
|
|
|
|
|
department TEXT,
|
2026-03-17 16:50:44 +02:00
|
|
|
|
pid INTEGER,
|
Add core/db.py — SQLite schema with all 9 tables from DESIGN.md 3.5
Tables: projects, tasks, decisions, agent_logs, modules, pipelines,
project_links, support_tickets, support_bot_config.
WAL mode, foreign keys enabled, idempotent init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:12:54 +02:00
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
completed_at DATETIME
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-03-17 14:03:53 +02:00
|
|
|
|
-- Межотдельные handoff-ы (KIN-098)
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS department_handoffs (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
pipeline_id INTEGER NOT NULL REFERENCES pipelines(id),
|
|
|
|
|
|
task_id TEXT NOT NULL REFERENCES tasks(id),
|
|
|
|
|
|
from_department TEXT NOT NULL,
|
|
|
|
|
|
to_department TEXT,
|
|
|
|
|
|
artifacts JSON,
|
|
|
|
|
|
decisions_made JSON,
|
|
|
|
|
|
blockers JSON,
|
|
|
|
|
|
status TEXT DEFAULT 'pending',
|
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_handoffs_pipeline ON department_handoffs(pipeline_id);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_handoffs_task ON department_handoffs(task_id);
|
|
|
|
|
|
|
2026-03-15 18:31:00 +02:00
|
|
|
|
-- 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'))
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-03-16 07:13:32 +02:00
|
|
|
|
-- Аудит-лог опасных операций (dangerously-skip-permissions)
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS audit_log (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
task_id TEXT REFERENCES tasks(id),
|
|
|
|
|
|
step_id TEXT,
|
|
|
|
|
|
event_type TEXT NOT NULL DEFAULT 'dangerous_skip',
|
|
|
|
|
|
reason TEXT,
|
|
|
|
|
|
project_id TEXT REFERENCES projects(id)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_audit_log_task ON audit_log(task_id);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_audit_log_event ON audit_log(event_type, timestamp);
|
|
|
|
|
|
|
Add core/db.py — SQLite schema with all 9 tables from DESIGN.md 3.5
Tables: projects, tasks, decisions, agent_logs, modules, pipelines,
project_links, support_tickets, support_bot_config.
WAL mode, foreign keys enabled, idempotent init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:12:54 +02:00
|
|
|
|
-- Кросс-проектные зависимости
|
|
|
|
|
|
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,
|
2026-03-17 18:31:33 +02:00
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
UNIQUE(from_project, to_project, type)
|
Add core/db.py — SQLite schema with all 9 tables from DESIGN.md 3.5
Tables: projects, tasks, decisions, agent_logs, modules, pipelines,
project_links, support_tickets, support_bot_config.
WAL mode, foreign keys enabled, idempotent init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:12:54 +02:00
|
|
|
|
);
|
|
|
|
|
|
|
2026-03-17 18:25:16 +02:00
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_project_links_to ON project_links(to_project);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_project_links_from ON project_links(from_project);
|
|
|
|
|
|
|
Add core/db.py — SQLite schema with all 9 tables from DESIGN.md 3.5
Tables: projects, tasks, decisions, agent_logs, modules, pipelines,
project_links, support_tickets, support_bot_config.
WAL mode, foreign keys enabled, idempotent init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:12:54 +02:00
|
|
|
|
-- Тикеты от пользователей
|
|
|
|
|
|
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
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-03-16 19:26:51 +02:00
|
|
|
|
-- Среды развёртывания проекта (prod/dev серверы)
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS project_environments (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
|
host TEXT NOT NULL,
|
|
|
|
|
|
port INTEGER DEFAULT 22,
|
|
|
|
|
|
username TEXT NOT NULL,
|
|
|
|
|
|
auth_type TEXT NOT NULL DEFAULT 'password',
|
|
|
|
|
|
auth_value TEXT,
|
|
|
|
|
|
is_installed INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
UNIQUE(project_id, name)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_environments_project ON project_environments(project_id);
|
|
|
|
|
|
|
Add core/db.py — SQLite schema with all 9 tables from DESIGN.md 3.5
Tables: projects, tasks, decisions, agent_logs, modules, pipelines,
project_links, support_tickets, support_bot_config.
WAL mode, foreign keys enabled, idempotent init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:12:54 +02:00
|
|
|
|
-- Индексы
|
|
|
|
|
|
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);
|
2026-03-16 19:26:51 +02:00
|
|
|
|
|
|
|
|
|
|
-- Чат-сообщения (KIN-OBS-012)
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
|
|
|
|
role TEXT NOT NULL,
|
|
|
|
|
|
content TEXT NOT NULL,
|
|
|
|
|
|
message_type TEXT DEFAULT 'text',
|
|
|
|
|
|
task_id TEXT REFERENCES tasks(id),
|
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_chat_messages_project ON chat_messages(project_id, created_at);
|
2026-03-16 20:17:39 +02:00
|
|
|
|
|
|
|
|
|
|
-- Вложения задач (KIN-090)
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS task_attachments (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
|
|
|
|
filename TEXT NOT NULL,
|
|
|
|
|
|
path TEXT NOT NULL,
|
|
|
|
|
|
mime_type TEXT NOT NULL,
|
|
|
|
|
|
size INTEGER NOT NULL,
|
|
|
|
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_task_attachments_task ON task_attachments(task_id);
|
2026-03-17 17:26:31 +02:00
|
|
|
|
|
|
|
|
|
|
-- Live console log (KIN-084)
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS pipeline_log (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
pipeline_id INTEGER NOT NULL REFERENCES pipelines(id),
|
|
|
|
|
|
ts TEXT NOT NULL DEFAULT (datetime('now')),
|
|
|
|
|
|
level TEXT NOT NULL DEFAULT 'INFO',
|
|
|
|
|
|
message TEXT NOT NULL,
|
|
|
|
|
|
extra_json TEXT
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_pipeline_log_pipeline_id ON pipeline_log(pipeline_id, id);
|
Add core/db.py — SQLite schema with all 9 tables from DESIGN.md 3.5
Tables: projects, tasks, decisions, agent_logs, modules, pipelines,
project_links, support_tickets, support_bot_config.
WAL mode, foreign keys enabled, idempotent init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:12:54 +02:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-15 14:39:33 +02:00
|
|
|
|
def _migrate(conn: sqlite3.Connection):
|
|
|
|
|
|
"""Run migrations for existing databases."""
|
|
|
|
|
|
# Check if language column exists on projects
|
2026-03-15 20:02:01 +02:00
|
|
|
|
proj_cols = {r[1] for r in conn.execute("PRAGMA table_info(projects)").fetchall()}
|
|
|
|
|
|
if "language" not in proj_cols:
|
2026-03-15 14:39:33 +02:00
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN language TEXT DEFAULT 'ru'")
|
|
|
|
|
|
conn.commit()
|
2026-03-15 20:02:01 +02:00
|
|
|
|
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()
|
2026-03-15 23:22:49 +02:00
|
|
|
|
if "blocked_reason" not in task_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE tasks ADD COLUMN blocked_reason TEXT")
|
|
|
|
|
|
conn.commit()
|
2026-03-15 14:39:33 +02:00
|
|
|
|
|
2026-03-16 06:59:46 +02:00
|
|
|
|
if "autocommit_enabled" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN autocommit_enabled INTEGER DEFAULT 0")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 07:13:32 +02:00
|
|
|
|
if "dangerously_skipped" not in task_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE tasks ADD COLUMN dangerously_skipped BOOLEAN DEFAULT 0")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "revise_comment" not in task_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE tasks ADD COLUMN revise_comment TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 08:21:13 +02:00
|
|
|
|
if "category" not in task_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE tasks ADD COLUMN category TEXT DEFAULT NULL")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 09:13:34 +02:00
|
|
|
|
if "blocked_at" not in task_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE tasks ADD COLUMN blocked_at DATETIME")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
if "blocked_agent_role" not in task_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE tasks ADD COLUMN blocked_agent_role TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
if "blocked_pipeline_step" not in task_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE tasks ADD COLUMN blocked_pipeline_step TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 09:43:26 +02:00
|
|
|
|
if "telegram_sent" not in task_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE tasks ADD COLUMN telegram_sent BOOLEAN DEFAULT 0")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 09:57:14 +02:00
|
|
|
|
if "acceptance_criteria" not in task_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE tasks ADD COLUMN acceptance_criteria TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 22:35:31 +02:00
|
|
|
|
if "revise_count" not in task_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE tasks ADD COLUMN revise_count INTEGER DEFAULT 0")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "labels" not in task_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE tasks ADD COLUMN labels JSON DEFAULT NULL")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "revise_target_role" not in task_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE tasks ADD COLUMN revise_target_role TEXT DEFAULT NULL")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 07:13:32 +02:00
|
|
|
|
if "obsidian_vault_path" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN obsidian_vault_path TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 22:35:31 +02:00
|
|
|
|
if "worktrees_enabled" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN worktrees_enabled INTEGER DEFAULT 0")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "auto_test_enabled" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN auto_test_enabled INTEGER DEFAULT 0")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 08:21:13 +02:00
|
|
|
|
if "deploy_command" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN deploy_command TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 09:13:34 +02:00
|
|
|
|
if "project_type" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN project_type TEXT DEFAULT 'development'")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "ssh_host" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN ssh_host TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "ssh_user" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN ssh_user TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "ssh_key_path" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN ssh_key_path TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "ssh_proxy_jump" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN ssh_proxy_jump TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "description" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN description TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-17 17:26:31 +02:00
|
|
|
|
if "deploy_host" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN deploy_host TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "deploy_path" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN deploy_path TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "deploy_runtime" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN deploy_runtime TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "deploy_restart_cmd" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN deploy_restart_cmd TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 09:13:34 +02:00
|
|
|
|
# Migrate audit_log + project_phases tables
|
2026-03-16 07:13:32 +02:00
|
|
|
|
existing_tables = {r[0] for r in conn.execute(
|
|
|
|
|
|
"SELECT name FROM sqlite_master WHERE type='table'"
|
|
|
|
|
|
).fetchall()}
|
2026-03-16 19:26:51 +02:00
|
|
|
|
if "project_environments" not in existing_tables:
|
|
|
|
|
|
conn.executescript("""
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS project_environments (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
|
host TEXT NOT NULL,
|
|
|
|
|
|
port INTEGER DEFAULT 22,
|
|
|
|
|
|
username TEXT NOT NULL,
|
|
|
|
|
|
auth_type TEXT NOT NULL DEFAULT 'password',
|
|
|
|
|
|
auth_value TEXT,
|
|
|
|
|
|
is_installed INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
UNIQUE(project_id, name)
|
|
|
|
|
|
);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_environments_project ON project_environments(project_id);
|
|
|
|
|
|
""")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 20:39:17 +02:00
|
|
|
|
# Migrate project_environments: old schema used label/login/credential,
|
|
|
|
|
|
# new schema uses name/username/auth_value (KIN-087 column rename).
|
|
|
|
|
|
env_cols = {r[1] for r in conn.execute("PRAGMA table_info(project_environments)").fetchall()}
|
|
|
|
|
|
if "name" not in env_cols and "label" in env_cols:
|
|
|
|
|
|
conn.executescript("""
|
|
|
|
|
|
PRAGMA foreign_keys=OFF;
|
|
|
|
|
|
CREATE TABLE project_environments_new (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
|
host TEXT NOT NULL,
|
|
|
|
|
|
port INTEGER DEFAULT 22,
|
|
|
|
|
|
username TEXT NOT NULL,
|
|
|
|
|
|
auth_type TEXT NOT NULL DEFAULT 'password',
|
|
|
|
|
|
auth_value TEXT,
|
|
|
|
|
|
is_installed INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
UNIQUE(project_id, name)
|
|
|
|
|
|
);
|
|
|
|
|
|
INSERT INTO project_environments_new
|
|
|
|
|
|
SELECT id, project_id, label, host, port, login, auth_type,
|
|
|
|
|
|
credential, is_installed, created_at, updated_at
|
|
|
|
|
|
FROM project_environments;
|
|
|
|
|
|
DROP TABLE project_environments;
|
|
|
|
|
|
ALTER TABLE project_environments_new RENAME TO project_environments;
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_environments_project ON project_environments(project_id);
|
|
|
|
|
|
PRAGMA foreign_keys=ON;
|
|
|
|
|
|
""")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 09:13:34 +02:00
|
|
|
|
if "project_phases" not in existing_tables:
|
|
|
|
|
|
conn.executescript("""
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS project_phases (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
|
|
|
|
role TEXT NOT NULL,
|
|
|
|
|
|
phase_order INTEGER NOT NULL,
|
|
|
|
|
|
status TEXT DEFAULT 'pending',
|
|
|
|
|
|
task_id TEXT REFERENCES tasks(id),
|
|
|
|
|
|
revise_count INTEGER DEFAULT 0,
|
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
|
);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_phases_project ON project_phases(project_id, phase_order);
|
|
|
|
|
|
""")
|
|
|
|
|
|
conn.commit()
|
kin: KIN-059 Workflow new_project с выбором команды. При создании нового проекта через GUI или CLI директор описывает проект свободным текстом и выбирает галочками какие этапы research нужны: ☐ Business analyst (бизнес-модель, аудитория, монетизация) ☐ Market researcher (конкуренты, ниша, отзывы, сильные/слабые стороны) ☐ Legal researcher (юрисдикция, лицензии, KYC/AML, GDPR) ☐ Tech researcher (API, ограничения, стоимость, альтернативы) ☐ UX designer (анализ UX конкурентов, user journey, wireframes) ☐ Marketer (стратегия продвижения, SEO, conversion-паттерны) ☐ Architect (blueprint на основе одобренных research'ей) — всегда последний Architect включается автоматически если выбран хотя бы один researcher. Каждый выбранный этап — отдельная задача на review. Директор одобряет, отклоняет, или просит доисследовать (Revise). Следующий этап только после approve предыдущего. GUI: форма 'New Project' с описанием + чекбоксы ролей + кнопка 'Start Research'. CLI: kin new-project 'описание' --roles 'business,market,tech,architect'
2026-03-16 09:30:00 +02:00
|
|
|
|
# Migrate project_phases columns (table may already exist without revise_comment)
|
|
|
|
|
|
phase_cols = {r[1] for r in conn.execute("PRAGMA table_info(project_phases)").fetchall()}
|
|
|
|
|
|
if "revise_comment" not in phase_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE project_phases ADD COLUMN revise_comment TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 07:13:32 +02:00
|
|
|
|
if "audit_log" not in existing_tables:
|
|
|
|
|
|
conn.executescript("""
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS audit_log (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
task_id TEXT REFERENCES tasks(id),
|
|
|
|
|
|
step_id TEXT,
|
|
|
|
|
|
event_type TEXT NOT NULL DEFAULT 'dangerous_skip',
|
|
|
|
|
|
reason TEXT,
|
|
|
|
|
|
project_id TEXT REFERENCES projects(id)
|
|
|
|
|
|
);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_audit_log_task ON audit_log(task_id);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_audit_log_event ON audit_log(event_type, timestamp);
|
|
|
|
|
|
""")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 10:04:01 +02:00
|
|
|
|
# Migrate columns that must exist before table recreation (KIN-UI-002)
|
|
|
|
|
|
# These columns are referenced in the INSERT SELECT below but were not added
|
|
|
|
|
|
# by any prior ALTER TABLE in this chain — causing OperationalError on minimal schemas.
|
|
|
|
|
|
if "tech_stack" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN tech_stack JSON DEFAULT NULL")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "priority" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN priority INTEGER DEFAULT 5")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "pm_prompt" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN pm_prompt TEXT DEFAULT NULL")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "claude_md_path" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN claude_md_path TEXT DEFAULT NULL")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "forgejo_repo" not in proj_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN forgejo_repo TEXT DEFAULT NULL")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
|
|
|
|
|
if "created_at" not in proj_cols:
|
|
|
|
|
|
# SQLite ALTER TABLE does not allow non-constant defaults like CURRENT_TIMESTAMP
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN created_at DATETIME DEFAULT NULL")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 09:44:31 +02:00
|
|
|
|
# Migrate projects.path from NOT NULL to nullable (KIN-ARCH-003)
|
|
|
|
|
|
# SQLite doesn't support ALTER COLUMN, so we recreate the table.
|
|
|
|
|
|
path_col_rows = conn.execute("PRAGMA table_info(projects)").fetchall()
|
|
|
|
|
|
path_col = next((r for r in path_col_rows if r[1] == "path"), None)
|
|
|
|
|
|
if path_col and path_col[3] == 1: # notnull == 1, migration needed
|
|
|
|
|
|
conn.executescript("""
|
|
|
|
|
|
PRAGMA foreign_keys=OFF;
|
|
|
|
|
|
CREATE TABLE projects_new (
|
|
|
|
|
|
id TEXT PRIMARY KEY,
|
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
|
path TEXT CHECK (path IS NOT NULL OR project_type = 'operations'),
|
|
|
|
|
|
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',
|
|
|
|
|
|
deploy_command TEXT,
|
|
|
|
|
|
project_type TEXT DEFAULT 'development',
|
|
|
|
|
|
ssh_host TEXT,
|
|
|
|
|
|
ssh_user TEXT,
|
|
|
|
|
|
ssh_key_path TEXT,
|
|
|
|
|
|
ssh_proxy_jump TEXT,
|
|
|
|
|
|
description TEXT,
|
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
|
autocommit_enabled INTEGER DEFAULT 0,
|
|
|
|
|
|
obsidian_vault_path TEXT
|
|
|
|
|
|
);
|
|
|
|
|
|
INSERT INTO projects_new
|
|
|
|
|
|
SELECT id, name, path, tech_stack, status, priority,
|
|
|
|
|
|
pm_prompt, claude_md_path, forgejo_repo, language,
|
|
|
|
|
|
execution_mode, deploy_command, project_type,
|
|
|
|
|
|
ssh_host, ssh_user, ssh_key_path, ssh_proxy_jump,
|
|
|
|
|
|
description, created_at, autocommit_enabled, obsidian_vault_path
|
|
|
|
|
|
FROM projects;
|
|
|
|
|
|
DROP TABLE projects;
|
|
|
|
|
|
ALTER TABLE projects_new RENAME TO projects;
|
|
|
|
|
|
PRAGMA foreign_keys=ON;
|
|
|
|
|
|
""")
|
|
|
|
|
|
|
2026-03-16 19:26:51 +02:00
|
|
|
|
if "chat_messages" not in existing_tables:
|
|
|
|
|
|
conn.executescript("""
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
|
|
|
|
role TEXT NOT NULL,
|
|
|
|
|
|
content TEXT NOT NULL,
|
|
|
|
|
|
message_type TEXT DEFAULT 'text',
|
|
|
|
|
|
task_id TEXT REFERENCES tasks(id),
|
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
|
);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_chat_messages_project ON chat_messages(project_id, created_at);
|
|
|
|
|
|
""")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 20:39:17 +02:00
|
|
|
|
if "task_attachments" not in existing_tables:
|
|
|
|
|
|
conn.executescript("""
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS task_attachments (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
|
|
|
|
filename TEXT NOT NULL,
|
|
|
|
|
|
path TEXT NOT NULL,
|
|
|
|
|
|
mime_type TEXT NOT NULL,
|
|
|
|
|
|
size INTEGER NOT NULL,
|
|
|
|
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
|
|
|
|
);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_task_attachments_task ON task_attachments(task_id);
|
|
|
|
|
|
""")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-17 17:26:31 +02:00
|
|
|
|
if "pipeline_log" not in existing_tables:
|
|
|
|
|
|
conn.executescript("""
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS pipeline_log (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
pipeline_id INTEGER NOT NULL REFERENCES pipelines(id),
|
|
|
|
|
|
ts TEXT NOT NULL DEFAULT (datetime('now')),
|
|
|
|
|
|
level TEXT NOT NULL DEFAULT 'INFO',
|
|
|
|
|
|
message TEXT NOT NULL,
|
|
|
|
|
|
extra_json TEXT
|
|
|
|
|
|
);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_pipeline_log_pipeline_id ON pipeline_log(pipeline_id, id);
|
|
|
|
|
|
""")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-17 14:03:53 +02:00
|
|
|
|
# Migrate pipelines: add parent_pipeline_id and department columns (KIN-098)
|
2026-03-17 16:01:40 +02:00
|
|
|
|
# Guard: table may not exist in legacy schemas without pipelines (old test fixtures)
|
|
|
|
|
|
if "pipelines" in existing_tables:
|
|
|
|
|
|
pipeline_cols = {r[1] for r in conn.execute("PRAGMA table_info(pipelines)").fetchall()}
|
|
|
|
|
|
if "parent_pipeline_id" not in pipeline_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE pipelines ADD COLUMN parent_pipeline_id INTEGER REFERENCES pipelines(id)")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
if "department" not in pipeline_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE pipelines ADD COLUMN department TEXT")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
if "pid" not in pipeline_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE pipelines ADD COLUMN pid INTEGER")
|
|
|
|
|
|
conn.commit()
|
2026-03-17 14:03:53 +02:00
|
|
|
|
|
|
|
|
|
|
# Create department_handoffs table (KIN-098)
|
|
|
|
|
|
if "department_handoffs" not in existing_tables:
|
|
|
|
|
|
conn.executescript("""
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS department_handoffs (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
pipeline_id INTEGER NOT NULL REFERENCES pipelines(id),
|
|
|
|
|
|
task_id TEXT NOT NULL REFERENCES tasks(id),
|
|
|
|
|
|
from_department TEXT NOT NULL,
|
|
|
|
|
|
to_department TEXT,
|
|
|
|
|
|
artifacts JSON,
|
|
|
|
|
|
decisions_made JSON,
|
|
|
|
|
|
blockers JSON,
|
|
|
|
|
|
status TEXT DEFAULT 'pending',
|
|
|
|
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
|
);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_handoffs_pipeline ON department_handoffs(pipeline_id);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_handoffs_task ON department_handoffs(task_id);
|
|
|
|
|
|
""")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-17 15:40:31 +02:00
|
|
|
|
# Add test_command column to projects (KIN-ARCH-008)
|
|
|
|
|
|
projects_cols = {row["name"] for row in conn.execute("PRAGMA table_info(projects)")}
|
|
|
|
|
|
if "test_command" not in projects_cols:
|
|
|
|
|
|
conn.execute("ALTER TABLE projects ADD COLUMN test_command TEXT DEFAULT 'make test'")
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-16 06:59:46 +02:00
|
|
|
|
# Rename legacy 'auto' → 'auto_complete' (KIN-063)
|
|
|
|
|
|
conn.execute(
|
|
|
|
|
|
"UPDATE projects SET execution_mode = 'auto_complete' WHERE execution_mode = 'auto'"
|
|
|
|
|
|
)
|
|
|
|
|
|
conn.execute(
|
|
|
|
|
|
"UPDATE tasks SET execution_mode = 'auto_complete' WHERE execution_mode = 'auto'"
|
|
|
|
|
|
)
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-17 16:02:47 +02:00
|
|
|
|
# Fix orphaned dept_sub pipelines left in 'running' state due to double-create bug
|
|
|
|
|
|
# (KIN-ARCH-012): before the fix, _execute_department_head_step created a pipeline
|
|
|
|
|
|
# that was never updated, leaving ghost 'running' records in prod DB.
|
2026-03-17 16:37:09 +02:00
|
|
|
|
if "pipelines" in existing_tables:
|
|
|
|
|
|
conn.execute(
|
|
|
|
|
|
"UPDATE pipelines SET status = 'failed' WHERE route_type = 'dept_sub' AND status = 'running'"
|
|
|
|
|
|
)
|
|
|
|
|
|
conn.commit()
|
2026-03-17 16:02:47 +02:00
|
|
|
|
|
2026-03-17 18:25:20 +02:00
|
|
|
|
# Create indexes for project_links (KIN-INFRA-008).
|
|
|
|
|
|
# Guard: indexes must be created AFTER the table exists.
|
|
|
|
|
|
if "project_links" in existing_tables:
|
|
|
|
|
|
existing_indexes = {r[0] for r in conn.execute(
|
|
|
|
|
|
"SELECT name FROM sqlite_master WHERE type='index'"
|
|
|
|
|
|
).fetchall()}
|
|
|
|
|
|
if "idx_project_links_to" not in existing_indexes:
|
|
|
|
|
|
conn.execute(
|
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_project_links_to ON project_links(to_project)"
|
|
|
|
|
|
)
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
if "idx_project_links_from" not in existing_indexes:
|
|
|
|
|
|
conn.execute(
|
|
|
|
|
|
"CREATE INDEX IF NOT EXISTS idx_project_links_from ON project_links(from_project)"
|
|
|
|
|
|
)
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-17 18:32:03 +02:00
|
|
|
|
# Add UNIQUE(from_project, to_project, type) to project_links (KIN-INFRA-013).
|
|
|
|
|
|
# SQLite does not support ALTER TABLE ADD CONSTRAINT — table recreation required.
|
|
|
|
|
|
if "project_links" in existing_tables:
|
|
|
|
|
|
pl_sql_row = conn.execute(
|
|
|
|
|
|
"SELECT sql FROM sqlite_master WHERE type='table' AND name='project_links'"
|
|
|
|
|
|
).fetchone()
|
|
|
|
|
|
pl_has_unique = pl_sql_row and "UNIQUE" in (pl_sql_row[0] or "").upper()
|
|
|
|
|
|
if not pl_has_unique:
|
|
|
|
|
|
conn.executescript("""
|
|
|
|
|
|
PRAGMA foreign_keys=OFF;
|
|
|
|
|
|
CREATE TABLE project_links_new (
|
|
|
|
|
|
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,
|
|
|
|
|
|
UNIQUE(from_project, to_project, type)
|
|
|
|
|
|
);
|
|
|
|
|
|
INSERT OR IGNORE INTO project_links_new
|
|
|
|
|
|
SELECT id, from_project, to_project, type, description, created_at
|
|
|
|
|
|
FROM project_links;
|
|
|
|
|
|
DROP TABLE project_links;
|
|
|
|
|
|
ALTER TABLE project_links_new RENAME TO project_links;
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_project_links_to ON project_links(to_project);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_project_links_from ON project_links(from_project);
|
|
|
|
|
|
PRAGMA foreign_keys=ON;
|
|
|
|
|
|
""")
|
|
|
|
|
|
|
2026-03-16 06:59:46 +02:00
|
|
|
|
|
|
|
|
|
|
def _seed_default_hooks(conn: sqlite3.Connection):
|
|
|
|
|
|
"""Seed default hooks for the kin project (idempotent).
|
|
|
|
|
|
|
|
|
|
|
|
Creates rebuild-frontend hook only when:
|
|
|
|
|
|
- project 'kin' exists in the projects table
|
|
|
|
|
|
- the hook doesn't already exist (no duplicate)
|
2026-03-16 15:48:09 +02:00
|
|
|
|
|
|
|
|
|
|
Also updates existing hooks to the correct command/config if outdated.
|
2026-03-16 06:59:46 +02:00
|
|
|
|
"""
|
2026-03-16 15:48:09 +02:00
|
|
|
|
kin_row = conn.execute(
|
|
|
|
|
|
"SELECT path FROM projects WHERE id = 'kin'"
|
2026-03-16 06:59:46 +02:00
|
|
|
|
).fetchone()
|
2026-03-16 15:48:09 +02:00
|
|
|
|
if not kin_row or not kin_row["path"]:
|
2026-03-16 06:59:46 +02:00
|
|
|
|
return
|
|
|
|
|
|
|
2026-03-16 15:48:09 +02:00
|
|
|
|
_PROJECT_PATH = kin_row["path"].rstrip("/")
|
|
|
|
|
|
_REBUILD_SCRIPT = f"{_PROJECT_PATH}/scripts/rebuild-frontend.sh"
|
|
|
|
|
|
_REBUILD_TRIGGER = "web/frontend/*"
|
|
|
|
|
|
_REBUILD_WORKDIR = _PROJECT_PATH
|
|
|
|
|
|
|
2026-03-16 06:59:46 +02:00
|
|
|
|
exists = conn.execute(
|
|
|
|
|
|
"SELECT 1 FROM hooks"
|
|
|
|
|
|
" WHERE project_id = 'kin'"
|
|
|
|
|
|
" AND name = 'rebuild-frontend'"
|
|
|
|
|
|
" AND event = 'pipeline_completed'"
|
|
|
|
|
|
).fetchone()
|
|
|
|
|
|
if not exists:
|
|
|
|
|
|
conn.execute(
|
2026-03-16 15:48:09 +02:00
|
|
|
|
"""INSERT INTO hooks
|
|
|
|
|
|
(project_id, name, event, trigger_module_path, command,
|
|
|
|
|
|
working_dir, timeout_seconds, enabled)
|
2026-03-16 06:59:46 +02:00
|
|
|
|
VALUES ('kin', 'rebuild-frontend', 'pipeline_completed',
|
2026-03-16 15:48:09 +02:00
|
|
|
|
?, ?, ?, 300, 1)""",
|
|
|
|
|
|
(_REBUILD_TRIGGER, _REBUILD_SCRIPT, _REBUILD_WORKDIR),
|
2026-03-16 06:59:46 +02:00
|
|
|
|
)
|
2026-03-16 15:48:09 +02:00
|
|
|
|
else:
|
|
|
|
|
|
# Migrate existing hook: set trigger_module_path, correct command, working_dir
|
|
|
|
|
|
conn.execute(
|
|
|
|
|
|
"""UPDATE hooks
|
|
|
|
|
|
SET trigger_module_path = ?,
|
|
|
|
|
|
command = ?,
|
|
|
|
|
|
working_dir = ?,
|
|
|
|
|
|
timeout_seconds = 300
|
|
|
|
|
|
WHERE project_id = 'kin' AND name = 'rebuild-frontend'""",
|
|
|
|
|
|
(_REBUILD_TRIGGER, _REBUILD_SCRIPT, _REBUILD_WORKDIR),
|
|
|
|
|
|
)
|
|
|
|
|
|
conn.commit()
|
2026-03-16 06:59:46 +02:00
|
|
|
|
|
|
|
|
|
|
# Enable autocommit for kin project (opt-in, idempotent)
|
|
|
|
|
|
conn.execute(
|
|
|
|
|
|
"UPDATE projects SET autocommit_enabled=1 WHERE id='kin' AND autocommit_enabled=0"
|
|
|
|
|
|
)
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
|
2026-03-15 14:39:33 +02:00
|
|
|
|
|
Add core/db.py — SQLite schema with all 9 tables from DESIGN.md 3.5
Tables: projects, tasks, decisions, agent_logs, modules, pipelines,
project_links, support_tickets, support_bot_config.
WAL mode, foreign keys enabled, idempotent init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:12:54 +02:00
|
|
|
|
def init_db(db_path: Path = DB_PATH) -> sqlite3.Connection:
|
|
|
|
|
|
conn = get_connection(db_path)
|
|
|
|
|
|
conn.executescript(SCHEMA)
|
|
|
|
|
|
conn.commit()
|
2026-03-15 14:39:33 +02:00
|
|
|
|
_migrate(conn)
|
2026-03-16 06:59:46 +02:00
|
|
|
|
_seed_default_hooks(conn)
|
Add core/db.py — SQLite schema with all 9 tables from DESIGN.md 3.5
Tables: projects, tasks, decisions, agent_logs, modules, pipelines,
project_links, support_tickets, support_bot_config.
WAL mode, foreign keys enabled, idempotent init.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:12:54 +02:00
|
|
|
|
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()
|