重构: 后端编排拆解与DRY债收敛及命名纠偏
阶段1拆解: stream 系外移 stream_relay.rs,mod.rs 3048→2607,loop_body 502→83 技能收尾: regenerate/edit 回溯补技能,新增 read_skill 工具,项目级技能扫描 DRY: emit 双写 46 处收敛 event_bus 正典,KV 引号归一收口,LOCK-SLOW 33 处单行化 警示骨架 4→1 与压缩兜底 2→1,噪音目录档位收敛,truncate 同名陷阱消除 工具元数据落 ToolMeta 八维,7 张 match 表改查 registry,parity 测试锁等价 命名: is_noise_entry/teardown_round/probe_missed_terminal_event 等 6 项纠偏
This commit is contained in:
@@ -6,7 +6,8 @@
|
||||
//! - `OnceLock<Vec>` → `RwLock<Option<Vec>>` + 快路径(读锁命中 clone)/ 慢路径(重扫)
|
||||
//! + `invalidate_skills` 写锁置 None,支持进程内 `ai_reload_skills` 热重载
|
||||
//! - `scan_skills` 返 `ScanResult{skills, conflicts}`:同名收集所有 path 入 conflicts,
|
||||
//! 保留首份(优先级 skills>commands>plugins);`SkillInfo.duplicates` 仅冲突时填。
|
||||
//! 保留首份(优先级 project>skills>commands>plugins,项目级经 `ai_list_skills(project_path)`
|
||||
//! 设 `PROJECT_SKILLS_ROOT` 后生效);`SkillInfo.duplicates` 仅冲突时填。
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
@@ -15,6 +16,8 @@ use std::sync::{RwLock, RwLockReadGuard};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::commands::ai::tool_registry::{is_noise_entry, NoiseDirTier};
|
||||
|
||||
/// 技能元信息(前端 `/` 联想 + 后端注入用)
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SkillInfo {
|
||||
@@ -51,6 +54,58 @@ fn claude_home() -> Option<PathBuf> {
|
||||
.map(|h| h.join(".claude"))
|
||||
}
|
||||
|
||||
/// 项目级技能根目录(进程内,前端经 `ai_list_skills(project_path)` 设置)。
|
||||
///
|
||||
/// Some(root) 时 `scan_skills` 额外扫 `<root>/.claude/skills/*/SKILL.md`(source="project"),
|
||||
/// 与 home 级三类一起参与去重/冲突;项目级优先级最高(同名覆盖 home,对齐 Claude Code
|
||||
/// 项目技能语义)。None = 仅 home 级(与旧行为一致)。
|
||||
/// 更新入口 `set_project_skills_root`(变更才触发重扫),`ai_list_skills` IPC 调用。
|
||||
static PROJECT_SKILLS_ROOT: RwLock<Option<PathBuf>> = RwLock::new(None);
|
||||
|
||||
/// 更新项目级技能根目录。返回是否发生变更(调用方据 true 调 `invalidate_skills` 触发重扫)。
|
||||
///
|
||||
/// `None`/空串 = 不改动当前值(`ai_list_skills` 不传参沿用已设 root,remote_bridge 零参
|
||||
/// 调用不受影响);重复传同值幂等(不触发重扫)。
|
||||
pub(crate) fn set_project_skills_root(path: Option<String>) -> bool {
|
||||
let new_root = path
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(PathBuf::from);
|
||||
// 锁中毒:写不进就当"无变更"返回(下次 IPC 重试自愈,不 panic)。
|
||||
let mut g = match RwLock::write(&PROJECT_SKILLS_ROOT) {
|
||||
Ok(g) => g,
|
||||
Err(_) => {
|
||||
tracing::error!("PROJECT_SKILLS_ROOT 写锁中毒,set_project_skills_root 跳过");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if *g == new_root {
|
||||
return false;
|
||||
}
|
||||
*g = new_root;
|
||||
true
|
||||
}
|
||||
|
||||
/// 读当前项目根快照(spawn_blocking 的 scan_skills 内同步读,clone 出避免持锁跨扫盘)。
|
||||
fn project_skills_root_snapshot() -> Option<PathBuf> {
|
||||
// 锁中毒降级 None(仅丢项目级技能,home 级不受影响)。
|
||||
RwLock::read(&PROJECT_SKILLS_ROOT)
|
||||
.ok()
|
||||
.and_then(|g| g.clone())
|
||||
}
|
||||
|
||||
/// 扫描项目级技能目录 `<root>/.claude/skills/*/SKILL.md`(单层,与 home skills 同构)。
|
||||
fn scan_project_skills(root: &Path, out: &mut Vec<SkillInfo>) {
|
||||
if let Ok(entries) = fs::read_dir(root.join(".claude").join("skills")) {
|
||||
for entry in entries.flatten() {
|
||||
if let Some(info) = parse_skill_file(&entry.path().join("SKILL.md"), "project") {
|
||||
out.push(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 剥离 markdown 首个 `---...---` frontmatter 块,返回正文。
|
||||
///
|
||||
/// 状态机:用行索引推进;首个 `---`(trim 后)进入 in_fm,遇到下个 `---` 退出,余为正文。
|
||||
@@ -159,9 +214,9 @@ fn collect_skill_files(dir: &Path, out: &mut Vec<PathBuf>) {
|
||||
for entry in entries.flatten() {
|
||||
let p = entry.path();
|
||||
if p.is_dir() {
|
||||
// 跳过依赖/版本目录,避免递归爆炸
|
||||
// 跳过依赖/VCS 目录,避免递归爆炸(SkillScan 最小集档,名单统一走 is_noise_entry)
|
||||
let name = p.file_name().and_then(|n| n.to_str()).unwrap_or("");
|
||||
if name == "node_modules" || name == ".git" {
|
||||
if is_noise_entry(name, NoiseDirTier::SkillScan) {
|
||||
continue;
|
||||
}
|
||||
collect_skill_files(&p, out);
|
||||
@@ -172,7 +227,10 @@ fn collect_skill_files(dir: &Path, out: &mut Vec<PathBuf>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// 扫描三类来源,按 name 去重(skills 优先 > commands > plugins)。
|
||||
/// 扫描全部来源,按 name 去重(project 优先 > skills > commands > plugins)。
|
||||
///
|
||||
/// 项目级:`PROJECT_SKILLS_ROOT` 已设时额外扫 `<root>/.claude/skills/*/SKILL.md`
|
||||
/// (source="project"),排在首位(项目级同名覆盖 home 级,对齐 Claude Code 项目技能语义)。
|
||||
///
|
||||
/// 返 `ScanResult`:`skills` 为去重后首份(优先级保留),`conflicts` 收集所有同名 path
|
||||
/// (仅当同名 > 1 时入列,前端提示用)。
|
||||
@@ -187,8 +245,15 @@ fn scan_skills() -> ScanResult {
|
||||
}
|
||||
};
|
||||
|
||||
// 三类来源按优先级顺序收集(skills > commands > plugins)
|
||||
let mut all: Vec<Vec<SkillInfo>> = Vec::with_capacity(3);
|
||||
// 各来源按优先级顺序收集(project > skills > commands > plugins)
|
||||
let mut all: Vec<Vec<SkillInfo>> = Vec::with_capacity(4);
|
||||
|
||||
// 0. <project>/.claude/skills/*/SKILL.md(项目级,PROJECT_SKILLS_ROOT 已设才扫)
|
||||
let mut batch_project = Vec::new();
|
||||
if let Some(root) = project_skills_root_snapshot() {
|
||||
scan_project_skills(&root, &mut batch_project);
|
||||
}
|
||||
all.push(batch_project);
|
||||
|
||||
// 1. ~/.claude/skills/*/SKILL.md
|
||||
let mut batch_skill = Vec::new();
|
||||
|
||||
Reference in New Issue
Block a user