From a093f8e8308b43aafbc4960b69bac798eda58389 Mon Sep 17 00:00:00 2001 From: shog86 Date: Thu, 19 Mar 2026 11:18:16 +0800 Subject: [PATCH 1/3] Add Trae IDE support - Add trae.js transformer with full metadata support (same as Claude Code) - Update build.js to include Trae in provider transformations - Update README.md with Trae installation instructions - Update DEVELOP.md with Trae transformer documentation Trae uses ~/.trae-cn/builtin_skills/ for skills installation. --- DEVELOP.md | 20 ++++++--- README.md | 9 +++++ scripts/build.js | 9 ++++- scripts/lib/transformers/index.js | 1 + scripts/lib/transformers/trae.js | 67 +++++++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 scripts/lib/transformers/trae.js diff --git a/DEVELOP.md b/DEVELOP.md index 16219237e..76ecd751d 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -9,7 +9,7 @@ This repository uses a **feature-rich source format** that transforms into provi ### Why This Approach? Different providers have different capabilities: -- **Claude Code, OpenCode**: Full metadata — args, user-invokable, allowed-tools, license, compatibility +- **Claude Code, OpenCode, Trae**: Full metadata — args, user-invokable, allowed-tools, license, compatibility - **Codex, Agents**: Args converted to `argument-hint` format - **Gemini**: Minimal frontmatter, `{{arg}}` placeholders become `{{args}}` - **Cursor, Kiro, Pi**: Basic frontmatter (name, description, license/compatibility) @@ -77,6 +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 ``` ## Provider Transformations @@ -121,6 +122,12 @@ All providers output skills to `dist/{provider}/.{config}/skills/{name}/SKILL.md - Output: `dist/pi/.pi/skills/{name}/SKILL.md` - Frontmatter: name, description, license, compatibility, metadata +### Trae (Full Featured) +- Output: `dist/trae/.trae-cn/builtin_skills/{name}/SKILL.md` +- Frontmatter: name, description, user-invokable, args, license, compatibility, metadata, allowed-tools +- Same format as Claude Code +- Skills are installed to `~/.trae-cn/builtin_skills/` + ## Adding New Content ### 1. Create Source File @@ -138,7 +145,7 @@ Add frontmatter and content following the format above. bun run build ``` -This generates all 8 provider formats automatically. +This generates all 9 provider formats automatically. ### 3. Test @@ -159,7 +166,7 @@ The build system uses a modular architecture under `scripts/`: - `build.js` — Main orchestrator - `lib/utils.js` — Shared utilities (frontmatter parsing, file I/O, placeholder replacement) - `lib/zip.js` — ZIP bundle generation -- `lib/transformers/*.js` — One file per provider (cursor, claude-code, gemini, codex, agents, kiro, opencode, pi) +- `lib/transformers/*.js` — One file per provider (cursor, claude-code, gemini, codex, agents, kiro, opencode, pi, trae) ### Key Functions @@ -174,6 +181,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 `.trae-cn/builtin_skills/` ## Best Practices @@ -220,7 +228,8 @@ impeccable/ │ ├── agents/ │ ├── kiro/ │ ├── opencode/ -│ └── pi/ +│ ├── pi/ +│ └── trae/ ├── scripts/ │ ├── build.js # Main orchestrator │ └── lib/ @@ -234,7 +243,8 @@ impeccable/ │ ├── agents.js │ ├── kiro.js │ ├── opencode.js -│ └── pi.js +│ ├── pi.js +│ └── trae.js ├── tests/ # Bun test suite ├── package.json # ESM project config ├── README.md # User documentation diff --git a/README.md b/README.md index 37c942024..be1f48442 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,14 @@ cp -r dist/gemini/.gemini your-project/ cp -r dist/codex/.codex/* ~/.codex/ ``` +**Trae:** +```bash +# Global installation (recommended) +cp -r dist/trae/.trae-cn/builtin_skills/* ~/.trae-cn/builtin_skills/ +``` + +> **Note:** Trae uses `~/.trae-cn/builtin_skills/` for skills. After copying, restart Trae IDE to activate the skills. + ## Usage Once installed, use commands in your AI harness: @@ -155,6 +163,7 @@ Most commands accept an optional argument to focus on a specific area: - [Codex CLI](https://github.com/openai/codex) - [VS Code Copilot](https://code.visualstudio.com) - [Kiro](https://kiro.dev) +- [Trae](https://trae.ai) ## Contributing diff --git a/scripts/build.js b/scripts/build.js index 2e51691b6..6287c5a00 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -26,7 +26,8 @@ import { transformAgents, transformKiro, transformOpenCode, - transformPi + transformPi, + transformTrae } from './lib/transformers/index.js'; import { createAllZips } from './lib/zip.js'; import { execSync } from 'child_process'; @@ -149,7 +150,8 @@ function assembleUniversal(distDir, suffix = '') { { provider: 'agents', configDir: '.agents' }, { provider: 'kiro', configDir: '.kiro' }, { provider: 'opencode', configDir: '.opencode' }, - { provider: 'pi', configDir: '.pi' } + { provider: 'pi', configDir: '.pi' }, + { provider: 'trae', configDir: '.trae-cn' } ]; for (const { provider, configDir } of providerMappings) { @@ -177,6 +179,7 @@ This folder contains skills for all supported tools: .kiro/ → Kiro .opencode/ → OpenCode .pi/ → Pi + .trae-cn/ → Trae To install, copy the relevant folder(s) into your project root. These are hidden folders (dotfiles) — press Cmd+Shift+. in Finder to see them. @@ -349,6 +352,7 @@ async function build() { transformKiro(skills, DIST_DIR, patterns); transformOpenCode(skills, DIST_DIR, patterns); transformPi(skills, DIST_DIR, patterns); + transformTrae(skills, DIST_DIR, patterns); // Transform for each provider (prefixed with i-) const prefixOptions = { prefix: 'i-', outputSuffix: '-prefixed' }; @@ -360,6 +364,7 @@ async function build() { transformKiro(skills, DIST_DIR, patterns, prefixOptions); transformOpenCode(skills, DIST_DIR, patterns, prefixOptions); transformPi(skills, DIST_DIR, patterns, prefixOptions); + transformTrae(skills, DIST_DIR, patterns, prefixOptions); // Assemble universal directory (unprefixed and prefixed) assembleUniversal(DIST_DIR); diff --git a/scripts/lib/transformers/index.js b/scripts/lib/transformers/index.js index da00044f3..fe6c4650a 100644 --- a/scripts/lib/transformers/index.js +++ b/scripts/lib/transformers/index.js @@ -6,3 +6,4 @@ export { transformAgents } from './agents.js'; export { transformKiro } from './kiro.js'; export { transformOpenCode } from "./opencode.js"; export { transformPi } from './pi.js'; +export { transformTrae } from './trae.js'; diff --git a/scripts/lib/transformers/trae.js b/scripts/lib/transformers/trae.js new file mode 100644 index 000000000..e997e4396 --- /dev/null +++ b/scripts/lib/transformers/trae.js @@ -0,0 +1,67 @@ +import path from 'path'; +import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders, prefixSkillReferences } from '../utils.js'; + +/** + * Trae Transformer (Skills Only) + * + * All skills output to .trae-cn/builtin_skills/{name}/SKILL.md + * Trae uses a similar format to Claude Code with full metadata support. + * + * @param {Array} skills - All skills (including user-invokable ones) + * @param {string} distDir - Distribution output directory + * @param {Object} patterns - Design patterns data (unused, kept for interface consistency) + * @param {Object} options - Optional settings + * @param {string} options.prefix - Prefix to add to user-invokable skill names (e.g., 'i-') + * @param {string} options.outputSuffix - Suffix for output directory (e.g., '-prefixed') + */ +export function transformTrae(skills, distDir, patterns = null, options = {}) { + const { prefix = '', outputSuffix = '' } = options; + const traeDir = path.join(distDir, `trae${outputSuffix}`); + const skillsDir = path.join(traeDir, '.trae-cn/builtin_skills'); + + cleanDir(traeDir); + ensureDir(skillsDir); + + const allSkillNames = skills.map(s => s.name); + const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`); + let refCount = 0; + for (const skill of skills) { + const skillName = `${prefix}${skill.name}`; + const skillDir = path.join(skillsDir, skillName); + + const frontmatterObj = { + name: skillName, + description: skill.description, + }; + + if (skill.userInvokable) frontmatterObj['user-invokable'] = 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; + if (skill.metadata) frontmatterObj.metadata = skill.metadata; + if (skill.allowedTools) frontmatterObj['allowed-tools'] = skill.allowedTools; + + const frontmatter = generateYamlFrontmatter(frontmatterObj); + let skillBody = replacePlaceholders(skill.body, 'trae', commandNames); + if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames); + const content = `${frontmatter}\n\n${skillBody}`; + const outputPath = path.join(skillDir, 'SKILL.md'); + writeFile(outputPath, content); + + if (skill.references && skill.references.length > 0) { + const refDir = path.join(skillDir, 'reference'); + ensureDir(refDir); + for (const ref of skill.references) { + const refOutputPath = path.join(refDir, `${ref.name}.md`); + const refContent = replacePlaceholders(ref.content, 'trae'); + writeFile(refOutputPath, refContent); + refCount++; + } + } + } + + const userInvokableCount = skills.filter(s => s.userInvokable).length; + const refInfo = refCount > 0 ? ` (${refCount} reference files)` : ''; + const prefixInfo = prefix ? ` [${prefix}prefixed]` : ''; + console.log(`✓ Trae${prefixInfo}: ${skills.length} skills (${userInvokableCount} user-invokable)${refInfo}`); +} From 3f05d8b6a7b1a4a32d0222d9c12f6e7e30bbc2f9 Mon Sep 17 00:00:00 2001 From: shog86 Date: Thu, 19 Mar 2026 11:32:09 +0800 Subject: [PATCH 2/3] Support both Trae China and International versions - Output skills to both .trae-cn/ and .trae/ directories - Update README with installation instructions for both versions - Update DEVELOP.md with dual-variant documentation --- DEVELOP.md | 9 ++-- README.md | 11 ++++- scripts/build.js | 6 ++- scripts/lib/transformers/trae.js | 81 +++++++++++++++++++------------- 4 files changed, 67 insertions(+), 40 deletions(-) diff --git a/DEVELOP.md b/DEVELOP.md index 76ecd751d..8be14f555 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -123,10 +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` +- 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 - Same format as Claude Code -- Skills are installed to `~/.trae-cn/builtin_skills/` +- Two variants are generated automatically: + - **Trae China**: Skills installed to `~/.trae-cn/builtin_skills/` + - **Trae International**: Skills installed to `~/.trae/builtin_skills/` ## Adding New Content @@ -181,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 `.trae-cn/builtin_skills/` +- `transformTrae()`: Full metadata (same as Claude Code), outputs to both `.trae-cn/builtin_skills/` and `.trae/builtin_skills/` ## Best Practices diff --git a/README.md b/README.md index be1f48442..b7517c824 100644 --- a/README.md +++ b/README.md @@ -127,11 +127,18 @@ cp -r dist/codex/.codex/* ~/.codex/ **Trae:** ```bash -# Global installation (recommended) +# Trae China (domestic version) cp -r dist/trae/.trae-cn/builtin_skills/* ~/.trae-cn/builtin_skills/ + +# Trae International +cp -r dist/trae/.trae/builtin_skills/* ~/.trae/builtin_skills/ ``` -> **Note:** Trae uses `~/.trae-cn/builtin_skills/` for skills. After copying, restart Trae IDE to activate the skills. +> **Note:** Trae has two versions with different config directories: +> - **Trae China**: `~/.trae-cn/builtin_skills/` +> - **Trae International**: `~/.trae/builtin_skills/` +> +> After copying, restart Trae IDE to activate the skills. ## Usage diff --git a/scripts/build.js b/scripts/build.js index 6287c5a00..8a932d6ee 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -151,7 +151,8 @@ function assembleUniversal(distDir, suffix = '') { { provider: 'kiro', configDir: '.kiro' }, { provider: 'opencode', configDir: '.opencode' }, { provider: 'pi', configDir: '.pi' }, - { provider: 'trae', configDir: '.trae-cn' } + { provider: 'trae', configDir: '.trae-cn' }, + { provider: 'trae', configDir: '.trae' } ]; for (const { provider, configDir } of providerMappings) { @@ -179,7 +180,8 @@ This folder contains skills for all supported tools: .kiro/ → Kiro .opencode/ → OpenCode .pi/ → Pi - .trae-cn/ → Trae + .trae-cn/ → Trae China + .trae/ → Trae International To install, copy the relevant folder(s) into your project root. These are hidden folders (dotfiles) — press Cmd+Shift+. in Finder to see them. diff --git a/scripts/lib/transformers/trae.js b/scripts/lib/transformers/trae.js index e997e4396..19a1c787b 100644 --- a/scripts/lib/transformers/trae.js +++ b/scripts/lib/transformers/trae.js @@ -4,7 +4,10 @@ import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceho /** * Trae Transformer (Skills Only) * - * All skills output to .trae-cn/builtin_skills/{name}/SKILL.md + * 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 uses a similar format to Claude Code with full metadata support. * * @param {Array} skills - All skills (including user-invokable ones) @@ -17,51 +20,63 @@ import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceho export function transformTrae(skills, distDir, patterns = null, options = {}) { const { prefix = '', outputSuffix = '' } = options; const traeDir = path.join(distDir, `trae${outputSuffix}`); - const skillsDir = path.join(traeDir, '.trae-cn/builtin_skills'); cleanDir(traeDir); - ensureDir(skillsDir); const allSkillNames = skills.map(s => s.name); const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`); - let refCount = 0; - for (const skill of skills) { - const skillName = `${prefix}${skill.name}`; - const skillDir = path.join(skillsDir, skillName); - const frontmatterObj = { - name: skillName, - description: skill.description, - }; + const variants = [ + { name: 'China', configDir: '.trae-cn/builtin_skills' }, + { name: 'International', configDir: '.trae/builtin_skills' } + ]; - if (skill.userInvokable) frontmatterObj['user-invokable'] = 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; - if (skill.metadata) frontmatterObj.metadata = skill.metadata; - if (skill.allowedTools) frontmatterObj['allowed-tools'] = skill.allowedTools; + let totalRefCount = 0; - const frontmatter = generateYamlFrontmatter(frontmatterObj); - let skillBody = replacePlaceholders(skill.body, 'trae', commandNames); - if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames); - const content = `${frontmatter}\n\n${skillBody}`; - const outputPath = path.join(skillDir, 'SKILL.md'); - writeFile(outputPath, content); + for (const variant of variants) { + const skillsDir = path.join(traeDir, variant.configDir); + ensureDir(skillsDir); - if (skill.references && skill.references.length > 0) { - const refDir = path.join(skillDir, 'reference'); - ensureDir(refDir); - for (const ref of skill.references) { - const refOutputPath = path.join(refDir, `${ref.name}.md`); - const refContent = replacePlaceholders(ref.content, 'trae'); - writeFile(refOutputPath, refContent); - refCount++; + let refCount = 0; + for (const skill of skills) { + const skillName = `${prefix}${skill.name}`; + const skillDir = path.join(skillsDir, skillName); + + const frontmatterObj = { + name: skillName, + description: skill.description, + }; + + if (skill.userInvokable) frontmatterObj['user-invokable'] = 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; + if (skill.metadata) frontmatterObj.metadata = skill.metadata; + if (skill.allowedTools) frontmatterObj['allowed-tools'] = skill.allowedTools; + + const frontmatter = generateYamlFrontmatter(frontmatterObj); + let skillBody = replacePlaceholders(skill.body, 'trae', commandNames); + if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames); + const content = `${frontmatter}\n\n${skillBody}`; + const outputPath = path.join(skillDir, 'SKILL.md'); + writeFile(outputPath, content); + + if (skill.references && skill.references.length > 0) { + const refDir = path.join(skillDir, 'reference'); + ensureDir(refDir); + for (const ref of skill.references) { + const refOutputPath = path.join(refDir, `${ref.name}.md`); + const refContent = replacePlaceholders(ref.content, 'trae'); + writeFile(refOutputPath, refContent); + refCount++; + } } } + totalRefCount += refCount; } const userInvokableCount = skills.filter(s => s.userInvokable).length; - const refInfo = refCount > 0 ? ` (${refCount} reference files)` : ''; + 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)${refInfo}`); + console.log(`✓ Trae${prefixInfo}: ${skills.length} skills (${userInvokableCount} user-invokable) for 2 variants${refInfo}`); } From 9faa6900478a5f2df9ba273609f0b249892c35ae Mon Sep 17 00:00:00 2001 From: shog86 Date: Sun, 22 Mar 2026 12:12:24 +0800 Subject: [PATCH 3/3] Fix Trae output format and add tests --- DEVELOP.md | 15 ++-- README.md | 8 +-- scripts/lib/transformers/trae.js | 12 ++-- tests/lib/transformers/trae.test.js | 103 ++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 18 deletions(-) create mode 100644 tests/lib/transformers/trae.test.js 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')); + }); +});