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.
Files changed (192) hide show
  1. package/.claude/CLAUDE.md +1065 -53
  2. package/.claude/agents/dev-implementer.md +195 -0
  3. package/.claude/commands/{flow-archive.md → flow/archive.md} +46 -11
  4. package/.claude/commands/flow/context.md +150 -0
  5. package/.claude/commands/flow/delta.md +245 -0
  6. package/.claude/commands/{flow-dev.md → flow/dev.md} +112 -11
  7. package/.claude/commands/flow/init.md +45 -0
  8. package/.claude/commands/flow/new.md +279 -0
  9. package/.claude/commands/flow/quality.md +159 -0
  10. package/.claude/commands/flow/spec.md +186 -0
  11. package/.claude/commands/flow/workspace.md +146 -0
  12. package/.claude/commands/{cancel-ralph.md → util/cancel-ralph.md} +1 -0
  13. package/.claude/config/quality-gates.yml +305 -0
  14. package/.claude/docs/guides/TEAM_MODE_GUIDE.md +313 -0
  15. package/.claude/docs/templates/DELTA_SPEC_TEMPLATE.md +91 -0
  16. package/.claude/docs/templates/DESIGN_DECISIONS_TEMPLATE.md +151 -0
  17. package/.claude/docs/templates/JOURNAL_TEMPLATE.md +75 -0
  18. package/.claude/docs/templates/NEW_ORCHESTRATION_TEMPLATE.md +51 -91
  19. package/.claude/docs/templates/_shared/CLAUDE.md +36 -0
  20. package/.claude/docs/templates/_shared/CONSTITUTION_CHECK.md +125 -0
  21. package/.claude/docs/templates/_shared/VALIDATION_CHECKLIST.md +187 -0
  22. package/.claude/docs/templates/_shared/YAML_FRONTMATTER.md +164 -0
  23. package/.claude/docs/templates/context/dev.jsonl.template +6 -0
  24. package/.claude/docs/templates/context/epic.jsonl.template +5 -0
  25. package/.claude/docs/templates/context/prd.jsonl.template +4 -0
  26. package/.claude/docs/templates/context/research.jsonl.template +4 -0
  27. package/.claude/docs/templates/context/review.jsonl.template +5 -0
  28. package/.claude/docs/templates/context/tech.jsonl.template +5 -0
  29. package/.claude/hooks/CLAUDE.md +342 -0
  30. package/.claude/hooks/inject-agent-context.ts +480 -0
  31. package/.claude/hooks/inject-skill-context.ts +359 -0
  32. package/.claude/hooks/ralph-loop.ts +931 -0
  33. package/.claude/hooks/task-completed-hook.ts +593 -0
  34. package/.claude/hooks/teammate-idle-hook.ts +690 -0
  35. package/.claude/hooks/types/team-types.d.ts +238 -0
  36. package/.claude/rules/devflow-conventions.md +82 -9
  37. package/.claude/scripts/archive-requirement.sh +44 -1
  38. package/.claude/scripts/common.sh +670 -3
  39. package/.claude/scripts/delta-parser.ts +527 -0
  40. package/.claude/scripts/detect-file-conflicts.sh +151 -0
  41. package/.claude/scripts/flow-context-add.sh +134 -0
  42. package/.claude/scripts/flow-context-init.sh +133 -0
  43. package/.claude/scripts/flow-context-validate.sh +144 -0
  44. package/.claude/scripts/flow-delta-apply.sh +297 -0
  45. package/.claude/scripts/flow-delta-archive.sh +71 -0
  46. package/.claude/scripts/flow-delta-create.sh +202 -0
  47. package/.claude/scripts/flow-delta-list.sh +142 -0
  48. package/.claude/scripts/flow-delta-status.sh +235 -0
  49. package/.claude/scripts/flow-quality-full.sh +184 -0
  50. package/.claude/scripts/flow-quality-quick.sh +64 -0
  51. package/.claude/scripts/flow-workspace-init.sh +117 -0
  52. package/.claude/scripts/flow-workspace-record.sh +164 -0
  53. package/.claude/scripts/flow-workspace-start.sh +88 -0
  54. package/.claude/scripts/get-workflow-status.sh +415 -0
  55. package/.claude/scripts/parse-task-dependencies.js +334 -0
  56. package/.claude/scripts/record-quality-error.sh +165 -0
  57. package/.claude/scripts/run-quality-gates.sh +242 -0
  58. package/.claude/scripts/team-dev-init.sh +319 -0
  59. package/.claude/scripts/team-state-recovery.sh +229 -0
  60. package/.claude/scripts/workflow-status.ts +433 -0
  61. package/.claude/settings.json +19 -0
  62. package/.claude/skills/cc-devflow-orchestrator/SKILL.md +85 -200
  63. package/.claude/skills/domain/using-git-worktrees/SKILL.md +252 -0
  64. package/.claude/skills/domain/using-git-worktrees/assets/SHELL_ALIASES.md +133 -0
  65. package/.claude/skills/domain/using-git-worktrees/context.jsonl +4 -0
  66. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-cleanup.sh +218 -0
  67. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-create.sh +232 -0
  68. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-list.sh +130 -0
  69. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-status.sh +140 -0
  70. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-switch.sh +70 -0
  71. package/.claude/skills/utility/skill-creator/LICENSE.txt +202 -0
  72. package/.claude/skills/utility/skill-creator/SKILL.md +356 -0
  73. package/.claude/skills/utility/skill-creator/references/output-patterns.md +82 -0
  74. package/.claude/skills/utility/skill-creator/references/workflows.md +28 -0
  75. package/.claude/skills/utility/skill-creator/scripts/init_skill.py +303 -0
  76. package/.claude/skills/utility/skill-creator/scripts/package_skill.py +110 -0
  77. package/.claude/skills/utility/skill-creator/scripts/quick_validate.py +95 -0
  78. package/.claude/skills/workflow/flow-dev/CLAUDE.md +78 -0
  79. package/.claude/skills/workflow/flow-dev/SKILL.md +96 -0
  80. package/.claude/skills/workflow/flow-dev/assets/IMPLEMENTATION_PLAN_TEMPLATE.md +71 -0
  81. package/.claude/skills/workflow/flow-dev/context.jsonl +8 -0
  82. package/.claude/skills/workflow/flow-dev/dev-implementer.jsonl +8 -0
  83. package/.claude/skills/workflow/flow-dev/scripts/entry-gate.sh +116 -0
  84. package/.claude/skills/workflow/flow-dev/scripts/exit-gate.sh +101 -0
  85. package/.claude/skills/workflow/flow-dev/scripts/task-orchestrator.sh +106 -0
  86. package/.claude/skills/workflow/flow-fix/SKILL.md +105 -0
  87. package/.claude/skills/workflow/flow-fix/context.jsonl +6 -0
  88. package/.claude/skills/workflow/flow-fix/references/bug-analyzer.md +381 -0
  89. package/.claude/skills/workflow/flow-init/SKILL.md +211 -0
  90. package/.claude/skills/workflow/flow-init/assets/BRAINSTORM_TEMPLATE.md +148 -0
  91. package/.claude/skills/workflow/flow-init/assets/INIT_FLOW_TEMPLATE.md +198 -0
  92. package/.claude/skills/workflow/flow-init/assets/RESEARCH_TEMPLATE.md +276 -0
  93. package/.claude/skills/workflow/flow-init/context.jsonl +5 -0
  94. package/.claude/skills/workflow/flow-init/references/flow-researcher.md +132 -0
  95. package/.claude/skills/workflow/flow-init/scripts/check-prerequisites.sh +240 -0
  96. package/.claude/skills/workflow/flow-init/scripts/consolidate-research.sh +182 -0
  97. package/.claude/skills/workflow/flow-init/scripts/create-requirement.sh +523 -0
  98. package/.claude/skills/workflow/flow-init/scripts/generate-research-tasks.sh +157 -0
  99. package/.claude/skills/workflow/flow-init/scripts/populate-research-tasks.sh +284 -0
  100. package/.claude/skills/workflow/flow-init/scripts/validate-research.sh +340 -0
  101. package/.claude/skills/workflow/flow-quality/SKILL.md +94 -0
  102. package/.claude/skills/workflow/flow-quality/context.jsonl +6 -0
  103. package/.claude/skills/workflow/flow-quality/references/code-quality-reviewer.md +205 -0
  104. package/.claude/skills/workflow/flow-quality/references/qa-tester.md +313 -0
  105. package/.claude/skills/workflow/flow-quality/references/security-reviewer.md +314 -0
  106. package/.claude/skills/workflow/flow-quality/references/spec-reviewer.md +221 -0
  107. package/.claude/skills/workflow/flow-release/SKILL.md +126 -0
  108. package/.claude/skills/workflow/flow-release/context.jsonl +7 -0
  109. package/.claude/skills/workflow/flow-release/references/release-manager.md +295 -0
  110. package/.claude/skills/workflow/flow-spec/CLAUDE.md +103 -0
  111. package/.claude/skills/workflow/flow-spec/SKILL.md +545 -0
  112. package/.claude/skills/workflow/flow-spec/context.jsonl +7 -0
  113. package/.claude/skills/workflow/flow-spec/scripts/entry-gate.sh +194 -0
  114. package/.claude/skills/workflow/flow-spec/scripts/exit-gate.sh +244 -0
  115. package/.claude/skills/workflow/flow-spec/scripts/parallel-orchestrator.sh +205 -0
  116. package/.claude/skills/workflow/flow-spec/scripts/team-communication.sh +353 -0
  117. package/.claude/skills/workflow/flow-spec/scripts/team-init.sh +195 -0
  118. package/.claude/skills/workflow/flow-spec/scripts/test-team-mode.sh +496 -0
  119. package/.claude/skills/workflow/flow-spec/team-config.json +165 -0
  120. package/.claude/skills/workflow.yaml +417 -0
  121. package/CHANGELOG.md +268 -0
  122. package/README.md +206 -50
  123. package/README.zh-CN.md +219 -57
  124. package/lib/compiler/CLAUDE.md +77 -46
  125. package/lib/compiler/__tests__/multi-module-emitters.test.js +508 -0
  126. package/lib/compiler/context-expander.js +179 -0
  127. package/lib/compiler/emitters/antigravity-emitter.js +195 -5
  128. package/lib/compiler/emitters/base-emitter.js +217 -2
  129. package/lib/compiler/emitters/codex-emitter.js +200 -4
  130. package/lib/compiler/emitters/cursor-emitter.js +307 -3
  131. package/lib/compiler/emitters/qwen-emitter.js +196 -4
  132. package/lib/compiler/index.js +197 -2
  133. package/lib/compiler/platforms.js +270 -21
  134. package/package.json +2 -2
  135. package/.claude/commands/flow-epic.md +0 -183
  136. package/.claude/commands/flow-init.md +0 -370
  137. package/.claude/commands/flow-new.md +0 -442
  138. package/.claude/commands/flow-prd.md +0 -144
  139. package/.claude/commands/flow-qa.md +0 -93
  140. package/.claude/commands/flow-review.md +0 -257
  141. package/.claude/commands/flow-tech.md +0 -142
  142. package/.claude/commands/flow-ui.md +0 -189
  143. package/.claude/skills/file-header-guardian/SKILL.md +0 -56
  144. package/.claude/skills/skill-developer/ADVANCED.md +0 -197
  145. package/.claude/skills/skill-developer/HOOK_MECHANISMS.md +0 -306
  146. package/.claude/skills/skill-developer/PATTERNS_LIBRARY.md +0 -152
  147. package/.claude/skills/skill-developer/SKILL.md +0 -426
  148. package/.claude/skills/skill-developer/SKILL_RULES_REFERENCE.md +0 -315
  149. package/.claude/skills/skill-developer/TRIGGER_TYPES.md +0 -305
  150. package/.claude/skills/skill-developer/TROUBLESHOOTING.md +0 -514
  151. package/.claude/skills/writing-skills/SKILL.md +0 -655
  152. package/.claude/skills/writing-skills/anthropic-best-practices.md +0 -1150
  153. package/.claude/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +0 -189
  154. package/.claude/skills/writing-skills/graphviz-conventions.dot +0 -172
  155. package/.claude/skills/writing-skills/persuasion-principles.md +0 -187
  156. package/.claude/skills/writing-skills/render-graphs.js +0 -168
  157. package/.claude/skills/writing-skills/testing-skills-with-subagents.md +0 -384
  158. package/.claude/tsc-cache/795ba6e3-b98a-423b-bab2-51aa62812569/affected-repos.txt +0 -1
  159. package/.claude/tsc-cache/ae335694-be5a-4ba4-a1a0-b676c09a7906/affected-repos.txt +0 -1
  160. /package/.claude/commands/{core-architecture.md → core/architecture.md} +0 -0
  161. /package/.claude/commands/{core-guidelines.md → core/guidelines.md} +0 -0
  162. /package/.claude/commands/{core-roadmap.md → core/roadmap.md} +0 -0
  163. /package/.claude/commands/{core-style.md → core/style.md} +0 -0
  164. /package/.claude/commands/{flow-checklist.md → flow/checklist.md} +0 -0
  165. /package/.claude/commands/{flow-clarify.md → flow/clarify.md} +0 -0
  166. /package/.claude/commands/{flow-constitution.md → flow/constitution.md} +0 -0
  167. /package/.claude/commands/{flow-fix.md → flow/fix.md} +0 -0
  168. /package/.claude/commands/{flow-ideate.md → flow/ideate.md} +0 -0
  169. /package/.claude/commands/{flow-release.md → flow/release.md} +0 -0
  170. /package/.claude/commands/{flow-restart.md → flow/restart.md} +0 -0
  171. /package/.claude/commands/{flow-status.md → flow/status.md} +0 -0
  172. /package/.claude/commands/{flow-update.md → flow/update.md} +0 -0
  173. /package/.claude/commands/{flow-upgrade.md → flow/upgrade.md} +0 -0
  174. /package/.claude/commands/{flow-verify.md → flow/verify.md} +0 -0
  175. /package/.claude/commands/{code-review-high.md → util/code-review.md} +0 -0
  176. /package/.claude/commands/{git-commit.md → util/git-commit.md} +0 -0
  177. /package/.claude/commands/{problem-analyzer.md → util/problem-analyzer.md} +0 -0
  178. /package/.claude/skills/{flow-attention-refresh → domain/attention-refresh}/SKILL.md +0 -0
  179. /package/.claude/skills/{flow-brainstorming → domain/brainstorming}/SKILL.md +0 -0
  180. /package/.claude/skills/{flow-debugging → domain/debugging}/SKILL.md +0 -0
  181. /package/.claude/skills/{flow-finishing-branch → domain/finishing-branch}/SKILL.md +0 -0
  182. /package/.claude/skills/{flow-receiving-review → domain/receiving-review}/SKILL.md +0 -0
  183. /package/.claude/skills/{flow-tdd → domain/tdd}/SKILL.md +0 -0
  184. /package/.claude/skills/{verification-before-completion → domain/verification}/SKILL.md +0 -0
  185. /package/.claude/skills/{constitution-guardian → guardrail/constitution-guardian}/SKILL.md +0 -0
  186. /package/.claude/skills/{devflow-tdd-enforcer → guardrail/tdd-enforcer}/SKILL.md +0 -0
  187. /package/.claude/skills/{devflow-constitution-quick-ref → utility/constitution-quick-ref}/SKILL.md +0 -0
  188. /package/.claude/skills/{devflow-file-standards → utility/file-standards}/SKILL.md +0 -0
  189. /package/.claude/skills/{fractal-docs-generator → utility/fractal-docs}/SKILL.md +0 -0
  190. /package/.claude/skills/{journey-coherence-checker → utility/journey-checker}/SKILL.md +0 -0
  191. /package/.claude/skills/{journey-coherence-checker → utility/journey-checker}/pressure-scenarios.md +0 -0
  192. /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
- * 输出格式: Markdown (无 frontmatter)
5
- * 目录: .cursor/commands/
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
- * 输出格式: TOML
5
- * 目录: .qwen/commands/
6
- * 字段: description, prompt
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;