diff --git a/DEVELOP.md b/DEVELOP.md index 8be14f555..5fcd64e38 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -77,7 +77,7 @@ source/ → dist/ kiro/.kiro/skills/{name}/SKILL.md opencode/.opencode/skills/{name}/SKILL.md pi/.pi/skills/{name}/SKILL.md - trae/.trae-cn/builtin_skills/{name}/SKILL.md + trae/.trae-cn/skills/{name}/SKILL.md ``` ## Provider Transformations @@ -123,13 +123,13 @@ All providers output skills to `dist/{provider}/.{config}/skills/{name}/SKILL.md - Frontmatter: name, description, license, compatibility, metadata ### Trae (Full Featured) -- Output: `dist/trae/.trae-cn/builtin_skills/{name}/SKILL.md` (China version) -- Output: `dist/trae/.trae/builtin_skills/{name}/SKILL.md` (International version) -- Frontmatter: name, description, user-invokable, args, license, compatibility, metadata, allowed-tools +- Output: `dist/trae/.trae-cn/skills/{name}/SKILL.md` (China version) +- Output: `dist/trae/.trae/skills/{name}/SKILL.md` (International version) +- Frontmatter: name, description, user-invocable, args, license, compatibility, metadata, allowed-tools - Same format as Claude Code - Two variants are generated automatically: - - **Trae China**: Skills installed to `~/.trae-cn/builtin_skills/` - - **Trae International**: Skills installed to `~/.trae/builtin_skills/` + - **Trae China**: Skills installed to `~/.trae-cn/skills/` + - **Trae International**: Skills installed to `~/.trae/skills/` ## Adding New Content @@ -184,7 +184,7 @@ The build system uses a modular architecture under `scripts/`: - `transformKiro()`: Basic frontmatter with license/compatibility/metadata - `transformOpenCode()`: Full metadata (same as Claude Code) - `transformPi()`: Basic frontmatter with license/compatibility/metadata -- `transformTrae()`: Full metadata (same as Claude Code), outputs to both `.trae-cn/builtin_skills/` and `.trae/builtin_skills/` +- `transformTrae()`: Full metadata (same as Claude Code), outputs to both `.trae-cn/skills/` and `.trae/skills/` ## Best Practices @@ -275,4 +275,3 @@ impeccable/ ## Questions? Open an issue or submit a PR! - diff --git a/README.md b/README.md index b7517c824..12f866ef4 100644 --- a/README.md +++ b/README.md @@ -128,15 +128,15 @@ cp -r dist/codex/.codex/* ~/.codex/ **Trae:** ```bash # Trae China (domestic version) -cp -r dist/trae/.trae-cn/builtin_skills/* ~/.trae-cn/builtin_skills/ +cp -r dist/trae/.trae-cn/skills/* ~/.trae-cn/skills/ # Trae International -cp -r dist/trae/.trae/builtin_skills/* ~/.trae/builtin_skills/ +cp -r dist/trae/.trae/skills/* ~/.trae/skills/ ``` > **Note:** Trae has two versions with different config directories: -> - **Trae China**: `~/.trae-cn/builtin_skills/` -> - **Trae International**: `~/.trae/builtin_skills/` +> - **Trae China**: `~/.trae-cn/skills/` +> - **Trae International**: `~/.trae/skills/` > > After copying, restart Trae IDE to activate the skills. diff --git a/scripts/lib/transformers/trae.js b/scripts/lib/transformers/trae.js index 19a1c787b..545fa9e54 100644 --- a/scripts/lib/transformers/trae.js +++ b/scripts/lib/transformers/trae.js @@ -5,8 +5,8 @@ import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceho * Trae Transformer (Skills Only) * * Outputs skills for both Trae China and Trae International versions: - * - .trae-cn/builtin_skills/{name}/SKILL.md (China version) - * - .trae/builtin_skills/{name}/SKILL.md (International version) + * - .trae-cn/skills/{name}/SKILL.md (China version) + * - .trae/skills/{name}/SKILL.md (International version) * * Trae uses a similar format to Claude Code with full metadata support. * @@ -27,8 +27,8 @@ export function transformTrae(skills, distDir, patterns = null, options = {}) { const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`); const variants = [ - { name: 'China', configDir: '.trae-cn/builtin_skills' }, - { name: 'International', configDir: '.trae/builtin_skills' } + { name: 'China', configDir: '.trae-cn/skills' }, + { name: 'International', configDir: '.trae/skills' } ]; let totalRefCount = 0; @@ -47,7 +47,7 @@ export function transformTrae(skills, distDir, patterns = null, options = {}) { description: skill.description, }; - if (skill.userInvokable) frontmatterObj['user-invokable'] = true; + if (skill.userInvokable) frontmatterObj['user-invocable'] = true; if (skill.args && skill.args.length > 0) frontmatterObj.args = skill.args; if (skill.license) frontmatterObj.license = skill.license; if (skill.compatibility) frontmatterObj.compatibility = skill.compatibility; @@ -78,5 +78,5 @@ export function transformTrae(skills, distDir, patterns = null, options = {}) { const userInvokableCount = skills.filter(s => s.userInvokable).length; const refInfo = totalRefCount > 0 ? ` (${totalRefCount / 2} reference files per variant)` : ''; const prefixInfo = prefix ? ` [${prefix}prefixed]` : ''; - console.log(`✓ Trae${prefixInfo}: ${skills.length} skills (${userInvokableCount} user-invokable) for 2 variants${refInfo}`); + console.log(`✓ Trae${prefixInfo}: ${skills.length} skills (${userInvokableCount} user-invocable) for 2 variants${refInfo}`); } diff --git a/tests/lib/transformers/trae.test.js b/tests/lib/transformers/trae.test.js new file mode 100644 index 000000000..bce36c29c --- /dev/null +++ b/tests/lib/transformers/trae.test.js @@ -0,0 +1,103 @@ +import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'; +import fs from 'fs'; +import path from 'path'; +import { transformTrae } from '../../../scripts/lib/transformers/trae.js'; + +const TEST_DIR = path.join(process.cwd(), 'test-tmp-trae'); + +describe('transformTrae', () => { + beforeEach(() => { + if (fs.existsSync(TEST_DIR)) { + fs.rmSync(TEST_DIR, { recursive: true, force: true }); + } + }); + + afterEach(() => { + if (fs.existsSync(TEST_DIR)) { + fs.rmSync(TEST_DIR, { recursive: true, force: true }); + } + }); + + test('should create correct directory structure for both variants', () => { + transformTrae([], TEST_DIR); + + expect(fs.existsSync(path.join(TEST_DIR, 'trae/.trae-cn/skills'))).toBe(true); + expect(fs.existsSync(path.join(TEST_DIR, 'trae/.trae/skills'))).toBe(true); + }); + + test('should create skill with frontmatter and body', () => { + const skills = [ + { + name: 'test-skill', + description: 'A test skill', + license: 'MIT', + userInvokable: true, + body: 'Skill instructions here.' + } + ]; + + transformTrae(skills, TEST_DIR); + + const chinaPath = path.join(TEST_DIR, 'trae/.trae-cn/skills/test-skill/SKILL.md'); + const intlPath = path.join(TEST_DIR, 'trae/.trae/skills/test-skill/SKILL.md'); + expect(fs.existsSync(chinaPath)).toBe(true); + expect(fs.existsSync(intlPath)).toBe(true); + + const content = fs.readFileSync(chinaPath, 'utf-8'); + expect(content).toContain('---'); + expect(content).toContain('name: test-skill'); + expect(content).toContain('description: A test skill'); + expect(content).toContain('license: MIT'); + expect(content).toContain('user-invocable: true'); + expect(content).toContain('Skill instructions here.'); + }); + + test('should copy reference files', () => { + const skills = [ + { + name: 'frontend-design', + description: 'Design skill', + license: 'MIT', + body: 'Design instructions.', + references: [ + { name: 'typography', content: 'Typography reference', filePath: '/fake/path/typography.md' } + ] + } + ]; + + transformTrae(skills, TEST_DIR); + + expect(fs.existsSync(path.join(TEST_DIR, 'trae/.trae-cn/skills/frontend-design/reference/typography.md'))).toBe(true); + expect(fs.existsSync(path.join(TEST_DIR, 'trae/.trae/skills/frontend-design/reference/typography.md'))).toBe(true); + }); + + test('should support prefix option', () => { + const skills = [ + { name: 'audit', description: 'Audit', license: '', body: 'Audit body' } + ]; + + transformTrae(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' }); + + expect(fs.existsSync(path.join(TEST_DIR, 'trae-prefixed/.trae-cn/skills/i-audit/SKILL.md'))).toBe(true); + expect(fs.existsSync(path.join(TEST_DIR, 'trae-prefixed/.trae/skills/i-audit/SKILL.md'))).toBe(true); + }); + + test('should log correct summary', () => { + const consoleMock = mock(() => {}); + const originalLog = console.log; + console.log = consoleMock; + + const skills = [ + { name: 'skill1', description: '', license: '', userInvokable: true, body: 'body1' }, + { name: 'skill2', description: '', license: '', userInvokable: false, body: 'body2' } + ]; + + transformTrae(skills, TEST_DIR); + + console.log = originalLog; + + expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Trae:')); + expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills')); + expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable')); + }); +});