mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-29 09:37:37 +08:00
fix: replace implicit app inference with explicit selection for Skills import and sync
Skills import previously inferred app enablement from filesystem presence, causing incorrect multi-app activation when the same skill directory existed under multiple app paths. Now the frontend submits explicit app selections via ImportSkillSelection, and schema migration preserves a snapshot of legacy app mappings to avoid lossy reconstruction. Also adds reconciliation to sync_to_app (removes disabled/orphaned symlinks) and MCP sync_all_enabled (removes disabled servers from live config).
This commit is contained in:
@@ -5,6 +5,13 @@
|
||||
use super::{lock_conn, Database, SCHEMA_VERSION};
|
||||
use crate::error::AppError;
|
||||
use rusqlite::Connection;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct LegacySkillMigrationRow {
|
||||
directory: String,
|
||||
app_type: String,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
/// 创建所有数据库表
|
||||
@@ -846,12 +853,31 @@ impl Database {
|
||||
|
||||
log::info!("开始迁移 skills 表到 v3 结构(统一管理架构)...");
|
||||
|
||||
// 1. 备份旧数据(用于日志)
|
||||
// 1. 备份旧数据(用于日志和后续启动迁移)
|
||||
let old_count: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM skills", [], |row| row.get(0))
|
||||
.unwrap_or(0);
|
||||
log::info!("旧 skills 表有 {old_count} 条记录");
|
||||
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT directory, app_type FROM skills
|
||||
WHERE installed = 1",
|
||||
)
|
||||
.map_err(|e| AppError::Database(format!("查询旧 skills 快照失败: {e}")))?;
|
||||
let snapshot_rows: Vec<LegacySkillMigrationRow> = stmt
|
||||
.query_map([], |row| {
|
||||
Ok(LegacySkillMigrationRow {
|
||||
directory: row.get(0)?,
|
||||
app_type: row.get(1)?,
|
||||
})
|
||||
})
|
||||
.map_err(|e| AppError::Database(format!("读取旧 skills 快照失败: {e}")))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|e| AppError::Database(format!("解析旧 skills 快照失败: {e}")))?;
|
||||
let snapshot_json = serde_json::to_string(&snapshot_rows)
|
||||
.map_err(|e| AppError::Database(format!("序列化旧 skills 快照失败: {e}")))?;
|
||||
|
||||
// 标记:需要在启动后从文件系统扫描并重建 Skills 数据
|
||||
// 说明:v3 结构将 Skills 的 SSOT 迁移到 ~/.cc-switch/skills/,
|
||||
// 旧表只存“安装记录”,无法直接无损迁移到新结构,因此改为启动后扫描 app 目录导入。
|
||||
@@ -859,6 +885,10 @@ impl Database {
|
||||
"INSERT OR REPLACE INTO settings (key, value) VALUES ('skills_ssot_migration_pending', 'true')",
|
||||
[],
|
||||
);
|
||||
let _ = conn.execute(
|
||||
"INSERT OR REPLACE INTO settings (key, value) VALUES ('skills_ssot_migration_snapshot', ?1)",
|
||||
[snapshot_json],
|
||||
);
|
||||
|
||||
// 2. 删除旧表
|
||||
conn.execute("DROP TABLE IF EXISTS skills", [])
|
||||
|
||||
@@ -488,6 +488,25 @@ fn migration_from_v3_8_schema_v1_to_current_schema_v3() {
|
||||
matches!(pending.as_deref(), Some("true") | Some("1")),
|
||||
"skills_ssot_migration_pending should be set after v2->v3 migration"
|
||||
);
|
||||
let snapshot: Option<String> = conn
|
||||
.query_row(
|
||||
"SELECT value FROM settings WHERE key = 'skills_ssot_migration_snapshot'",
|
||||
[],
|
||||
|r| r.get(0),
|
||||
)
|
||||
.ok();
|
||||
let snapshot = snapshot.expect("skills migration snapshot should be recorded");
|
||||
let snapshot_rows: serde_json::Value =
|
||||
serde_json::from_str(&snapshot).expect("parse skills migration snapshot");
|
||||
assert!(
|
||||
snapshot_rows
|
||||
.as_array()
|
||||
.is_some_and(|rows| rows.iter().any(|row| {
|
||||
row.get("directory").and_then(|v| v.as_str()) == Some("demo-skill")
|
||||
&& row.get("app_type").and_then(|v| v.as_str()) == Some("claude")
|
||||
})),
|
||||
"skills migration snapshot should preserve legacy app mapping"
|
||||
);
|
||||
|
||||
// v3.9+ 新增:proxy_config 三行 seed 必须存在(否则 UI 会查不到默认值)
|
||||
let proxy_rows: i64 = conn
|
||||
|
||||
Reference in New Issue
Block a user