skill-dir-manager.js
4.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/**
* Skill Directory Manager — Skill 目录管理
*/
const fs = require('fs');
const path = require('path');
const config = require('../config');
// 核心 Skills 白名单(保留在 repo/skills/,随 Git 部署)
const CORE_SKILLS = new Set([
'master', 'innovator', 'gateway', 'coder', 'verifier', 'editor',
'meta_benchmark', 'meta_evolving', 'meta_creator', 'meta_hunt', 'meta_digest', 'meta_research', 'task-monitor',
'feishu-bot', 'whatsapp-bot', 'arenask',
'frontend-tester', 'dual_column_layout', 'multi-agent-system',
]);
function isCodeSkill(skillName) {
return CORE_SKILLS.has(skillName);
}
// 项目根目录 skills/(核心 skills,随 Git 部署)
const REPO_SKILLS_DIR = path.resolve(__dirname, '../../skills');
/**
* 查找 skill 目录 (优先 repo/skills/ → skillsCodeDir → skillsDataDir)
*/
function resolveSkillDir(skillName) {
// 1. 项目根目录 skills/(core skills: master, coder, etc.)
const repoDir = path.join(REPO_SKILLS_DIR, skillName);
if (fs.existsSync(repoDir)) return repoDir;
// 2. skillsCodeDir(data/skills/)
const codeDir = path.join(config.skillsCodeDir, skillName);
if (fs.existsSync(codeDir)) return codeDir;
// 3. skillsDataDir(通常与 codeDir 相同,但可通过 env 分离)
const dataDir = path.join(config.skillsDataDir, skillName);
if (fs.existsSync(dataDir)) return dataDir;
return null;
}
/**
* 获取新建 skill 的目标目录
*/
function getTargetDir(skillName) {
return path.join(
isCodeSkill(skillName) ? config.skillsCodeDir : config.skillsDataDir,
skillName
);
}
/**
* 初始化 skill 目录(创建生命周期子目录)
*/
function initSkillDir(skillName, options = {}) {
const baseDir = (options.isCode ?? isCodeSkill(skillName)) ? config.skillsCodeDir : config.skillsDataDir;
const skillDir = path.join(baseDir, skillName);
const subdirs = ['source', 'hunt', 'hunt/crawl', 'digest', 'evolve',
'production', 'production/logs', 'production/stats'];
for (const sub of subdirs) {
fs.mkdirSync(path.join(skillDir, sub), { recursive: true });
}
const metaPath = path.join(skillDir, 'source', 'metadata.json');
if (!fs.existsSync(metaPath)) {
fs.writeFileSync(metaPath, JSON.stringify({
skillName, createdAt: new Date().toISOString(), sourceUrl: null, author: null,
}, null, 2));
}
return skillDir;
}
/**
* 获取用户训练 skill 的目标目录
* @param {string} skillName
* @param {string|number} userId
* @returns {string} 绝对路径
*/
function getTrainedSkillDir(skillName, userId) {
return path.join(config.trainedSkillsDir, `user_${userId}`, skillName);
}
/**
* 初始化用户训练 skill 目录(含生命周期子目录)
* @param {string} skillName
* @param {string|number} userId
* @returns {string} skillDir 绝对路径
*/
function initTrainedSkillDir(skillName, userId) {
const skillDir = getTrainedSkillDir(skillName, userId);
const subdirs = ['source', 'hunt', 'hunt/crawl', 'digest', 'evolve',
'production', 'production/logs', 'production/stats'];
for (const sub of subdirs) {
fs.mkdirSync(path.join(skillDir, sub), { recursive: true });
}
const metaPath = path.join(skillDir, 'source', 'metadata.json');
if (!fs.existsSync(metaPath)) {
fs.writeFileSync(metaPath, JSON.stringify({
skillName, userId: String(userId),
createdAt: new Date().toISOString(), sourceUrl: null, author: null,
}, null, 2));
}
return skillDir;
}
/**
* 在训练目录中查找 skill(扫描所有 user_* 子目录)
* @param {string} skillName
* @param {string|number} [userId] - 指定用户,不指定则搜索所有用户
* @returns {string|null}
*/
function resolveTrainedSkillDir(skillName, userId) {
// 优先指定用户的目录
if (userId) {
const dir = getTrainedSkillDir(skillName, userId);
if (fs.existsSync(dir)) return dir;
}
// 搜索所有用户目录
const trainedDir = config.trainedSkillsDir;
if (!fs.existsSync(trainedDir)) return null;
try {
const userDirs = fs.readdirSync(trainedDir).filter(d => d.startsWith('user_'));
for (const userDir of userDirs) {
const dir = path.join(trainedDir, userDir, skillName);
if (fs.existsSync(dir)) return dir;
}
} catch {}
return null;
}
/** 确保全局数据目录存在 */
function ensureDataDirs() {
for (const dir of [config.skillsDataDir, config.skillsLogDir, config.trainedSkillsDir]) {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
}
}
module.exports = {
CORE_SKILLS, isCodeSkill, resolveSkillDir, getTargetDir, initSkillDir,
getTrainedSkillDir, initTrainedSkillDir, resolveTrainedSkillDir,
ensureDataDirs,
};