564 lines
22 KiB
Rust
564 lines
22 KiB
Rust
//! 数据库迁移 — 建表 SQL 与版本管理
|
|
|
|
use anyhow::Result;
|
|
use rusqlite::Connection;
|
|
|
|
/// 检测表是否存在指定列(幂等迁移通用辅助)
|
|
fn column_exists(conn: &Connection, table: &str, col: &str) -> bool {
|
|
let Ok(mut stmt) = conn.prepare(&format!("PRAGMA table_info({table})")) else { return false };
|
|
let Ok(rows) = stmt.query_map([], |r| r.get::<_, String>(1)) else { return false };
|
|
for r in rows {
|
|
if let Ok(name) = r {
|
|
if name == col { return true; }
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// 执行所有迁移
|
|
pub fn run(conn: &Connection) -> Result<()> {
|
|
// 创建迁移版本表
|
|
conn.execute_batch(
|
|
"CREATE TABLE IF NOT EXISTS schema_version (
|
|
version INTEGER PRIMARY KEY
|
|
);"
|
|
)?;
|
|
|
|
let current_version: i32 = conn
|
|
.query_row(
|
|
"SELECT COALESCE(MAX(version), 0) FROM schema_version",
|
|
[],
|
|
|row| row.get(0),
|
|
)
|
|
.unwrap_or(0);
|
|
|
|
// 迁移步骤链: 顺序执行,跳过已应用的版本(current_version < N 才跑)。
|
|
// 新增版本时,在此数组追加一项 (N, migrate_vN) 即可,无需改逻辑。
|
|
let steps: [(i32, fn(&Connection) -> Result<()>); 18] = [
|
|
(1, migrate_v1),
|
|
(2, migrate_v2),
|
|
(3, migrate_v3),
|
|
(4, migrate_v4),
|
|
(5, migrate_v5),
|
|
(6, migrate_v6),
|
|
(7, migrate_v7),
|
|
(8, migrate_v8),
|
|
(9, migrate_v9),
|
|
(10, migrate_v10),
|
|
(11, migrate_v11),
|
|
(12, migrate_v12),
|
|
(13, migrate_v13),
|
|
(14, migrate_v14),
|
|
(15, migrate_v15),
|
|
(16, migrate_v16),
|
|
(17, migrate_v17),
|
|
(18, migrate_v18),
|
|
];
|
|
|
|
for (version, migrate_fn) in steps {
|
|
if current_version < version {
|
|
migrate_fn(conn)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// V1: 初始表结构
|
|
fn migrate_v1(conn: &Connection) -> Result<()> {
|
|
conn.execute_batch(V1_SQL)?;
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [1])?;
|
|
tracing::info!("迁移 v1 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V2: 补齐关联字段 + branches 表
|
|
fn migrate_v2(conn: &Connection) -> Result<()> {
|
|
conn.execute_batch(V2_SQL)?;
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [2])?;
|
|
tracing::info!("迁移 v2 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V3: AI 对话表补建(新库) + 归档标记列(新老库统一)
|
|
fn migrate_v3(conn: &Connection) -> Result<()> {
|
|
conn.execute_batch(V3_SQL)?;
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [3])?;
|
|
tracing::info!("迁移 v3 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V4: 幂等补 ai_conversations.archived 列
|
|
///
|
|
/// 修复历史缺陷:早期 v3 迁移仅写入版本号 3,ALTER ADD COLUMN archived 未实际生效,
|
|
/// 导致 schema_version=3 但 ai_conversations 缺列,from_row 读 archived 报错,
|
|
/// list_all 失败 → 前端历史会话不显示 + 新对话 insert 失败。
|
|
/// 因 run() 按 `current_version < 3` 跳过 v3,该列无法靠 v3 自补。
|
|
/// 此处用 PRAGMA 探测列存在性,缺失才 ALTER,对新库/老库/坏库均安全。
|
|
fn migrate_v4(conn: &Connection) -> Result<()> {
|
|
let has_archived = column_exists(conn, "ai_conversations", "archived");
|
|
if !has_archived {
|
|
conn.execute(
|
|
"ALTER TABLE ai_conversations ADD COLUMN archived INTEGER NOT NULL DEFAULT 0",
|
|
[],
|
|
)?;
|
|
tracing::info!("v4: 补建 ai_conversations.archived 列");
|
|
}
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [4])?;
|
|
tracing::info!("迁移 v4 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V5: 幂等补 ai_conversations.prompt_tokens / completion_tokens 列
|
|
///
|
|
/// 流式 token 用量记录:对话级累计 input/output token(由 save_conversation 写入)。
|
|
/// 用 PRAGMA 探测列存在性,缺失才 ALTER,对新库/老库/坏库均安全(同 v4 模式)。
|
|
fn migrate_v5(conn: &Connection) -> Result<()> {
|
|
if !column_exists(conn, "ai_conversations", "prompt_tokens") {
|
|
conn.execute("ALTER TABLE ai_conversations ADD COLUMN prompt_tokens INTEGER", [])?;
|
|
tracing::info!("v5: 补建 ai_conversations.prompt_tokens 列");
|
|
}
|
|
if !column_exists(conn, "ai_conversations", "completion_tokens") {
|
|
conn.execute("ALTER TABLE ai_conversations ADD COLUMN completion_tokens INTEGER", [])?;
|
|
tracing::info!("v5: 补建 ai_conversations.completion_tokens 列");
|
|
}
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [5])?;
|
|
tracing::info!("迁移 v5 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V6: 幂等补 ai_conversations.models 列
|
|
///
|
|
/// 对话级多 model 记录:JSON 数组字符串(去重存对话用过的所有 model)。
|
|
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5 模式)。
|
|
fn migrate_v6(conn: &Connection) -> Result<()> {
|
|
let has_models = column_exists(conn, "ai_conversations", "models");
|
|
if !has_models {
|
|
conn.execute("ALTER TABLE ai_conversations ADD COLUMN models TEXT", [])?;
|
|
tracing::info!("v6: 补建 ai_conversations.models 列");
|
|
}
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [6])?;
|
|
tracing::info!("迁移 v6 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V7: 知识库表 — 经验沉淀的基本单元(共享记忆层)
|
|
///
|
|
/// 状态机: candidate → pending_review → published → archived
|
|
/// AI 只产 candidate,人工门控发布;reuse_count 是唯一客观排序信号。
|
|
/// effectiveness 列不建(决策撤销人工评分)。时间字段用毫秒字符串(同既有 model 约定)。
|
|
fn migrate_v7(conn: &Connection) -> Result<()> {
|
|
conn.execute_batch(V7_SQL)?;
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [7])?;
|
|
tracing::info!("迁移 v7 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V8: 幂等补 knowledges.embedding 列(向量检索)
|
|
///
|
|
/// 存 Vec<f32> 的小端字节序列化 BLOB。NULL = 未嵌入(走 LIKE 降级)。
|
|
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6 模式)。
|
|
fn migrate_v8(conn: &Connection) -> Result<()> {
|
|
let has_embedding = column_exists(conn, "knowledges", "embedding");
|
|
if !has_embedding {
|
|
conn.execute("ALTER TABLE knowledges ADD COLUMN embedding BLOB", [])?;
|
|
tracing::info!("v8: 补建 knowledges.embedding 列");
|
|
}
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [8])?;
|
|
tracing::info!("迁移 v8 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V9: 幂等补建 ai_providers + ai_tool_executions 表
|
|
///
|
|
/// 历史遗漏:这两张表从未写入迁移文件(V1-V8 均未包含),
|
|
/// 旧库可能通过其他方式已建,新库缺失导致 save_provider 等操作报 SQL 错误。
|
|
/// 用 CREATE TABLE IF NOT EXISTS 幂等,已有表不受影响。
|
|
fn migrate_v9(conn: &Connection) -> Result<()> {
|
|
conn.execute_batch(V9_SQL)?;
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [9])?;
|
|
tracing::info!("迁移 v9 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V10: 知识生命线 — 补 knowledges.reasoning 列 + 新建 knowledge_events 事件表
|
|
///
|
|
/// - reasoning: AI 提炼时给出的"为何值得沉淀"判断依据(此前 prompt 要求但写库丢弃,
|
|
/// 此处补列修复;老库行默认 NULL,前端降级展示"手动录入/无依据")。幂等(PRAGMA 探测)。
|
|
/// - knowledge_events: 追加型审计表,记录产生/审核/引用/归档四类事件,支撑生命线视图。
|
|
/// 独立表(非 JSON 嵌主表): 一条知识可被引用数百次,JSON 嵌入致行膨胀+更新竞争。
|
|
fn migrate_v10(conn: &Connection) -> Result<()> {
|
|
if !column_exists(conn, "knowledges", "reasoning") {
|
|
conn.execute("ALTER TABLE knowledges ADD COLUMN reasoning TEXT", [])?;
|
|
tracing::info!("v10: 补建 knowledges.reasoning 列");
|
|
}
|
|
conn.execute_batch(V10_SQL)?;
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [10])?;
|
|
tracing::info!("迁移 v10 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V11: 幂等补 projects.deleted_at 列(软删回收站)
|
|
///
|
|
/// 删除项目改为软删:deleted_at NULL=正常,非空=已进回收站(可恢复)。
|
|
/// ProjectRecord 不带该字段,纯靠 SQL WHERE deleted_at IS NULL 过滤;
|
|
/// 子表(tasks/releases/branches)不动,FK 仍满足,项目数据完整保留待恢复。
|
|
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10 模式)。
|
|
fn migrate_v11(conn: &Connection) -> Result<()> {
|
|
if !column_exists(conn, "projects", "deleted_at") {
|
|
conn.execute("ALTER TABLE projects ADD COLUMN deleted_at TEXT", [])?;
|
|
tracing::info!("v11: 补建 projects.deleted_at 列(软删回收站)");
|
|
}
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [11])?;
|
|
tracing::info!("迁移 v11 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V12: 幂等补 projects.path / projects.stack 列(项目绑定真实代码目录)
|
|
///
|
|
/// 项目与磁盘代码库脱钩是项目管理核心缺失:此版补 path(绑定目录绝对路径) +
|
|
/// stack(技术栈 JSON 数组字符串),为「绑定目录 + 探测技术栈」打地基,
|
|
/// 第二步「导入历史项目」直接复用。两列均 nullable,老项目 path/stack=NULL 天然兼容。
|
|
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10/v11 模式)。
|
|
fn migrate_v12(conn: &Connection) -> Result<()> {
|
|
if !column_exists(conn, "projects", "path") {
|
|
conn.execute("ALTER TABLE projects ADD COLUMN path TEXT", [])?;
|
|
tracing::info!("v12: 补建 projects.path 列(绑定代码目录)");
|
|
}
|
|
if !column_exists(conn, "projects", "stack") {
|
|
conn.execute("ALTER TABLE projects ADD COLUMN stack TEXT", [])?;
|
|
tracing::info!("v12: 补建 projects.stack 列(技术栈)");
|
|
}
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [12])?;
|
|
tracing::info!("迁移 v12 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V13: 通用应用设置 KV 表(前端 localStorage 迁移目标)
|
|
///
|
|
/// 存主题/语言/AI 偏好/连接配置等,`value` 为 JSON 字符串。CREATE TABLE IF NOT EXISTS 幂等。
|
|
fn migrate_v13(conn: &Connection) -> Result<()> {
|
|
conn.execute_batch(V13_SQL)?;
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [13])?;
|
|
tracing::info!("迁移 v13 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V14: 幂等补 tasks.deleted_at 列(软删回收站,对标 projects.deleted_at V11)
|
|
///
|
|
/// 删除任务改为软删:deleted_at NULL=正常,非空=已进回收站(可恢复)。
|
|
/// 与 projects.soft_delete 同模板:TaskRecord 不带该字段,纯靠 SQL WHERE deleted_at IS NULL
|
|
/// 过滤;子表(branches)不动,FK 仍满足,任务数据完整保留待恢复。
|
|
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10/v11 模式)。
|
|
fn migrate_v14(conn: &Connection) -> Result<()> {
|
|
if !column_exists(conn, "tasks", "deleted_at") {
|
|
conn.execute("ALTER TABLE tasks ADD COLUMN deleted_at TEXT", [])?;
|
|
tracing::info!("v14: 补建 tasks.deleted_at 列(软删回收站)");
|
|
}
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [14])?;
|
|
tracing::info!("迁移 v14 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V15: 幂等补 tasks.review_rounds 列(review 退回累计轮数,F-260616-04)
|
|
///
|
|
/// 任务推进链状态机退回时累加:in_review→in_progress / testing→in_review 各 +1,
|
|
/// 由 advance_task(df-nodes::task_advance_node)原子写入。默认 0(从未退回过的任务)。
|
|
/// NOT NULL DEFAULT 0 保证老库行迁移后取值确定(非 NULL),TaskRecord 字段为 i32(非 Option)。
|
|
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10/v11/v14 模式)。
|
|
fn migrate_v15(conn: &Connection) -> Result<()> {
|
|
if !column_exists(conn, "tasks", "review_rounds") {
|
|
conn.execute(
|
|
"ALTER TABLE tasks ADD COLUMN review_rounds INTEGER NOT NULL DEFAULT 0",
|
|
[],
|
|
)?;
|
|
tracing::info!("v15: 补建 tasks.review_rounds 列(review 退回累计轮数)");
|
|
}
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [15])?;
|
|
tracing::info!("迁移 v15 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V16: 幂等补 ai_conversations.pinned 列(对话置顶,UX-17)
|
|
///
|
|
/// 侧栏置顶分组排序信号:前端按 pinned DESC, updated_at DESC 排,置顶在前。
|
|
/// 纯元数据标记(同 archived),NOT NULL DEFAULT 0 保证老库行非 NULL,AiConversationRecord.pinned 为 bool。
|
|
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10/v11/v14/v15 模式),对新库/老库均安全。
|
|
fn migrate_v16(conn: &Connection) -> Result<()> {
|
|
if !column_exists(conn, "ai_conversations", "pinned") {
|
|
conn.execute(
|
|
"ALTER TABLE ai_conversations ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0",
|
|
[],
|
|
)?;
|
|
tracing::info!("v16: 补建 ai_conversations.pinned 列(对话置顶)");
|
|
}
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [16])?;
|
|
tracing::info!("迁移 v16 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V17: 幂等补 tasks.output_json 列(AiNode 自审闭环产出,决策 a:task 中心)
|
|
///
|
|
/// 任务产出 JSON 字符串:ai_execute 写产出 / ai_self_review 读产出做自审 / human_review 展示对象。
|
|
/// TEXT NULL 向后兼容(老库行默认 NULL,TaskRecord 字段为 Option<String>),
|
|
/// 经通用 update_field 白名单写入(非 status 状态机收口字段,status 收口 F-03 不变)。
|
|
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10/v11/v14/v15/v16 模式),对新库/老库均安全。
|
|
fn migrate_v17(conn: &Connection) -> Result<()> {
|
|
if !column_exists(conn, "tasks", "output_json") {
|
|
conn.execute("ALTER TABLE tasks ADD COLUMN output_json TEXT", [])?;
|
|
tracing::info!("v17: 补建 tasks.output_json 列(AiNode 自审闭环产出)");
|
|
}
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [17])?;
|
|
tracing::info!("迁移 v17 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V18: 幂等补 ai_providers.model_configs 列(模型能力配置,F-01 阶段1)
|
|
///
|
|
/// 模型 4 维度(模态/能力/价格/智力)+ 路由控制配置 JSON 字符串。TEXT NULL 向后兼容:
|
|
/// 老库行默认 NULL,from_row 经 deserialize_model_configs 解析为空 Vec(配合 default_model 过渡)。
|
|
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10/v11/v14/v15/v16/v17 模式),对新库/老库均安全。
|
|
fn migrate_v18(conn: &Connection) -> Result<()> {
|
|
if !column_exists(conn, "ai_providers", "model_configs") {
|
|
conn.execute("ALTER TABLE ai_providers ADD COLUMN model_configs TEXT", [])?;
|
|
tracing::info!("v18: 补建 ai_providers.model_configs 列(模型能力配置)");
|
|
}
|
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [18])?;
|
|
tracing::info!("迁移 v18 完成");
|
|
Ok(())
|
|
}
|
|
|
|
/// V1 建表 SQL
|
|
const V1_SQL: &str = "
|
|
-- 想法表
|
|
CREATE TABLE IF NOT EXISTS ideas (
|
|
id TEXT PRIMARY KEY,
|
|
title TEXT NOT NULL,
|
|
description TEXT NOT NULL DEFAULT '',
|
|
status TEXT NOT NULL DEFAULT 'draft',
|
|
priority INTEGER NOT NULL DEFAULT 1,
|
|
score REAL,
|
|
tags TEXT,
|
|
source TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
-- 项目表
|
|
CREATE TABLE IF NOT EXISTS projects (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
description TEXT NOT NULL DEFAULT '',
|
|
status TEXT NOT NULL DEFAULT 'planning',
|
|
idea_id TEXT REFERENCES ideas(id),
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
-- 任务表
|
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
id TEXT PRIMARY KEY,
|
|
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
title TEXT NOT NULL,
|
|
description TEXT NOT NULL DEFAULT '',
|
|
status TEXT NOT NULL DEFAULT 'todo',
|
|
-- priority 默认 2 对齐 task.rs default_priority()=2(medium)
|
|
priority INTEGER NOT NULL DEFAULT 2,
|
|
branch_name TEXT,
|
|
assignee TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
-- 发布表
|
|
CREATE TABLE IF NOT EXISTS releases (
|
|
id TEXT PRIMARY KEY,
|
|
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
version TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'planned',
|
|
task_ids TEXT NOT NULL DEFAULT '[]',
|
|
changelog TEXT,
|
|
created_at TEXT NOT NULL,
|
|
released_at TEXT
|
|
);
|
|
|
|
-- 工作流执行表
|
|
CREATE TABLE IF NOT EXISTS workflow_executions (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
dag_json TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
triggered_by TEXT,
|
|
created_at TEXT NOT NULL,
|
|
completed_at TEXT
|
|
);
|
|
|
|
-- 节点执行表
|
|
CREATE TABLE IF NOT EXISTS node_executions (
|
|
id TEXT PRIMARY KEY,
|
|
workflow_id TEXT NOT NULL REFERENCES workflow_executions(id),
|
|
node_id TEXT NOT NULL,
|
|
node_type TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
input_json TEXT,
|
|
output_json TEXT,
|
|
error_message TEXT,
|
|
started_at TEXT,
|
|
completed_at TEXT
|
|
);
|
|
|
|
-- 索引
|
|
CREATE INDEX IF NOT EXISTS idx_tasks_project_id ON tasks(project_id);
|
|
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
CREATE INDEX IF NOT EXISTS idx_releases_project_id ON releases(project_id);
|
|
CREATE INDEX IF NOT EXISTS idx_node_executions_workflow_id ON node_executions(workflow_id);
|
|
";
|
|
|
|
/// V2 迁移 SQL — 补齐数据层断裂字段
|
|
///
|
|
/// 注意: SQLite 的 ALTER TABLE ADD COLUMN 一条语句只能加一列。
|
|
const V2_SQL: &str = "
|
|
-- 想法表: 晋升关联 + AI 分析 + 多维评分
|
|
ALTER TABLE ideas ADD COLUMN promoted_to TEXT;
|
|
ALTER TABLE ideas ADD COLUMN ai_analysis TEXT;
|
|
ALTER TABLE ideas ADD COLUMN scores TEXT;
|
|
|
|
-- 任务表: 工作流定义关联 + 基础分支
|
|
ALTER TABLE tasks ADD COLUMN workflow_def_id TEXT;
|
|
ALTER TABLE tasks ADD COLUMN base_branch TEXT;
|
|
|
|
-- 工作流执行表: 项目 / 任务关联
|
|
ALTER TABLE workflow_executions ADD COLUMN project_id TEXT;
|
|
ALTER TABLE workflow_executions ADD COLUMN task_id TEXT;
|
|
|
|
-- 分支表 — 任务与 Git 分支绑定(核心功能)
|
|
CREATE TABLE IF NOT EXISTS branches (
|
|
id TEXT PRIMARY KEY,
|
|
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
task_id TEXT REFERENCES tasks(id),
|
|
name TEXT NOT NULL,
|
|
base TEXT NOT NULL DEFAULT 'main',
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
merged_at TEXT
|
|
);
|
|
|
|
-- 索引
|
|
CREATE INDEX IF NOT EXISTS idx_branches_project_id ON branches(project_id);
|
|
CREATE INDEX IF NOT EXISTS idx_branches_task_id ON branches(task_id);
|
|
";
|
|
|
|
/// V3 迁移 SQL — AI 对话表补建(新库首次创建;老库 IF NOT EXISTS 跳过)
|
|
///
|
|
/// 注:archived 列不在此处 ALTER —— 由 v4 迁移幂等补建。
|
|
/// (历史 v3 曾写入版本号但 ALTER 未生效,统一交 v4 用 PRAGMA 探测修复)
|
|
const V3_SQL: &str = "
|
|
CREATE TABLE IF NOT EXISTS ai_conversations (
|
|
id TEXT PRIMARY KEY,
|
|
title TEXT,
|
|
messages TEXT NOT NULL DEFAULT '[]',
|
|
provider_id TEXT,
|
|
model TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
";
|
|
|
|
/// V7 建表 SQL — 知识库表
|
|
///
|
|
/// kind: 7 种 KnowledgeKind snake_case(review_rule/prompt_template/pitfall/
|
|
/// architecture_pattern/diagnosis/deployment_note/workflow_optimization)
|
|
/// status: candidate|pending_review|published|archived
|
|
/// confidence: high|medium|low(AI 提炼自评,可空)
|
|
/// verified: 发布审核时一次性人工标(INTEGER 0/1)
|
|
/// reuse_count: 检索命中自动 +1(唯一客观排序信号)
|
|
/// source_project/source_ref: 来源溯源(不过滤,仅展示)
|
|
const V7_SQL: &str = "
|
|
CREATE TABLE IF NOT EXISTS knowledges (
|
|
id TEXT PRIMARY KEY,
|
|
kind TEXT NOT NULL DEFAULT 'pitfall',
|
|
title TEXT NOT NULL,
|
|
content TEXT NOT NULL DEFAULT '',
|
|
tags TEXT,
|
|
status TEXT NOT NULL DEFAULT 'candidate',
|
|
confidence TEXT,
|
|
reuse_count INTEGER NOT NULL DEFAULT 0,
|
|
verified INTEGER NOT NULL DEFAULT 0,
|
|
source_project TEXT,
|
|
source_ref TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_knowledges_status ON knowledges(status);
|
|
CREATE INDEX IF NOT EXISTS idx_knowledges_kind ON knowledges(kind);
|
|
CREATE INDEX IF NOT EXISTS idx_knowledges_reuse_count ON knowledges(reuse_count DESC);
|
|
";
|
|
|
|
/// V9 建表 SQL — AI Provider 配置 + 工具执行审计
|
|
///
|
|
/// 历史遗漏补建:ai_providers(AI 提供商配置) + ai_tool_executions(工具调用审计记录)。
|
|
/// CREATE TABLE IF NOT EXISTS 保证老库(已有表)和新库(缺表)均安全。
|
|
const V9_SQL: &str = "
|
|
CREATE TABLE IF NOT EXISTS ai_providers (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
provider_type TEXT NOT NULL DEFAULT 'openai_compat',
|
|
api_key TEXT NOT NULL,
|
|
base_url TEXT NOT NULL,
|
|
default_model TEXT NOT NULL,
|
|
models TEXT,
|
|
is_default INTEGER NOT NULL DEFAULT 0,
|
|
config TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS ai_tool_executions (
|
|
id TEXT PRIMARY KEY,
|
|
conversation_id TEXT,
|
|
tool_call_id TEXT NOT NULL,
|
|
tool_name TEXT NOT NULL,
|
|
arguments TEXT NOT NULL,
|
|
result TEXT,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
risk_level TEXT NOT NULL DEFAULT 'medium',
|
|
requested_at TEXT NOT NULL,
|
|
executed_at TEXT,
|
|
decided_by TEXT
|
|
);
|
|
";
|
|
|
|
/// V10 建表 SQL — 知识生命线事件表
|
|
///
|
|
/// 追加型审计表(只增不改),记录知识产生/审核/引用/归档四类事件,支撑生命线视图。
|
|
/// event_type: created | extracted | status_changed | referenced | archived
|
|
/// context_json: 因 event_type 而异的上下文(如引用事件的 conv_id+query)。
|
|
const V10_SQL: &str = "
|
|
CREATE TABLE IF NOT EXISTS knowledge_events (
|
|
id TEXT PRIMARY KEY,
|
|
knowledge_id TEXT NOT NULL,
|
|
event_type TEXT NOT NULL,
|
|
source_ref TEXT,
|
|
context_json TEXT,
|
|
timestamp TEXT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_knowledge_events_kid ON knowledge_events(knowledge_id);
|
|
CREATE INDEX IF NOT EXISTS idx_knowledge_events_type ON knowledge_events(event_type);
|
|
CREATE INDEX IF NOT EXISTS idx_knowledge_events_kid_type ON knowledge_events(knowledge_id, event_type);
|
|
";
|
|
|
|
/// V13 建表 SQL — 通用应用设置 KV 表
|
|
///
|
|
/// 前端 localStorage 迁移目标:key/value(JSON 字符串)+ updated_at。
|
|
/// CREATE TABLE IF NOT EXISTS 幂等(新库建、老库已有则跳过)。
|
|
const V13_SQL: &str = "
|
|
CREATE TABLE IF NOT EXISTS app_settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
";
|