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
@@ -0,0 +1,508 @@
1
+ /**
2
+ * Multi-Module Emitters Test Suite
3
+ *
4
+ * [INPUT]: 测试 fixtures
5
+ * [OUTPUT]: 测试结果
6
+ * [POS]: 编译器测试套件,验证多模块编译功能
7
+ * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
8
+ *
9
+ * 测试覆盖:
10
+ * - context-expander.js
11
+ * - 各平台 Emitter 的多模块方法
12
+ * - compileMultiModule 集成测试
13
+ */
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const os = require('os');
17
+
18
+ const { ContextExpander } = require('../context-expander.js');
19
+ const CodexEmitter = require('../emitters/codex-emitter.js');
20
+ const CursorEmitter = require('../emitters/cursor-emitter.js');
21
+ const QwenEmitter = require('../emitters/qwen-emitter.js');
22
+ const AntigravityEmitter = require('../emitters/antigravity-emitter.js');
23
+ const { compileMultiModule, PLATFORMS, DEFAULT_MODULES } = require('../index.js');
24
+
25
+ // ============================================================
26
+ // Test Fixtures
27
+ // ============================================================
28
+ const FIXTURES_DIR = path.join(__dirname, 'fixtures', 'multi-module');
29
+
30
+ // 创建临时目录
31
+ function createTempDir() {
32
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'cc-devflow-test-'));
33
+ }
34
+
35
+ // 清理临时目录
36
+ function cleanupTempDir(dir) {
37
+ if (fs.existsSync(dir)) {
38
+ fs.rmSync(dir, { recursive: true, force: true });
39
+ }
40
+ }
41
+
42
+ // 创建测试 fixtures
43
+ function setupFixtures(tempDir) {
44
+ // 创建 skills 目录结构
45
+ const skillsDir = path.join(tempDir, '.claude', 'skills', 'workflow', 'test-skill');
46
+ fs.mkdirSync(skillsDir, { recursive: true });
47
+
48
+ // 创建 SKILL.md
49
+ fs.writeFileSync(path.join(skillsDir, 'SKILL.md'), `---
50
+ name: test-skill
51
+ description: A test skill for unit testing
52
+ ---
53
+
54
+ # Test Skill
55
+
56
+ This is a test skill.
57
+
58
+ ## Usage
59
+
60
+ Use this skill for testing.
61
+ `);
62
+
63
+ // 创建 context.jsonl
64
+ fs.writeFileSync(path.join(skillsDir, 'context.jsonl'), `{"file": "devflow/requirements/REQ-001/PRD.md", "reason": "Product requirements"}
65
+ {"file": "devflow/spec/frontend/index.md", "reason": "Frontend conventions", "optional": true}
66
+ `);
67
+
68
+ // 创建 agents 目录
69
+ const agentsDir = path.join(tempDir, '.claude', 'agents');
70
+ fs.mkdirSync(agentsDir, { recursive: true });
71
+
72
+ fs.writeFileSync(path.join(agentsDir, 'test-agent.md'), `---
73
+ name: test-agent
74
+ description: A test agent
75
+ tools: Read, Write
76
+ ---
77
+
78
+ # Test Agent
79
+
80
+ This agent is for testing.
81
+ `);
82
+
83
+ // 创建 rules 目录
84
+ const rulesDir = path.join(tempDir, '.claude', 'rules');
85
+ fs.mkdirSync(rulesDir, { recursive: true });
86
+
87
+ fs.writeFileSync(path.join(rulesDir, 'test-rule.md'), `---
88
+ description: A test rule
89
+ alwaysApply: true
90
+ ---
91
+
92
+ # Test Rule
93
+
94
+ This rule is for testing.
95
+ `);
96
+
97
+ // 创建 hooks 目录
98
+ const hooksDir = path.join(tempDir, '.claude', 'hooks');
99
+ fs.mkdirSync(hooksDir, { recursive: true });
100
+
101
+ fs.writeFileSync(path.join(hooksDir, 'preToolUse-test.ts'), `// Test hook
102
+ export default function preToolUseTest() {
103
+ console.log('Hook triggered');
104
+ }
105
+ `);
106
+
107
+ return tempDir;
108
+ }
109
+
110
+ // ============================================================
111
+ // ContextExpander Tests
112
+ // ============================================================
113
+ describe('ContextExpander', () => {
114
+ describe('parseJsonl', () => {
115
+ test('parses valid JSONL content', () => {
116
+ const jsonl = `{"file": "a.md", "reason": "test"}
117
+ {"file": "b.md", "reason": "test2", "optional": true}`;
118
+
119
+ const result = ContextExpander.parseJsonl(jsonl);
120
+
121
+ expect(result).toHaveLength(2);
122
+ expect(result[0]).toEqual({ file: 'a.md', reason: 'test', optional: false });
123
+ expect(result[1]).toEqual({ file: 'b.md', reason: 'test2', optional: true });
124
+ });
125
+
126
+ test('handles empty content', () => {
127
+ expect(ContextExpander.parseJsonl('')).toEqual([]);
128
+ expect(ContextExpander.parseJsonl(' ')).toEqual([]);
129
+ });
130
+
131
+ test('skips invalid lines', () => {
132
+ const jsonl = `{"file": "a.md", "reason": "test"}
133
+ invalid json
134
+ {"file": "b.md", "reason": "test2"}`;
135
+
136
+ const result = ContextExpander.parseJsonl(jsonl);
137
+ expect(result).toHaveLength(2);
138
+ });
139
+ });
140
+
141
+ describe('expand', () => {
142
+ const contexts = [
143
+ { file: 'a.md', reason: 'test', optional: false },
144
+ { file: 'b.md', reason: 'test2', optional: true }
145
+ ];
146
+
147
+ test('expands for Cursor with @file references', () => {
148
+ const result = ContextExpander.expand(contexts, 'cursor');
149
+
150
+ expect(result).toContain('@a.md');
151
+ expect(result).toContain('@b.md (optional)');
152
+ expect(result).toContain('## Context Files');
153
+ });
154
+
155
+ test('expands for Codex with Required Context section', () => {
156
+ const result = ContextExpander.expand(contexts, 'codex');
157
+
158
+ expect(result).toContain('## Required Context');
159
+ expect(result).toContain('`a.md`');
160
+ expect(result).toContain('`b.md`');
161
+ });
162
+
163
+ test('expands for Qwen with Required Context section', () => {
164
+ const result = ContextExpander.expand(contexts, 'qwen');
165
+
166
+ expect(result).toContain('## Required Context');
167
+ });
168
+
169
+ test('expands for Antigravity with Required Context section', () => {
170
+ const result = ContextExpander.expand(contexts, 'antigravity');
171
+
172
+ expect(result).toContain('## Required Context');
173
+ });
174
+
175
+ test('returns empty string for empty contexts', () => {
176
+ expect(ContextExpander.expand([], 'cursor')).toBe('');
177
+ expect(ContextExpander.expand(null, 'cursor')).toBe('');
178
+ });
179
+ });
180
+ });
181
+
182
+ // ============================================================
183
+ // CodexEmitter Multi-Module Tests
184
+ // ============================================================
185
+ describe('CodexEmitter Multi-Module', () => {
186
+ let tempDir;
187
+ let emitter;
188
+
189
+ beforeEach(() => {
190
+ tempDir = createTempDir();
191
+ setupFixtures(tempDir);
192
+ emitter = new CodexEmitter();
193
+ });
194
+
195
+ afterEach(() => {
196
+ cleanupTempDir(tempDir);
197
+ });
198
+
199
+ test('emitSkills creates SKILL.md in target directory', async () => {
200
+ const sourceDir = path.join(tempDir, '.claude', 'skills');
201
+ const targetDir = path.join(tempDir, '.codex', 'skills');
202
+
203
+ const results = await emitter.emitSkills(sourceDir, targetDir);
204
+
205
+ expect(results.length).toBeGreaterThan(0);
206
+ expect(results[0].skillName).toBe('test-skill');
207
+
208
+ const targetPath = path.join(targetDir, 'test-skill', 'SKILL.md');
209
+ expect(fs.existsSync(targetPath)).toBe(true);
210
+
211
+ const content = fs.readFileSync(targetPath, 'utf8');
212
+ expect(content).toContain('name: test-skill');
213
+ expect(content).toContain('## Required Context');
214
+ });
215
+
216
+ test('emitAgents merges to AGENTS.md', async () => {
217
+ const sourceDir = path.join(tempDir, '.claude', 'agents');
218
+ const targetPath = path.join(tempDir, 'AGENTS.md');
219
+
220
+ const results = await emitter.emitAgents(sourceDir, targetPath);
221
+
222
+ expect(results.length).toBe(1);
223
+ expect(fs.existsSync(targetPath)).toBe(true);
224
+
225
+ const content = fs.readFileSync(targetPath, 'utf8');
226
+ expect(content).toContain('# Agents');
227
+ expect(content).toContain('## test-agent');
228
+ });
229
+
230
+ test('emitRules appends to AGENTS.md', async () => {
231
+ const sourceDir = path.join(tempDir, '.claude', 'rules');
232
+ const targetPath = path.join(tempDir, 'AGENTS.md');
233
+
234
+ // 先创建 AGENTS.md
235
+ fs.writeFileSync(targetPath, '# Agents\n\nExisting content.\n');
236
+
237
+ const results = await emitter.emitRules(sourceDir, targetPath);
238
+
239
+ expect(results.length).toBe(1);
240
+
241
+ const content = fs.readFileSync(targetPath, 'utf8');
242
+ expect(content).toContain('# Agents');
243
+ expect(content).toContain('## Rules');
244
+ expect(content).toContain('### test-rule');
245
+ });
246
+ });
247
+
248
+ // ============================================================
249
+ // CursorEmitter Multi-Module Tests
250
+ // ============================================================
251
+ describe('CursorEmitter Multi-Module', () => {
252
+ let tempDir;
253
+ let emitter;
254
+
255
+ beforeEach(() => {
256
+ tempDir = createTempDir();
257
+ setupFixtures(tempDir);
258
+ emitter = new CursorEmitter();
259
+ });
260
+
261
+ afterEach(() => {
262
+ cleanupTempDir(tempDir);
263
+ });
264
+
265
+ test('emitSkills creates .mdc files in rules directory', async () => {
266
+ const sourceDir = path.join(tempDir, '.claude', 'skills');
267
+ const targetDir = path.join(tempDir, '.cursor', 'rules');
268
+
269
+ const results = await emitter.emitSkills(sourceDir, targetDir);
270
+
271
+ expect(results.length).toBeGreaterThan(0);
272
+
273
+ const targetPath = path.join(targetDir, 'test-skill.mdc');
274
+ expect(fs.existsSync(targetPath)).toBe(true);
275
+
276
+ const content = fs.readFileSync(targetPath, 'utf8');
277
+ expect(content).toContain('description:');
278
+ expect(content).toContain('globs:');
279
+ expect(content).toContain('@devflow/requirements/REQ-001/PRD.md');
280
+ });
281
+
282
+ test('emitAgents creates subagent files', async () => {
283
+ const sourceDir = path.join(tempDir, '.claude', 'agents');
284
+ const targetDir = path.join(tempDir, '.cursor', 'subagents');
285
+
286
+ const results = await emitter.emitAgents(sourceDir, targetDir);
287
+
288
+ expect(results.length).toBe(1);
289
+
290
+ const targetPath = path.join(targetDir, 'test-agent.md');
291
+ expect(fs.existsSync(targetPath)).toBe(true);
292
+
293
+ const content = fs.readFileSync(targetPath, 'utf8');
294
+ expect(content).toContain('name: test-agent');
295
+ expect(content).toContain('description:');
296
+ });
297
+
298
+ test('emitRules creates .mdc files', async () => {
299
+ const sourceDir = path.join(tempDir, '.claude', 'rules');
300
+ const targetDir = path.join(tempDir, '.cursor', 'rules');
301
+
302
+ const results = await emitter.emitRules(sourceDir, targetDir);
303
+
304
+ expect(results.length).toBe(1);
305
+
306
+ const targetPath = path.join(targetDir, 'test-rule.mdc');
307
+ expect(fs.existsSync(targetPath)).toBe(true);
308
+
309
+ const content = fs.readFileSync(targetPath, 'utf8');
310
+ expect(content).toContain('description:');
311
+ expect(content).toContain('alwaysApply:');
312
+ });
313
+
314
+ test('emitHooks creates hooks.json and shell scripts', async () => {
315
+ const sourceDir = path.join(tempDir, '.claude', 'hooks');
316
+ const targetDir = path.join(tempDir, '.cursor');
317
+
318
+ const results = await emitter.emitHooks(sourceDir, targetDir);
319
+
320
+ expect(results.length).toBeGreaterThan(0);
321
+
322
+ const configPath = path.join(targetDir, 'hooks.json');
323
+ expect(fs.existsSync(configPath)).toBe(true);
324
+
325
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
326
+ expect(config.version).toBe(1);
327
+ expect(config.hooks).toBeDefined();
328
+ });
329
+ });
330
+
331
+ // ============================================================
332
+ // QwenEmitter Multi-Module Tests
333
+ // ============================================================
334
+ describe('QwenEmitter Multi-Module', () => {
335
+ let tempDir;
336
+ let emitter;
337
+
338
+ beforeEach(() => {
339
+ tempDir = createTempDir();
340
+ setupFixtures(tempDir);
341
+ emitter = new QwenEmitter();
342
+ });
343
+
344
+ afterEach(() => {
345
+ cleanupTempDir(tempDir);
346
+ });
347
+
348
+ test('emitSkills creates .toml files', async () => {
349
+ const sourceDir = path.join(tempDir, '.claude', 'skills');
350
+ const targetDir = path.join(tempDir, '.qwen', 'commands');
351
+
352
+ const results = await emitter.emitSkills(sourceDir, targetDir);
353
+
354
+ expect(results.length).toBeGreaterThan(0);
355
+
356
+ const targetPath = path.join(targetDir, 'test-skill.toml');
357
+ expect(fs.existsSync(targetPath)).toBe(true);
358
+
359
+ const content = fs.readFileSync(targetPath, 'utf8');
360
+ expect(content).toContain('description =');
361
+ expect(content).toContain('prompt =');
362
+ });
363
+
364
+ test('emitAgents creates agent files', async () => {
365
+ const sourceDir = path.join(tempDir, '.claude', 'agents');
366
+ const targetDir = path.join(tempDir, '.qwen', 'agents');
367
+
368
+ const results = await emitter.emitAgents(sourceDir, targetDir);
369
+
370
+ expect(results.length).toBe(1);
371
+
372
+ const targetPath = path.join(targetDir, 'test-agent.md');
373
+ expect(fs.existsSync(targetPath)).toBe(true);
374
+ });
375
+
376
+ test('emitRules creates CONTEXT.md', async () => {
377
+ const sourceDir = path.join(tempDir, '.claude', 'rules');
378
+ const targetPath = path.join(tempDir, 'CONTEXT.md');
379
+
380
+ const results = await emitter.emitRules(sourceDir, targetPath);
381
+
382
+ expect(results.length).toBe(1);
383
+ expect(fs.existsSync(targetPath)).toBe(true);
384
+
385
+ const content = fs.readFileSync(targetPath, 'utf8');
386
+ expect(content).toContain('# Project Context');
387
+ expect(content).toContain('## test-rule');
388
+ });
389
+ });
390
+
391
+ // ============================================================
392
+ // AntigravityEmitter Multi-Module Tests
393
+ // ============================================================
394
+ describe('AntigravityEmitter Multi-Module', () => {
395
+ let tempDir;
396
+ let emitter;
397
+
398
+ beforeEach(() => {
399
+ tempDir = createTempDir();
400
+ setupFixtures(tempDir);
401
+ emitter = new AntigravityEmitter();
402
+ });
403
+
404
+ afterEach(() => {
405
+ cleanupTempDir(tempDir);
406
+ });
407
+
408
+ test('emitSkills creates SKILL.md in skills directory', async () => {
409
+ const sourceDir = path.join(tempDir, '.claude', 'skills');
410
+ const targetDir = path.join(tempDir, '.agent', 'skills');
411
+
412
+ const results = await emitter.emitSkills(sourceDir, targetDir);
413
+
414
+ expect(results.length).toBeGreaterThan(0);
415
+
416
+ const targetPath = path.join(targetDir, 'test-skill', 'SKILL.md');
417
+ expect(fs.existsSync(targetPath)).toBe(true);
418
+ });
419
+
420
+ test('emitRules creates rule files', async () => {
421
+ const sourceDir = path.join(tempDir, '.claude', 'rules');
422
+ const targetDir = path.join(tempDir, '.agent', 'rules');
423
+
424
+ const results = await emitter.emitRules(sourceDir, targetDir);
425
+
426
+ expect(results.length).toBe(1);
427
+
428
+ const targetPath = path.join(targetDir, 'test-rule.md');
429
+ expect(fs.existsSync(targetPath)).toBe(true);
430
+ });
431
+ });
432
+
433
+ // ============================================================
434
+ // compileMultiModule Integration Tests
435
+ // ============================================================
436
+ describe('compileMultiModule Integration', () => {
437
+ let tempDir;
438
+
439
+ beforeEach(() => {
440
+ tempDir = createTempDir();
441
+ setupFixtures(tempDir);
442
+
443
+ // 创建 devflow/.generated 目录
444
+ fs.mkdirSync(path.join(tempDir, 'devflow', '.generated'), { recursive: true });
445
+ });
446
+
447
+ afterEach(() => {
448
+ cleanupTempDir(tempDir);
449
+ });
450
+
451
+ test('compiles all modules for all platforms', async () => {
452
+ const result = await compileMultiModule({
453
+ sourceBaseDir: path.join(tempDir, '.claude'),
454
+ outputBaseDir: tempDir,
455
+ platforms: ['codex', 'cursor'],
456
+ modules: ['skills', 'agents', 'rules'],
457
+ verbose: false
458
+ });
459
+
460
+ expect(result.success).toBe(true);
461
+ expect(result.skillsEmitted).toBeGreaterThan(0);
462
+ expect(result.agentsEmitted).toBeGreaterThan(0);
463
+ expect(result.rulesEmitted).toBeGreaterThan(0);
464
+ });
465
+
466
+ test('compiles only specified modules', async () => {
467
+ const result = await compileMultiModule({
468
+ sourceBaseDir: path.join(tempDir, '.claude'),
469
+ outputBaseDir: tempDir,
470
+ platforms: ['codex'],
471
+ modules: ['skills'],
472
+ verbose: false
473
+ });
474
+
475
+ expect(result.success).toBe(true);
476
+ expect(result.skillsEmitted).toBeGreaterThan(0);
477
+ expect(result.agentsEmitted).toBe(0);
478
+ expect(result.rulesEmitted).toBe(0);
479
+ });
480
+
481
+ test('handles missing source directories gracefully', async () => {
482
+ const emptyDir = createTempDir();
483
+
484
+ try {
485
+ const result = await compileMultiModule({
486
+ sourceBaseDir: path.join(emptyDir, '.claude'),
487
+ outputBaseDir: emptyDir,
488
+ platforms: ['codex'],
489
+ modules: ['skills', 'agents', 'rules'],
490
+ verbose: false
491
+ });
492
+
493
+ expect(result.success).toBe(true);
494
+ expect(result.skillsEmitted).toBe(0);
495
+ expect(result.agentsEmitted).toBe(0);
496
+ expect(result.rulesEmitted).toBe(0);
497
+ } finally {
498
+ cleanupTempDir(emptyDir);
499
+ }
500
+ });
501
+
502
+ test('exports DEFAULT_MODULES', () => {
503
+ expect(DEFAULT_MODULES).toContain('skills');
504
+ expect(DEFAULT_MODULES).toContain('commands');
505
+ expect(DEFAULT_MODULES).toContain('agents');
506
+ expect(DEFAULT_MODULES).toContain('rules');
507
+ });
508
+ });
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Context Expander - context.jsonl 编译时展开
3
+ *
4
+ * [INPUT]: context.jsonl 文件内容, 目标平台
5
+ * [OUTPUT]: 展开后的上下文引用字符串
6
+ * [POS]: 编译器核心模块,将 context.jsonl 转换为平台特定格式
7
+ * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
8
+ *
9
+ * 功能:
10
+ * - 解析 JSONL 格式的上下文定义
11
+ * - 根据平台生成不同格式的上下文引用
12
+ * - Cursor 使用 @file 引用
13
+ * - 其他平台生成 "Required Context" 章节
14
+ */
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ // ============================================================
19
+ // ContextExpander - 上下文展开器
20
+ // ============================================================
21
+ class ContextExpander {
22
+ /**
23
+ * 解析 context.jsonl 文件
24
+ * @param {string} jsonlPath - context.jsonl 文件路径
25
+ * @returns {Array<Object>} 上下文条目数组
26
+ */
27
+ static parseContextFile(jsonlPath) {
28
+ if (!fs.existsSync(jsonlPath)) {
29
+ return [];
30
+ }
31
+
32
+ const content = fs.readFileSync(jsonlPath, 'utf8');
33
+ return ContextExpander.parseJsonl(content);
34
+ }
35
+
36
+ /**
37
+ * 解析 JSONL 字符串
38
+ * @param {string} jsonlContent - JSONL 内容
39
+ * @returns {Array<Object>} 上下文条目数组
40
+ */
41
+ static parseJsonl(jsonlContent) {
42
+ if (!jsonlContent || !jsonlContent.trim()) {
43
+ return [];
44
+ }
45
+
46
+ const lines = jsonlContent.split('\n').filter(line => line.trim());
47
+ const contexts = [];
48
+
49
+ for (const line of lines) {
50
+ try {
51
+ const entry = JSON.parse(line);
52
+ if (entry.file) {
53
+ contexts.push({
54
+ file: entry.file,
55
+ reason: entry.reason || '',
56
+ optional: entry.optional || false
57
+ });
58
+ }
59
+ } catch (e) {
60
+ // 跳过无效行
61
+ console.warn(`Warning: Invalid JSONL line: ${line}`);
62
+ }
63
+ }
64
+
65
+ return contexts;
66
+ }
67
+
68
+ /**
69
+ * 展开上下文为平台特定格式
70
+ * @param {Array<Object>} contexts - 上下文条目数组
71
+ * @param {string} platform - 目标平台
72
+ * @returns {string} 展开后的字符串
73
+ */
74
+ static expand(contexts, platform) {
75
+ if (!contexts || contexts.length === 0) {
76
+ return '';
77
+ }
78
+
79
+ switch (platform) {
80
+ case 'cursor':
81
+ return ContextExpander.expandForCursor(contexts);
82
+ case 'codex':
83
+ return ContextExpander.expandForCodex(contexts);
84
+ case 'qwen':
85
+ return ContextExpander.expandForQwen(contexts);
86
+ case 'antigravity':
87
+ return ContextExpander.expandForAntigravity(contexts);
88
+ default:
89
+ return ContextExpander.expandDefault(contexts);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Cursor 格式: 使用 @file 引用
95
+ */
96
+ static expandForCursor(contexts) {
97
+ const lines = contexts.map(c => {
98
+ const optional = c.optional ? ' (optional)' : '';
99
+ return `@${c.file}${optional}`;
100
+ });
101
+
102
+ return `\n## Context Files\n\n${lines.join('\n')}\n`;
103
+ }
104
+
105
+ /**
106
+ * Codex 格式: 生成 Required Context 章节
107
+ */
108
+ static expandForCodex(contexts) {
109
+ return ContextExpander.expandDefault(contexts);
110
+ }
111
+
112
+ /**
113
+ * Qwen 格式: 生成 Required Context 章节
114
+ */
115
+ static expandForQwen(contexts) {
116
+ return ContextExpander.expandDefault(contexts);
117
+ }
118
+
119
+ /**
120
+ * Antigravity 格式: 生成 Required Context 章节
121
+ */
122
+ static expandForAntigravity(contexts) {
123
+ return ContextExpander.expandDefault(contexts);
124
+ }
125
+
126
+ /**
127
+ * 默认格式: 生成 Required Context 章节
128
+ */
129
+ static expandDefault(contexts) {
130
+ const lines = contexts.map(c => {
131
+ const optional = c.optional ? ' (optional)' : '';
132
+ return `- \`${c.file}\` - ${c.reason}${optional}`;
133
+ });
134
+
135
+ return `
136
+ ## Required Context
137
+
138
+ Before executing this skill, read the following files:
139
+ ${lines.join('\n')}
140
+ `;
141
+ }
142
+
143
+ /**
144
+ * 从 SKILL.md 所在目录读取并展开 context.jsonl
145
+ * @param {string} skillDir - SKILL.md 所在目录
146
+ * @param {string} platform - 目标平台
147
+ * @returns {string} 展开后的字符串
148
+ */
149
+ static expandFromSkillDir(skillDir, platform) {
150
+ const contextPath = path.join(skillDir, 'context.jsonl');
151
+ const contexts = ContextExpander.parseContextFile(contextPath);
152
+ return ContextExpander.expand(contexts, platform);
153
+ }
154
+
155
+ /**
156
+ * 替换 SKILL.md 中的 {CONTEXT} 占位符
157
+ * @param {string} content - SKILL.md 内容
158
+ * @param {string} skillDir - SKILL.md 所在目录
159
+ * @param {string} platform - 目标平台
160
+ * @returns {string} 替换后的内容
161
+ */
162
+ static replaceContextPlaceholder(content, skillDir, platform) {
163
+ const contextExpanded = ContextExpander.expandFromSkillDir(skillDir, platform);
164
+
165
+ // 替换 {CONTEXT} 占位符
166
+ if (content.includes('{CONTEXT}')) {
167
+ return content.replace('{CONTEXT}', contextExpanded);
168
+ }
169
+
170
+ // 如果没有占位符但有 context.jsonl,追加到末尾
171
+ if (contextExpanded) {
172
+ return content + '\n' + contextExpanded;
173
+ }
174
+
175
+ return content;
176
+ }
177
+ }
178
+
179
+ module.exports = { ContextExpander };