cc-devflow 4.1.0 → 4.1.2
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/new.md +89 -252
- package/.claude/commands/util/git-commit.md +1 -4
- package/.claude/docs/templates/NEW_ORCHESTRATION_TEMPLATE.md +51 -91
- package/.claude/skills/workflow/flow-init/scripts/check-prerequisites.sh +10 -2
- package/.claude/skills/workflow/flow-init/scripts/create-requirement.sh +9 -1
- package/.claude/skills/workflow/flow-init/scripts/validate-research.sh +9 -1
- package/CHANGELOG.md +76 -0
- package/README.md +14 -18
- package/README.zh-CN.md +14 -12
- package/bin/adapt.js +4 -0
- package/lib/compiler/CLAUDE.md +5 -3
- package/lib/compiler/__tests__/compile-regression.test.js +94 -0
- 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 +35 -36
- package/lib/compiler/emitters/base-emitter.js +3 -1
- package/lib/compiler/emitters/codex-emitter.js +34 -35
- 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
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
|
}
|