cc-devflow 2.5.0 → 4.1.1
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/CLAUDE.md +1065 -53
- package/.claude/agents/dev-implementer.md +195 -0
- package/.claude/commands/{flow-archive.md → flow/archive.md} +46 -11
- package/.claude/commands/flow/context.md +150 -0
- package/.claude/commands/flow/delta.md +245 -0
- package/.claude/commands/{flow-dev.md → flow/dev.md} +112 -11
- package/.claude/commands/flow/init.md +45 -0
- package/.claude/commands/flow/new.md +279 -0
- package/.claude/commands/flow/quality.md +159 -0
- package/.claude/commands/flow/spec.md +186 -0
- package/.claude/commands/flow/workspace.md +146 -0
- package/.claude/commands/{cancel-ralph.md → util/cancel-ralph.md} +1 -0
- package/.claude/config/quality-gates.yml +305 -0
- package/.claude/docs/guides/TEAM_MODE_GUIDE.md +313 -0
- package/.claude/docs/templates/DELTA_SPEC_TEMPLATE.md +91 -0
- package/.claude/docs/templates/DESIGN_DECISIONS_TEMPLATE.md +151 -0
- package/.claude/docs/templates/JOURNAL_TEMPLATE.md +75 -0
- package/.claude/docs/templates/NEW_ORCHESTRATION_TEMPLATE.md +51 -91
- package/.claude/docs/templates/_shared/CLAUDE.md +36 -0
- package/.claude/docs/templates/_shared/CONSTITUTION_CHECK.md +125 -0
- package/.claude/docs/templates/_shared/VALIDATION_CHECKLIST.md +187 -0
- package/.claude/docs/templates/_shared/YAML_FRONTMATTER.md +164 -0
- package/.claude/docs/templates/context/dev.jsonl.template +6 -0
- package/.claude/docs/templates/context/epic.jsonl.template +5 -0
- package/.claude/docs/templates/context/prd.jsonl.template +4 -0
- package/.claude/docs/templates/context/research.jsonl.template +4 -0
- package/.claude/docs/templates/context/review.jsonl.template +5 -0
- package/.claude/docs/templates/context/tech.jsonl.template +5 -0
- package/.claude/hooks/CLAUDE.md +342 -0
- package/.claude/hooks/inject-agent-context.ts +480 -0
- package/.claude/hooks/inject-skill-context.ts +359 -0
- package/.claude/hooks/ralph-loop.ts +931 -0
- package/.claude/hooks/task-completed-hook.ts +593 -0
- package/.claude/hooks/teammate-idle-hook.ts +690 -0
- package/.claude/hooks/types/team-types.d.ts +238 -0
- package/.claude/rules/devflow-conventions.md +82 -9
- package/.claude/scripts/archive-requirement.sh +44 -1
- package/.claude/scripts/common.sh +670 -3
- package/.claude/scripts/delta-parser.ts +527 -0
- package/.claude/scripts/detect-file-conflicts.sh +151 -0
- package/.claude/scripts/flow-context-add.sh +134 -0
- package/.claude/scripts/flow-context-init.sh +133 -0
- package/.claude/scripts/flow-context-validate.sh +144 -0
- package/.claude/scripts/flow-delta-apply.sh +297 -0
- package/.claude/scripts/flow-delta-archive.sh +71 -0
- package/.claude/scripts/flow-delta-create.sh +202 -0
- package/.claude/scripts/flow-delta-list.sh +142 -0
- package/.claude/scripts/flow-delta-status.sh +235 -0
- package/.claude/scripts/flow-quality-full.sh +184 -0
- package/.claude/scripts/flow-quality-quick.sh +64 -0
- package/.claude/scripts/flow-workspace-init.sh +117 -0
- package/.claude/scripts/flow-workspace-record.sh +164 -0
- package/.claude/scripts/flow-workspace-start.sh +88 -0
- package/.claude/scripts/get-workflow-status.sh +415 -0
- package/.claude/scripts/parse-task-dependencies.js +334 -0
- package/.claude/scripts/record-quality-error.sh +165 -0
- package/.claude/scripts/run-quality-gates.sh +242 -0
- package/.claude/scripts/team-dev-init.sh +319 -0
- package/.claude/scripts/team-state-recovery.sh +229 -0
- package/.claude/scripts/workflow-status.ts +433 -0
- package/.claude/settings.json +19 -0
- package/.claude/skills/cc-devflow-orchestrator/SKILL.md +85 -200
- package/.claude/skills/domain/using-git-worktrees/SKILL.md +252 -0
- package/.claude/skills/domain/using-git-worktrees/assets/SHELL_ALIASES.md +133 -0
- package/.claude/skills/domain/using-git-worktrees/context.jsonl +4 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-cleanup.sh +218 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-create.sh +232 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-list.sh +130 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-status.sh +140 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-switch.sh +70 -0
- package/.claude/skills/utility/skill-creator/LICENSE.txt +202 -0
- package/.claude/skills/utility/skill-creator/SKILL.md +356 -0
- package/.claude/skills/utility/skill-creator/references/output-patterns.md +82 -0
- package/.claude/skills/utility/skill-creator/references/workflows.md +28 -0
- package/.claude/skills/utility/skill-creator/scripts/init_skill.py +303 -0
- package/.claude/skills/utility/skill-creator/scripts/package_skill.py +110 -0
- package/.claude/skills/utility/skill-creator/scripts/quick_validate.py +95 -0
- package/.claude/skills/workflow/flow-dev/CLAUDE.md +78 -0
- package/.claude/skills/workflow/flow-dev/SKILL.md +96 -0
- package/.claude/skills/workflow/flow-dev/assets/IMPLEMENTATION_PLAN_TEMPLATE.md +71 -0
- package/.claude/skills/workflow/flow-dev/context.jsonl +8 -0
- package/.claude/skills/workflow/flow-dev/dev-implementer.jsonl +8 -0
- package/.claude/skills/workflow/flow-dev/scripts/entry-gate.sh +116 -0
- package/.claude/skills/workflow/flow-dev/scripts/exit-gate.sh +101 -0
- package/.claude/skills/workflow/flow-dev/scripts/task-orchestrator.sh +106 -0
- package/.claude/skills/workflow/flow-fix/SKILL.md +105 -0
- package/.claude/skills/workflow/flow-fix/context.jsonl +6 -0
- package/.claude/skills/workflow/flow-fix/references/bug-analyzer.md +381 -0
- package/.claude/skills/workflow/flow-init/SKILL.md +211 -0
- package/.claude/skills/workflow/flow-init/assets/BRAINSTORM_TEMPLATE.md +148 -0
- package/.claude/skills/workflow/flow-init/assets/INIT_FLOW_TEMPLATE.md +198 -0
- package/.claude/skills/workflow/flow-init/assets/RESEARCH_TEMPLATE.md +276 -0
- package/.claude/skills/workflow/flow-init/context.jsonl +5 -0
- package/.claude/skills/workflow/flow-init/references/flow-researcher.md +132 -0
- package/.claude/skills/workflow/flow-init/scripts/check-prerequisites.sh +240 -0
- package/.claude/skills/workflow/flow-init/scripts/consolidate-research.sh +182 -0
- package/.claude/skills/workflow/flow-init/scripts/create-requirement.sh +523 -0
- package/.claude/skills/workflow/flow-init/scripts/generate-research-tasks.sh +157 -0
- package/.claude/skills/workflow/flow-init/scripts/populate-research-tasks.sh +284 -0
- package/.claude/skills/workflow/flow-init/scripts/validate-research.sh +340 -0
- package/.claude/skills/workflow/flow-quality/SKILL.md +94 -0
- package/.claude/skills/workflow/flow-quality/context.jsonl +6 -0
- package/.claude/skills/workflow/flow-quality/references/code-quality-reviewer.md +205 -0
- package/.claude/skills/workflow/flow-quality/references/qa-tester.md +313 -0
- package/.claude/skills/workflow/flow-quality/references/security-reviewer.md +314 -0
- package/.claude/skills/workflow/flow-quality/references/spec-reviewer.md +221 -0
- package/.claude/skills/workflow/flow-release/SKILL.md +126 -0
- package/.claude/skills/workflow/flow-release/context.jsonl +7 -0
- package/.claude/skills/workflow/flow-release/references/release-manager.md +295 -0
- package/.claude/skills/workflow/flow-spec/CLAUDE.md +103 -0
- package/.claude/skills/workflow/flow-spec/SKILL.md +545 -0
- package/.claude/skills/workflow/flow-spec/context.jsonl +7 -0
- package/.claude/skills/workflow/flow-spec/scripts/entry-gate.sh +194 -0
- package/.claude/skills/workflow/flow-spec/scripts/exit-gate.sh +244 -0
- package/.claude/skills/workflow/flow-spec/scripts/parallel-orchestrator.sh +205 -0
- package/.claude/skills/workflow/flow-spec/scripts/team-communication.sh +353 -0
- package/.claude/skills/workflow/flow-spec/scripts/team-init.sh +195 -0
- package/.claude/skills/workflow/flow-spec/scripts/test-team-mode.sh +496 -0
- package/.claude/skills/workflow/flow-spec/team-config.json +165 -0
- package/.claude/skills/workflow.yaml +417 -0
- package/CHANGELOG.md +268 -0
- package/README.md +206 -50
- package/README.zh-CN.md +219 -57
- package/lib/compiler/CLAUDE.md +77 -46
- package/lib/compiler/__tests__/multi-module-emitters.test.js +508 -0
- package/lib/compiler/context-expander.js +179 -0
- package/lib/compiler/emitters/antigravity-emitter.js +195 -5
- package/lib/compiler/emitters/base-emitter.js +217 -2
- package/lib/compiler/emitters/codex-emitter.js +200 -4
- package/lib/compiler/emitters/cursor-emitter.js +307 -3
- package/lib/compiler/emitters/qwen-emitter.js +196 -4
- package/lib/compiler/index.js +197 -2
- package/lib/compiler/platforms.js +270 -21
- package/package.json +2 -2
- package/.claude/commands/flow-epic.md +0 -183
- package/.claude/commands/flow-init.md +0 -370
- package/.claude/commands/flow-new.md +0 -442
- package/.claude/commands/flow-prd.md +0 -144
- package/.claude/commands/flow-qa.md +0 -93
- package/.claude/commands/flow-review.md +0 -257
- package/.claude/commands/flow-tech.md +0 -142
- package/.claude/commands/flow-ui.md +0 -189
- package/.claude/skills/file-header-guardian/SKILL.md +0 -56
- package/.claude/skills/skill-developer/ADVANCED.md +0 -197
- package/.claude/skills/skill-developer/HOOK_MECHANISMS.md +0 -306
- package/.claude/skills/skill-developer/PATTERNS_LIBRARY.md +0 -152
- package/.claude/skills/skill-developer/SKILL.md +0 -426
- package/.claude/skills/skill-developer/SKILL_RULES_REFERENCE.md +0 -315
- package/.claude/skills/skill-developer/TRIGGER_TYPES.md +0 -305
- package/.claude/skills/skill-developer/TROUBLESHOOTING.md +0 -514
- package/.claude/skills/writing-skills/SKILL.md +0 -655
- package/.claude/skills/writing-skills/anthropic-best-practices.md +0 -1150
- package/.claude/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +0 -189
- package/.claude/skills/writing-skills/graphviz-conventions.dot +0 -172
- package/.claude/skills/writing-skills/persuasion-principles.md +0 -187
- package/.claude/skills/writing-skills/render-graphs.js +0 -168
- package/.claude/skills/writing-skills/testing-skills-with-subagents.md +0 -384
- package/.claude/tsc-cache/795ba6e3-b98a-423b-bab2-51aa62812569/affected-repos.txt +0 -1
- package/.claude/tsc-cache/ae335694-be5a-4ba4-a1a0-b676c09a7906/affected-repos.txt +0 -1
- /package/.claude/commands/{core-architecture.md → core/architecture.md} +0 -0
- /package/.claude/commands/{core-guidelines.md → core/guidelines.md} +0 -0
- /package/.claude/commands/{core-roadmap.md → core/roadmap.md} +0 -0
- /package/.claude/commands/{core-style.md → core/style.md} +0 -0
- /package/.claude/commands/{flow-checklist.md → flow/checklist.md} +0 -0
- /package/.claude/commands/{flow-clarify.md → flow/clarify.md} +0 -0
- /package/.claude/commands/{flow-constitution.md → flow/constitution.md} +0 -0
- /package/.claude/commands/{flow-fix.md → flow/fix.md} +0 -0
- /package/.claude/commands/{flow-ideate.md → flow/ideate.md} +0 -0
- /package/.claude/commands/{flow-release.md → flow/release.md} +0 -0
- /package/.claude/commands/{flow-restart.md → flow/restart.md} +0 -0
- /package/.claude/commands/{flow-status.md → flow/status.md} +0 -0
- /package/.claude/commands/{flow-update.md → flow/update.md} +0 -0
- /package/.claude/commands/{flow-upgrade.md → flow/upgrade.md} +0 -0
- /package/.claude/commands/{flow-verify.md → flow/verify.md} +0 -0
- /package/.claude/commands/{code-review-high.md → util/code-review.md} +0 -0
- /package/.claude/commands/{git-commit.md → util/git-commit.md} +0 -0
- /package/.claude/commands/{problem-analyzer.md → util/problem-analyzer.md} +0 -0
- /package/.claude/skills/{flow-attention-refresh → domain/attention-refresh}/SKILL.md +0 -0
- /package/.claude/skills/{flow-brainstorming → domain/brainstorming}/SKILL.md +0 -0
- /package/.claude/skills/{flow-debugging → domain/debugging}/SKILL.md +0 -0
- /package/.claude/skills/{flow-finishing-branch → domain/finishing-branch}/SKILL.md +0 -0
- /package/.claude/skills/{flow-receiving-review → domain/receiving-review}/SKILL.md +0 -0
- /package/.claude/skills/{flow-tdd → domain/tdd}/SKILL.md +0 -0
- /package/.claude/skills/{verification-before-completion → domain/verification}/SKILL.md +0 -0
- /package/.claude/skills/{constitution-guardian → guardrail/constitution-guardian}/SKILL.md +0 -0
- /package/.claude/skills/{devflow-tdd-enforcer → guardrail/tdd-enforcer}/SKILL.md +0 -0
- /package/.claude/skills/{devflow-constitution-quick-ref → utility/constitution-quick-ref}/SKILL.md +0 -0
- /package/.claude/skills/{devflow-file-standards → utility/file-standards}/SKILL.md +0 -0
- /package/.claude/skills/{fractal-docs-generator → utility/fractal-docs}/SKILL.md +0 -0
- /package/.claude/skills/{journey-coherence-checker → utility/journey-checker}/SKILL.md +0 -0
- /package/.claude/skills/{journey-coherence-checker → utility/journey-checker}/pressure-scenarios.md +0 -0
- /package/.claude/skills/{npm-release → utility/npm-release}/SKILL.md +0 -0
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* T033: CursorEmitter - Cursor 平台输出
|
|
2
|
+
* T033: CursorEmitter - Cursor 平台输出 (v2.0)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* [INPUT]: CommandIR, SKILL.md, agents/*.md, rules/*.md, hooks/*.ts
|
|
5
|
+
* [OUTPUT]: .cursor/commands/*.md, .cursor/rules/*.mdc, .cursor/subagents/*.md, hooks.json
|
|
6
|
+
* [POS]: Cursor IDE 平台编译器,支持完整功能模块
|
|
7
|
+
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
8
|
+
*
|
|
9
|
+
* 输出格式:
|
|
10
|
+
* - Commands: 纯 Markdown → .cursor/commands/
|
|
11
|
+
* - Skills: MDC (YAML frontmatter) → .cursor/rules/
|
|
12
|
+
* - Agents: Subagents (YAML frontmatter) → .cursor/subagents/
|
|
13
|
+
* - Rules: MDC → .cursor/rules/
|
|
14
|
+
* - Hooks: hooks.json + hooks/*.sh
|
|
15
|
+
*
|
|
16
|
+
* v2.0: 支持多模块编译
|
|
6
17
|
*/
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const yaml = require('js-yaml');
|
|
21
|
+
const matter = require('gray-matter');
|
|
7
22
|
const BaseEmitter = require('./base-emitter.js');
|
|
23
|
+
const { ContextExpander } = require('../context-expander.js');
|
|
8
24
|
|
|
9
25
|
class CursorEmitter extends BaseEmitter {
|
|
10
26
|
get name() {
|
|
@@ -26,6 +42,294 @@ class CursorEmitter extends BaseEmitter {
|
|
|
26
42
|
format(ir, transformedContent) {
|
|
27
43
|
return transformedContent;
|
|
28
44
|
}
|
|
45
|
+
|
|
46
|
+
// ----------------------------------------------------------
|
|
47
|
+
// Multi-Module Emit Methods (v2.0)
|
|
48
|
+
// ----------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 编译 Skills 模块
|
|
52
|
+
* .claude/skills/[name]/SKILL.md -> .cursor/rules/[name].mdc
|
|
53
|
+
*/
|
|
54
|
+
async emitSkills(sourceDir, targetDir) {
|
|
55
|
+
const results = [];
|
|
56
|
+
|
|
57
|
+
if (!fs.existsSync(sourceDir)) {
|
|
58
|
+
return results;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 扫描技能分组目录
|
|
62
|
+
const groupDirs = await fs.promises.readdir(sourceDir, { withFileTypes: true });
|
|
63
|
+
|
|
64
|
+
for (const groupEntry of groupDirs) {
|
|
65
|
+
if (!groupEntry.isDirectory() || groupEntry.name.startsWith('_')) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const groupDir = path.join(sourceDir, groupEntry.name);
|
|
70
|
+
const skillEntries = await fs.promises.readdir(groupDir, { withFileTypes: true });
|
|
71
|
+
|
|
72
|
+
for (const skillEntry of skillEntries) {
|
|
73
|
+
if (!skillEntry.isDirectory() || skillEntry.name.startsWith('_')) {
|
|
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
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return results;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 将 Skill 转换为 Cursor Rule (.mdc)
|
|
103
|
+
*/
|
|
104
|
+
async _emitSkillAsRule(skillName, skillDir, skillMdPath, targetDir) {
|
|
105
|
+
let content = await fs.promises.readFile(skillMdPath, 'utf8');
|
|
106
|
+
const parsed = matter(content);
|
|
107
|
+
|
|
108
|
+
// 展开 context.jsonl (Cursor 使用 @file 引用)
|
|
109
|
+
const contextExpanded = ContextExpander.expandFromSkillDir(skillDir, 'cursor');
|
|
110
|
+
|
|
111
|
+
// 构建 MDC frontmatter
|
|
112
|
+
const frontmatterData = {
|
|
113
|
+
description: parsed.data.description || `${skillName} skill`,
|
|
114
|
+
globs: this._inferGlobs(skillName, parsed.data),
|
|
115
|
+
alwaysApply: parsed.data.alwaysApply || false
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const yamlStr = yaml.dump(frontmatterData, {
|
|
119
|
+
lineWidth: -1,
|
|
120
|
+
quotingType: '"',
|
|
121
|
+
forceQuotes: false
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// 组合最终内容
|
|
125
|
+
let body = parsed.content;
|
|
126
|
+
if (contextExpanded) {
|
|
127
|
+
body = contextExpanded + '\n' + body;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const finalContent = `---\n${yamlStr}---\n\n${body}`;
|
|
131
|
+
|
|
132
|
+
// 输出到 .cursor/rules/<skill-name>.mdc
|
|
133
|
+
const targetPath = path.join(targetDir, `${skillName}.mdc`);
|
|
134
|
+
const result = await this.emitToPath(targetPath, finalContent);
|
|
135
|
+
|
|
136
|
+
return { ...result, skillName };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 推断 globs 模式
|
|
141
|
+
*/
|
|
142
|
+
_inferGlobs(skillName, frontmatter) {
|
|
143
|
+
if (frontmatter.globs) {
|
|
144
|
+
return frontmatter.globs;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 根据技能名推断
|
|
148
|
+
if (skillName.includes('flow-')) {
|
|
149
|
+
return ['devflow/**/*', '.claude/**/*'];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return ['**/*'];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 编译 Agents 模块
|
|
157
|
+
* .claude/agents/[name].md -> .cursor/subagents/[name].md
|
|
158
|
+
*/
|
|
159
|
+
async emitAgents(sourceDir, targetDir) {
|
|
160
|
+
const results = [];
|
|
161
|
+
|
|
162
|
+
if (!fs.existsSync(sourceDir)) {
|
|
163
|
+
return results;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const entries = await fs.promises.readdir(sourceDir, { withFileTypes: true });
|
|
167
|
+
|
|
168
|
+
for (const entry of entries) {
|
|
169
|
+
if (!entry.isFile() || !entry.name.endsWith('.md')) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const filePath = path.join(sourceDir, entry.name);
|
|
174
|
+
const content = await fs.promises.readFile(filePath, 'utf8');
|
|
175
|
+
const parsed = matter(content);
|
|
176
|
+
const agentName = entry.name.replace('.md', '');
|
|
177
|
+
|
|
178
|
+
// 构建 Cursor subagent frontmatter
|
|
179
|
+
const frontmatterData = {
|
|
180
|
+
name: parsed.data.name || agentName,
|
|
181
|
+
description: parsed.data.description || `${agentName} agent`
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// 添加 tools 字段(如果有)
|
|
185
|
+
if (parsed.data.tools) {
|
|
186
|
+
frontmatterData.tools = parsed.data.tools;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const yamlStr = yaml.dump(frontmatterData, {
|
|
190
|
+
lineWidth: -1,
|
|
191
|
+
quotingType: '"',
|
|
192
|
+
forceQuotes: false
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const finalContent = `---\n${yamlStr}---\n\n${parsed.content}`;
|
|
196
|
+
|
|
197
|
+
const targetPath = path.join(targetDir, `${agentName}.md`);
|
|
198
|
+
const result = await this.emitToPath(targetPath, finalContent);
|
|
199
|
+
results.push({ ...result, agentName });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return results;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 编译 Rules 模块
|
|
207
|
+
* .claude/rules/[name].md -> .cursor/rules/[name].mdc
|
|
208
|
+
*/
|
|
209
|
+
async emitRules(sourceDir, targetDir) {
|
|
210
|
+
const results = [];
|
|
211
|
+
|
|
212
|
+
if (!fs.existsSync(sourceDir)) {
|
|
213
|
+
return results;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const entries = await fs.promises.readdir(sourceDir, { withFileTypes: true });
|
|
217
|
+
|
|
218
|
+
for (const entry of entries) {
|
|
219
|
+
if (!entry.isFile() || !entry.name.endsWith('.md')) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const filePath = path.join(sourceDir, entry.name);
|
|
224
|
+
const content = await fs.promises.readFile(filePath, 'utf8');
|
|
225
|
+
const parsed = matter(content);
|
|
226
|
+
const ruleName = entry.name.replace('.md', '');
|
|
227
|
+
|
|
228
|
+
// 构建 MDC frontmatter
|
|
229
|
+
const frontmatterData = {
|
|
230
|
+
description: parsed.data.description || `${ruleName} rule`,
|
|
231
|
+
alwaysApply: parsed.data.alwaysApply || true
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
if (parsed.data.globs) {
|
|
235
|
+
frontmatterData.globs = parsed.data.globs;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const yamlStr = yaml.dump(frontmatterData, {
|
|
239
|
+
lineWidth: -1,
|
|
240
|
+
quotingType: '"',
|
|
241
|
+
forceQuotes: false
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const finalContent = `---\n${yamlStr}---\n\n${parsed.content}`;
|
|
245
|
+
|
|
246
|
+
const targetPath = path.join(targetDir, `${ruleName}.mdc`);
|
|
247
|
+
const result = await this.emitToPath(targetPath, finalContent);
|
|
248
|
+
results.push({ ...result, ruleName });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return results;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 编译 Hooks 模块
|
|
256
|
+
* .claude/hooks/[name].ts -> .cursor/hooks.json + .cursor/hooks/[name].sh
|
|
257
|
+
*/
|
|
258
|
+
async emitHooks(sourceDir, targetDir) {
|
|
259
|
+
const results = [];
|
|
260
|
+
|
|
261
|
+
if (!fs.existsSync(sourceDir)) {
|
|
262
|
+
return results;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const entries = await fs.promises.readdir(sourceDir, { withFileTypes: true });
|
|
266
|
+
const hooksConfig = { version: 1, hooks: {} };
|
|
267
|
+
|
|
268
|
+
// Claude hook 事件 → Cursor hook 事件映射
|
|
269
|
+
const eventMap = {
|
|
270
|
+
'PreToolUse': 'preToolUse',
|
|
271
|
+
'PostToolUse': 'postToolUse',
|
|
272
|
+
'UserPromptSubmit': 'sessionStart'
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
for (const entry of entries) {
|
|
276
|
+
if (!entry.isFile()) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 跳过 TypeScript 类型定义文件
|
|
281
|
+
if (entry.name.endsWith('.d.ts')) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const filePath = path.join(sourceDir, entry.name);
|
|
286
|
+
const hookName = entry.name.replace(/\.(ts|js)$/, '');
|
|
287
|
+
|
|
288
|
+
// 尝试从文件名推断事件类型
|
|
289
|
+
let event = null;
|
|
290
|
+
for (const [claudeEvent, cursorEvent] of Object.entries(eventMap)) {
|
|
291
|
+
if (hookName.toLowerCase().includes(claudeEvent.toLowerCase())) {
|
|
292
|
+
event = cursorEvent;
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (!event) {
|
|
298
|
+
// 默认为 sessionStart
|
|
299
|
+
event = 'sessionStart';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 创建 shell wrapper
|
|
303
|
+
const shellScript = `#!/bin/bash
|
|
304
|
+
# Auto-generated from ${entry.name}
|
|
305
|
+
# Original hook: ${filePath}
|
|
306
|
+
|
|
307
|
+
# Note: TypeScript hooks need to be manually converted
|
|
308
|
+
echo "Hook ${hookName} triggered"
|
|
309
|
+
`;
|
|
310
|
+
|
|
311
|
+
const shellPath = path.join(targetDir, 'hooks', `${hookName}.sh`);
|
|
312
|
+
await this.emitToPath(shellPath, shellScript);
|
|
313
|
+
|
|
314
|
+
// 添加到 hooks.json
|
|
315
|
+
if (!hooksConfig.hooks[event]) {
|
|
316
|
+
hooksConfig.hooks[event] = [];
|
|
317
|
+
}
|
|
318
|
+
hooksConfig.hooks[event].push({
|
|
319
|
+
command: `./hooks/${hookName}.sh`
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// 写入 hooks.json
|
|
324
|
+
if (Object.keys(hooksConfig.hooks).length > 0) {
|
|
325
|
+
const configPath = path.join(targetDir, 'hooks.json');
|
|
326
|
+
const configContent = JSON.stringify(hooksConfig, null, 2);
|
|
327
|
+
const result = await this.emitToPath(configPath, configContent);
|
|
328
|
+
results.push(result);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return results;
|
|
332
|
+
}
|
|
29
333
|
}
|
|
30
334
|
|
|
31
335
|
module.exports = CursorEmitter;
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* T034: QwenEmitter - Qwen 平台输出
|
|
2
|
+
* T034: QwenEmitter - Qwen 平台输出 (v2.0)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* [INPUT]: CommandIR, SKILL.md, agents, rules
|
|
5
|
+
* [OUTPUT]: .qwen/commands/*.toml, .qwen/agents/*.md, CONTEXT.md
|
|
6
|
+
* [POS]: Qwen Code 平台编译器,支持完整功能模块
|
|
7
|
+
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
8
|
+
*
|
|
9
|
+
* 输出格式:
|
|
10
|
+
* - Commands: TOML -> .qwen/commands/
|
|
11
|
+
* - Skills: TOML -> .qwen/commands/
|
|
12
|
+
* - Agents: Markdown (YAML frontmatter) -> .qwen/agents/
|
|
13
|
+
* - Rules: 合并到 CONTEXT.md
|
|
14
|
+
*
|
|
15
|
+
* v2.0: 支持多模块编译
|
|
7
16
|
*/
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
8
19
|
const toml = require('@iarna/toml');
|
|
20
|
+
const matter = require('gray-matter');
|
|
9
21
|
const BaseEmitter = require('./base-emitter.js');
|
|
22
|
+
const { ContextExpander } = require('../context-expander.js');
|
|
10
23
|
|
|
11
24
|
class QwenEmitter extends BaseEmitter {
|
|
12
25
|
get name() {
|
|
@@ -34,6 +47,185 @@ class QwenEmitter extends BaseEmitter {
|
|
|
34
47
|
|
|
35
48
|
return toml.stringify(tomlObj);
|
|
36
49
|
}
|
|
50
|
+
|
|
51
|
+
// ----------------------------------------------------------
|
|
52
|
+
// Multi-Module Emit Methods (v2.0)
|
|
53
|
+
// ----------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 编译 Skills 模块
|
|
57
|
+
* .claude/skills/[name]/SKILL.md -> .qwen/commands/[name].toml
|
|
58
|
+
*/
|
|
59
|
+
async emitSkills(sourceDir, targetDir) {
|
|
60
|
+
const results = [];
|
|
61
|
+
|
|
62
|
+
if (!fs.existsSync(sourceDir)) {
|
|
63
|
+
return results;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 扫描技能分组目录
|
|
67
|
+
const groupDirs = await fs.promises.readdir(sourceDir, { withFileTypes: true });
|
|
68
|
+
|
|
69
|
+
for (const groupEntry of groupDirs) {
|
|
70
|
+
if (!groupEntry.isDirectory() || groupEntry.name.startsWith('_')) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const groupDir = path.join(sourceDir, groupEntry.name);
|
|
75
|
+
const skillEntries = await fs.promises.readdir(groupDir, { withFileTypes: true });
|
|
76
|
+
|
|
77
|
+
for (const skillEntry of skillEntries) {
|
|
78
|
+
if (!skillEntry.isDirectory() || skillEntry.name.startsWith('_')) {
|
|
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
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return results;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 将 Skill 转换为 Qwen TOML 命令
|
|
108
|
+
*/
|
|
109
|
+
async _emitSkillAsToml(skillName, skillDir, skillMdPath, targetDir) {
|
|
110
|
+
let content = await fs.promises.readFile(skillMdPath, 'utf8');
|
|
111
|
+
const parsed = matter(content);
|
|
112
|
+
|
|
113
|
+
// 展开 context.jsonl
|
|
114
|
+
const contextExpanded = ContextExpander.expandFromSkillDir(skillDir, 'qwen');
|
|
115
|
+
|
|
116
|
+
// 构建 TOML 对象
|
|
117
|
+
let prompt = parsed.content;
|
|
118
|
+
if (contextExpanded) {
|
|
119
|
+
prompt = contextExpanded + '\n' + prompt;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 替换变量占位符为 Qwen 格式
|
|
123
|
+
prompt = prompt.replace(/\$ARGUMENTS/g, '{{args}}');
|
|
124
|
+
|
|
125
|
+
const tomlObj = {
|
|
126
|
+
description: parsed.data.description || `${skillName} skill`,
|
|
127
|
+
prompt: prompt
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const tomlContent = toml.stringify(tomlObj);
|
|
131
|
+
|
|
132
|
+
// 输出到 .qwen/commands/<skill-name>.toml
|
|
133
|
+
const targetPath = path.join(targetDir, `${skillName}.toml`);
|
|
134
|
+
const result = await this.emitToPath(targetPath, tomlContent);
|
|
135
|
+
|
|
136
|
+
return { ...result, skillName };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 编译 Agents 模块
|
|
141
|
+
* .claude/agents/[name].md -> .qwen/agents/[name].md
|
|
142
|
+
*/
|
|
143
|
+
async emitAgents(sourceDir, targetDir) {
|
|
144
|
+
const results = [];
|
|
145
|
+
|
|
146
|
+
if (!fs.existsSync(sourceDir)) {
|
|
147
|
+
return results;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const entries = await fs.promises.readdir(sourceDir, { withFileTypes: true });
|
|
151
|
+
|
|
152
|
+
for (const entry of entries) {
|
|
153
|
+
if (!entry.isFile() || !entry.name.endsWith('.md')) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const filePath = path.join(sourceDir, entry.name);
|
|
158
|
+
const content = await fs.promises.readFile(filePath, 'utf8');
|
|
159
|
+
const parsed = matter(content);
|
|
160
|
+
const agentName = entry.name.replace('.md', '');
|
|
161
|
+
|
|
162
|
+
// Qwen agents 支持 ${variable} 模板
|
|
163
|
+
let body = parsed.content;
|
|
164
|
+
// 转换 $ARGUMENTS 为 ${args}
|
|
165
|
+
body = body.replace(/\$ARGUMENTS/g, '${args}');
|
|
166
|
+
|
|
167
|
+
// 重新构建带 frontmatter 的内容
|
|
168
|
+
const frontmatterData = {
|
|
169
|
+
name: parsed.data.name || agentName,
|
|
170
|
+
description: parsed.data.description || `${agentName} agent`
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
if (parsed.data.tools) {
|
|
174
|
+
frontmatterData.tools = parsed.data.tools;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const yaml = require('js-yaml');
|
|
178
|
+
const yamlStr = yaml.dump(frontmatterData, {
|
|
179
|
+
lineWidth: -1,
|
|
180
|
+
quotingType: '"',
|
|
181
|
+
forceQuotes: false
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const finalContent = `---\n${yamlStr}---\n\n${body}`;
|
|
185
|
+
|
|
186
|
+
const targetPath = path.join(targetDir, `${agentName}.md`);
|
|
187
|
+
const result = await this.emitToPath(targetPath, finalContent);
|
|
188
|
+
results.push({ ...result, agentName });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return results;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 编译 Rules 模块
|
|
196
|
+
* .claude/rules/[name].md -> CONTEXT.md (合并)
|
|
197
|
+
*/
|
|
198
|
+
async emitRules(sourceDir, targetPath) {
|
|
199
|
+
const results = [];
|
|
200
|
+
|
|
201
|
+
if (!fs.existsSync(sourceDir)) {
|
|
202
|
+
return results;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const entries = await fs.promises.readdir(sourceDir, { withFileTypes: true });
|
|
206
|
+
const sections = [];
|
|
207
|
+
|
|
208
|
+
for (const entry of entries) {
|
|
209
|
+
if (!entry.isFile() || !entry.name.endsWith('.md')) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const filePath = path.join(sourceDir, entry.name);
|
|
214
|
+
const content = await fs.promises.readFile(filePath, 'utf8');
|
|
215
|
+
const parsed = matter(content);
|
|
216
|
+
const ruleName = entry.name.replace('.md', '');
|
|
217
|
+
|
|
218
|
+
sections.push(`## ${ruleName}\n\n${parsed.content}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (sections.length > 0) {
|
|
222
|
+
const merged = `# Project Context\n\n${sections.join('\n\n---\n\n')}`;
|
|
223
|
+
const result = await this.emitToPath(targetPath, merged);
|
|
224
|
+
results.push(result);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return results;
|
|
228
|
+
}
|
|
37
229
|
}
|
|
38
230
|
|
|
39
231
|
module.exports = QwenEmitter;
|