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
|
@@ -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 };
|