cc-devflow 4.1.1 → 4.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/flow/dev.md +1 -1
- package/.claude/commands/flow/quality.md +5 -0
- package/.claude/commands/flow/release.md +3 -3
- package/.claude/commands/flow/restart.md +1 -1
- package/.claude/commands/flow/status.md +2 -2
- package/.claude/commands/flow/update.md +1 -1
- package/.claude/commands/util/git-commit.md +1 -4
- package/.claude/scripts/flow-quality-full.sh +32 -1
- package/.claude/scripts/flow-quality-quick.sh +57 -2
- package/.claude/scripts/generate-status-report.sh +5 -5
- package/.claude/scripts/recover-workflow.sh +24 -24
- package/.claude/skills/workflow/flow-quality/SKILL.md +4 -0
- package/.claude/skills/workflow/flow-release/SKILL.md +1 -1
- package/CHANGELOG.md +70 -0
- package/bin/adapt.js +4 -0
- package/lib/compiler/CLAUDE.md +5 -3
- package/lib/compiler/__tests__/compile-regression.test.js +103 -0
- package/lib/compiler/__tests__/multi-module-emitters.test.js +37 -11
- package/lib/compiler/__tests__/parser.test.js +46 -0
- package/lib/compiler/__tests__/resource-copier.test.js +26 -0
- package/lib/compiler/__tests__/skill-discovery.test.js +72 -0
- package/lib/compiler/emitters/antigravity-emitter.js +57 -39
- package/lib/compiler/emitters/base-emitter.js +81 -1
- package/lib/compiler/emitters/codex-emitter.js +71 -69
- package/lib/compiler/emitters/cursor-emitter.js +14 -34
- package/lib/compiler/emitters/qwen-emitter.js +14 -34
- package/lib/compiler/index.js +124 -0
- package/lib/compiler/parser.js +43 -8
- package/lib/compiler/resource-copier.js +6 -0
- package/lib/compiler/skill-discovery.js +68 -0
- package/lib/compiler/skills-registry.js +8 -41
- package/package.json +1 -1
|
@@ -21,6 +21,7 @@ const yaml = require('js-yaml');
|
|
|
21
21
|
const matter = require('gray-matter');
|
|
22
22
|
const BaseEmitter = require('./base-emitter.js');
|
|
23
23
|
const { ContextExpander } = require('../context-expander.js');
|
|
24
|
+
const { discoverSkillEntries } = require('../skill-discovery.js');
|
|
24
25
|
|
|
25
26
|
class CursorEmitter extends BaseEmitter {
|
|
26
27
|
get name() {
|
|
@@ -58,40 +59,19 @@ class CursorEmitter extends BaseEmitter {
|
|
|
58
59
|
return results;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const skillDir = path.join(groupDir, skillEntry.name);
|
|
78
|
-
const skillMdPath = path.join(skillDir, 'SKILL.md');
|
|
79
|
-
|
|
80
|
-
if (!fs.existsSync(skillMdPath)) {
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
const result = await this._emitSkillAsRule(
|
|
86
|
-
skillEntry.name,
|
|
87
|
-
skillDir,
|
|
88
|
-
skillMdPath,
|
|
89
|
-
targetDir
|
|
90
|
-
);
|
|
91
|
-
results.push(result);
|
|
92
|
-
} catch (error) {
|
|
93
|
-
console.warn(`Warning: Failed to emit skill ${skillEntry.name}: ${error.message}`);
|
|
94
|
-
}
|
|
62
|
+
const skills = discoverSkillEntries(sourceDir);
|
|
63
|
+
|
|
64
|
+
for (const skill of skills) {
|
|
65
|
+
try {
|
|
66
|
+
const result = await this._emitSkillAsRule(
|
|
67
|
+
skill.name,
|
|
68
|
+
skill.skillDir,
|
|
69
|
+
skill.skillMdPath,
|
|
70
|
+
targetDir
|
|
71
|
+
);
|
|
72
|
+
results.push(result);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.warn(`Warning: Failed to emit skill ${skill.name}: ${error.message}`);
|
|
95
75
|
}
|
|
96
76
|
}
|
|
97
77
|
|
|
@@ -20,6 +20,7 @@ const toml = require('@iarna/toml');
|
|
|
20
20
|
const matter = require('gray-matter');
|
|
21
21
|
const BaseEmitter = require('./base-emitter.js');
|
|
22
22
|
const { ContextExpander } = require('../context-expander.js');
|
|
23
|
+
const { discoverSkillEntries } = require('../skill-discovery.js');
|
|
23
24
|
|
|
24
25
|
class QwenEmitter extends BaseEmitter {
|
|
25
26
|
get name() {
|
|
@@ -63,40 +64,19 @@ class QwenEmitter extends BaseEmitter {
|
|
|
63
64
|
return results;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const skillDir = path.join(groupDir, skillEntry.name);
|
|
83
|
-
const skillMdPath = path.join(skillDir, 'SKILL.md');
|
|
84
|
-
|
|
85
|
-
if (!fs.existsSync(skillMdPath)) {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const result = await this._emitSkillAsToml(
|
|
91
|
-
skillEntry.name,
|
|
92
|
-
skillDir,
|
|
93
|
-
skillMdPath,
|
|
94
|
-
targetDir
|
|
95
|
-
);
|
|
96
|
-
results.push(result);
|
|
97
|
-
} catch (error) {
|
|
98
|
-
console.warn(`Warning: Failed to emit skill ${skillEntry.name}: ${error.message}`);
|
|
99
|
-
}
|
|
67
|
+
const skills = discoverSkillEntries(sourceDir);
|
|
68
|
+
|
|
69
|
+
for (const skill of skills) {
|
|
70
|
+
try {
|
|
71
|
+
const result = await this._emitSkillAsToml(
|
|
72
|
+
skill.name,
|
|
73
|
+
skill.skillDir,
|
|
74
|
+
skill.skillMdPath,
|
|
75
|
+
targetDir
|
|
76
|
+
);
|
|
77
|
+
results.push(result);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.warn(`Warning: Failed to emit skill ${skill.name}: ${error.message}`);
|
|
100
80
|
}
|
|
101
81
|
}
|
|
102
82
|
|
package/lib/compiler/index.js
CHANGED
|
@@ -230,6 +230,110 @@ function getRulesTargetPath(platform, config) {
|
|
|
230
230
|
return path.join(config.folder, rulesConfig.dir || 'rules');
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
function getHooksTargetDir(config) {
|
|
234
|
+
return config.folder;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ============================================================
|
|
238
|
+
// emitPlatformModules - 编译 Skills/Agents/Rules/Hooks 模块
|
|
239
|
+
// ============================================================
|
|
240
|
+
async function emitPlatformModules(sourceBaseDir, platforms, options = {}) {
|
|
241
|
+
const {
|
|
242
|
+
includeSkills = true,
|
|
243
|
+
includeAgents = true,
|
|
244
|
+
includeRules = true,
|
|
245
|
+
includeHooks = true,
|
|
246
|
+
verbose = false
|
|
247
|
+
} = options;
|
|
248
|
+
|
|
249
|
+
const summary = {
|
|
250
|
+
skillsEmitted: 0,
|
|
251
|
+
agentsEmitted: 0,
|
|
252
|
+
moduleRulesEmitted: 0,
|
|
253
|
+
hooksEmitted: 0,
|
|
254
|
+
errors: []
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
for (const platform of platforms) {
|
|
258
|
+
const emitter = getEmitter(platform);
|
|
259
|
+
const platformConfig = getPlatformConfig(platform);
|
|
260
|
+
|
|
261
|
+
if (includeSkills) {
|
|
262
|
+
try {
|
|
263
|
+
const skillsSourceDir = path.join(sourceBaseDir, 'skills');
|
|
264
|
+
const skillsTargetDir = getSkillsTargetDir(platform, platformConfig);
|
|
265
|
+
|
|
266
|
+
if (fs.existsSync(skillsSourceDir)) {
|
|
267
|
+
const skillResults = await emitter.emitSkills(skillsSourceDir, skillsTargetDir);
|
|
268
|
+
summary.skillsEmitted += skillResults.length;
|
|
269
|
+
|
|
270
|
+
if (verbose && skillResults.length > 0) {
|
|
271
|
+
console.log(`Emitted skills: ${platform} (${skillResults.length})`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
} catch (error) {
|
|
275
|
+
summary.errors.push(`${platform}/skills: ${error.message}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (includeAgents) {
|
|
280
|
+
try {
|
|
281
|
+
const agentsSourceDir = path.join(sourceBaseDir, 'agents');
|
|
282
|
+
const agentsTargetPath = getAgentsTargetPath(platform, platformConfig);
|
|
283
|
+
|
|
284
|
+
if (fs.existsSync(agentsSourceDir)) {
|
|
285
|
+
const agentResults = await emitter.emitAgents(agentsSourceDir, agentsTargetPath);
|
|
286
|
+
summary.agentsEmitted += agentResults.length;
|
|
287
|
+
|
|
288
|
+
if (verbose && agentResults.length > 0) {
|
|
289
|
+
console.log(`Emitted agents: ${platform} (${agentResults.length})`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} catch (error) {
|
|
293
|
+
summary.errors.push(`${platform}/agents: ${error.message}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (includeRules) {
|
|
298
|
+
try {
|
|
299
|
+
const rulesSourceDir = path.join(sourceBaseDir, 'rules');
|
|
300
|
+
const rulesTargetPath = getRulesTargetPath(platform, platformConfig);
|
|
301
|
+
|
|
302
|
+
if (fs.existsSync(rulesSourceDir)) {
|
|
303
|
+
const ruleResults = await emitter.emitRules(rulesSourceDir, rulesTargetPath);
|
|
304
|
+
summary.moduleRulesEmitted += ruleResults.length;
|
|
305
|
+
|
|
306
|
+
if (verbose && ruleResults.length > 0) {
|
|
307
|
+
console.log(`Emitted module rules: ${platform} (${ruleResults.length})`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
summary.errors.push(`${platform}/rules: ${error.message}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (includeHooks && isModuleSupported(platform, 'hooks')) {
|
|
316
|
+
try {
|
|
317
|
+
const hooksSourceDir = path.join(sourceBaseDir, 'hooks');
|
|
318
|
+
const hooksTargetDir = getHooksTargetDir(platformConfig);
|
|
319
|
+
|
|
320
|
+
if (fs.existsSync(hooksSourceDir)) {
|
|
321
|
+
const hookResults = await emitter.emitHooks(hooksSourceDir, hooksTargetDir);
|
|
322
|
+
summary.hooksEmitted += hookResults.length;
|
|
323
|
+
|
|
324
|
+
if (verbose && hookResults.length > 0) {
|
|
325
|
+
console.log(`Emitted hooks: ${platform} (${hookResults.length})`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} catch (error) {
|
|
329
|
+
summary.errors.push(`${platform}/hooks: ${error.message}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return summary;
|
|
335
|
+
}
|
|
336
|
+
|
|
233
337
|
// ============================================================
|
|
234
338
|
// compile - 编译主函数
|
|
235
339
|
// ============================================================
|
|
@@ -259,6 +363,10 @@ async function compile(options = {}) {
|
|
|
259
363
|
filesSkipped: 0,
|
|
260
364
|
resourcesCopied: 0,
|
|
261
365
|
resourcesSkipped: 0,
|
|
366
|
+
skillsEmitted: 0,
|
|
367
|
+
agentsEmitted: 0,
|
|
368
|
+
moduleRulesEmitted: 0,
|
|
369
|
+
hooksEmitted: 0,
|
|
262
370
|
rulesGenerated: 0,
|
|
263
371
|
skillsRegistered: 0,
|
|
264
372
|
errors: []
|
|
@@ -307,6 +415,21 @@ async function compile(options = {}) {
|
|
|
307
415
|
}
|
|
308
416
|
}
|
|
309
417
|
|
|
418
|
+
// 编译多模块产物(skills / agents / rules / hooks)
|
|
419
|
+
const sourceBaseDir = path.resolve(path.dirname(sourceDir));
|
|
420
|
+
const moduleSummary = await emitPlatformModules(sourceBaseDir, platforms, {
|
|
421
|
+
includeSkills: skills,
|
|
422
|
+
includeAgents: true,
|
|
423
|
+
includeRules: rules,
|
|
424
|
+
includeHooks: true,
|
|
425
|
+
verbose
|
|
426
|
+
});
|
|
427
|
+
result.skillsEmitted = moduleSummary.skillsEmitted;
|
|
428
|
+
result.agentsEmitted = moduleSummary.agentsEmitted;
|
|
429
|
+
result.moduleRulesEmitted = moduleSummary.moduleRulesEmitted;
|
|
430
|
+
result.hooksEmitted = moduleSummary.hooksEmitted;
|
|
431
|
+
result.errors.push(...moduleSummary.errors);
|
|
432
|
+
|
|
310
433
|
// 解析所有命令文件
|
|
311
434
|
const absoluteSourceDir = path.resolve(sourceDir);
|
|
312
435
|
let irs = [];
|
|
@@ -440,6 +563,7 @@ async function compile(options = {}) {
|
|
|
440
563
|
manifest.generatedAt = new Date().toISOString();
|
|
441
564
|
await saveManifest(manifest, manifestPath);
|
|
442
565
|
|
|
566
|
+
result.success = result.errors.length === 0;
|
|
443
567
|
return result;
|
|
444
568
|
}
|
|
445
569
|
|
package/lib/compiler/parser.js
CHANGED
|
@@ -225,18 +225,18 @@ function parseCommand(filePath) {
|
|
|
225
225
|
async function parseAllCommands(dirPath) {
|
|
226
226
|
const absoluteDir = path.resolve(dirPath);
|
|
227
227
|
|
|
228
|
-
|
|
229
|
-
const files = fs.readdirSync(absoluteDir);
|
|
228
|
+
const mdFiles = collectMarkdownFiles(absoluteDir, absoluteDir);
|
|
230
229
|
|
|
231
|
-
//
|
|
232
|
-
const mdFiles = files.filter(file => file.endsWith('.md'));
|
|
233
|
-
|
|
234
|
-
// 解析每个文件
|
|
230
|
+
// 解析每个文件(保留相对路径,支持分层输出)
|
|
235
231
|
const results = [];
|
|
236
|
-
for (const
|
|
237
|
-
const filePath = path.join(absoluteDir, file);
|
|
232
|
+
for (const filePath of mdFiles) {
|
|
238
233
|
try {
|
|
239
234
|
const ir = parseCommand(filePath);
|
|
235
|
+
|
|
236
|
+
// 输出文件名使用 commands 根目录相对路径(不含扩展名)
|
|
237
|
+
const relativePath = path.relative(absoluteDir, filePath).replace(/\\/g, '/');
|
|
238
|
+
ir.source.filename = relativePath.replace(/\.md$/i, '');
|
|
239
|
+
|
|
240
240
|
results.push(ir);
|
|
241
241
|
} catch (error) {
|
|
242
242
|
// 重新抛出以便调用者处理
|
|
@@ -247,9 +247,44 @@ async function parseAllCommands(dirPath) {
|
|
|
247
247
|
return results;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
// ============================================================
|
|
251
|
+
// collectMarkdownFiles - 递归收集 Markdown 文件
|
|
252
|
+
// ============================================================
|
|
253
|
+
function collectMarkdownFiles(rootDir, currentDir) {
|
|
254
|
+
const results = [];
|
|
255
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
256
|
+
|
|
257
|
+
for (const entry of entries) {
|
|
258
|
+
// 跳过隐藏目录与 "_" 前缀目录
|
|
259
|
+
if (entry.isDirectory()) {
|
|
260
|
+
if (entry.name.startsWith('.') || entry.name.startsWith('_')) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const childFiles = collectMarkdownFiles(rootDir, path.join(currentDir, entry.name));
|
|
265
|
+
results.push(...childFiles);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!entry.isFile() || !entry.name.endsWith('.md')) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
results.push(path.join(currentDir, entry.name));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// 统一排序,确保输出稳定
|
|
277
|
+
if (currentDir === rootDir) {
|
|
278
|
+
results.sort((a, b) => a.localeCompare(b));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return results;
|
|
282
|
+
}
|
|
283
|
+
|
|
250
284
|
module.exports = {
|
|
251
285
|
parseCommand,
|
|
252
286
|
parseAllCommands,
|
|
287
|
+
collectMarkdownFiles,
|
|
253
288
|
hashContent,
|
|
254
289
|
detectPlaceholders,
|
|
255
290
|
validateScriptAliases,
|
|
@@ -120,6 +120,12 @@ function scanInlineClaudePaths(content) {
|
|
|
120
120
|
let cleanPath = match[0];
|
|
121
121
|
// 移除末尾的常见标点
|
|
122
122
|
cleanPath = cleanPath.replace(/[,;:.\s]+$/, '');
|
|
123
|
+
|
|
124
|
+
// 跳过 glob 模式(例如 *.jsonl.template),避免误判为单文件资源
|
|
125
|
+
if (cleanPath.includes('*')) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
123
129
|
paths.add(cleanPath);
|
|
124
130
|
}
|
|
125
131
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T041: Skill Discovery Utility
|
|
3
|
+
*
|
|
4
|
+
* [INPUT]: .claude/skills/ 目录
|
|
5
|
+
* [OUTPUT]: SkillEntry[] (name, skillDir, skillMdPath)
|
|
6
|
+
* [POS]: Skills 扫描工具,统一发现分组与非分组 Skill
|
|
7
|
+
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
8
|
+
*
|
|
9
|
+
* 规则:
|
|
10
|
+
* - 递归扫描所有子目录
|
|
11
|
+
* - 目录内存在 SKILL.md 即判定为 Skill
|
|
12
|
+
* - 跳过以下划线开头的目录(例如 _reference-implementations)
|
|
13
|
+
* - 扫描结果按 skillMdPath 排序,保证输出稳定
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
// ============================================================
|
|
20
|
+
// discoverSkillEntries - 递归扫描 Skill 目录
|
|
21
|
+
// ============================================================
|
|
22
|
+
function discoverSkillEntries(skillsDir) {
|
|
23
|
+
const absoluteDir = path.resolve(skillsDir);
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(absoluteDir)) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const entries = [];
|
|
30
|
+
walkSkillDirs(absoluteDir, entries);
|
|
31
|
+
|
|
32
|
+
// 保证输出稳定,避免不同文件系统顺序差异
|
|
33
|
+
entries.sort((a, b) => a.skillMdPath.localeCompare(b.skillMdPath));
|
|
34
|
+
return entries;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ============================================================
|
|
38
|
+
// walkSkillDirs - 深度优先扫描
|
|
39
|
+
// ============================================================
|
|
40
|
+
function walkSkillDirs(dirPath, entries) {
|
|
41
|
+
const dirName = path.basename(dirPath);
|
|
42
|
+
if (dirName.startsWith('_')) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const skillMdPath = path.join(dirPath, 'SKILL.md');
|
|
47
|
+
if (fs.existsSync(skillMdPath)) {
|
|
48
|
+
entries.push({
|
|
49
|
+
name: dirName,
|
|
50
|
+
skillDir: dirPath,
|
|
51
|
+
skillMdPath
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const children = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
57
|
+
for (const child of children) {
|
|
58
|
+
if (!child.isDirectory()) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
walkSkillDirs(path.join(dirPath, child.name), entries);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
discoverSkillEntries
|
|
68
|
+
};
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const matter = require('gray-matter');
|
|
14
|
+
const { discoverSkillEntries } = require('./skill-discovery.js');
|
|
14
15
|
|
|
15
16
|
// ============================================================
|
|
16
17
|
// Constants
|
|
@@ -23,35 +24,18 @@ const REGISTRY_OUTPUT_PATH = 'devflow/.generated/skills-registry.json';
|
|
|
23
24
|
// ============================================================
|
|
24
25
|
function generateSkillsRegistry(skillsDir) {
|
|
25
26
|
const registry = [];
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
if (!fs.existsSync(absoluteDir)) {
|
|
29
|
-
return registry;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const entries = fs.readdirSync(absoluteDir, { withFileTypes: true });
|
|
33
|
-
|
|
34
|
-
for (const entry of entries) {
|
|
35
|
-
if (!entry.isDirectory() || entry.name.startsWith('_')) {
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const skillDir = path.join(absoluteDir, entry.name);
|
|
40
|
-
const skillMdPath = path.join(skillDir, 'SKILL.md');
|
|
41
|
-
|
|
42
|
-
if (!fs.existsSync(skillMdPath)) {
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
27
|
+
const skillEntries = discoverSkillEntries(skillsDir);
|
|
45
28
|
|
|
29
|
+
for (const entry of skillEntries) {
|
|
46
30
|
try {
|
|
47
|
-
const skillMdContent = fs.readFileSync(skillMdPath, 'utf8');
|
|
31
|
+
const skillMdContent = fs.readFileSync(entry.skillMdPath, 'utf8');
|
|
48
32
|
const parsed = matter(skillMdContent);
|
|
49
33
|
|
|
50
34
|
registry.push({
|
|
51
35
|
name: parsed.data.name || entry.name,
|
|
52
36
|
description: parsed.data.description || '',
|
|
53
37
|
type: parsed.data.type || 'utility',
|
|
54
|
-
path: skillDir,
|
|
38
|
+
path: entry.skillDir,
|
|
55
39
|
triggers: []
|
|
56
40
|
});
|
|
57
41
|
} catch (error) {
|
|
@@ -82,29 +66,12 @@ async function generateSkillsRegistryV2(skillsDir) {
|
|
|
82
66
|
}
|
|
83
67
|
}
|
|
84
68
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
return createRegistry(skills);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const entries = fs.readdirSync(absoluteDir, { withFileTypes: true });
|
|
69
|
+
// 扫描技能目录(支持分组与非分组结构)
|
|
70
|
+
const entries = discoverSkillEntries(absoluteDir);
|
|
91
71
|
|
|
92
72
|
for (const entry of entries) {
|
|
93
|
-
// 跳过非目录和 _ 前缀目录
|
|
94
|
-
if (!entry.isDirectory() || entry.name.startsWith('_')) {
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const skillDir = path.join(absoluteDir, entry.name);
|
|
99
|
-
const skillMdPath = path.join(skillDir, 'SKILL.md');
|
|
100
|
-
|
|
101
|
-
// 必须有 SKILL.md
|
|
102
|
-
if (!fs.existsSync(skillMdPath)) {
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
73
|
try {
|
|
107
|
-
const skillEntry = parseSkillEntry(entry.name, skillDir, skillMdPath, skillRules);
|
|
74
|
+
const skillEntry = parseSkillEntry(entry.name, entry.skillDir, entry.skillMdPath, skillRules);
|
|
108
75
|
if (skillEntry) {
|
|
109
76
|
skills.push(skillEntry);
|
|
110
77
|
}
|