feat(#2094): migrate Trae onto EoS imperative adapter + SOLO stage-metadata upgrade (ADR-1239)
Fold trae logic branches into descriptor-driven reads: skipSharedHooksInstall gates (dropped && !isTrae), and the case 'trae' path-rewrite arm now computes the self-alias from the descriptor-driven dirName (.trae). Dead isTrae bindings removed from uninstall/writeManifest/finishInstall. trae's skills dispatch was already descriptor-driven (converter-by-name). RUNTIME_CONTENT_DISPATCH.trae is left as a runtime-keyed table registration (its regex/callback rewrites can't be a byte-identical descriptor map — matches cursor/windsurf/cline). trae stays in RUNTIME_FLAG_IDS: isTrae still gates the agents-converter selection (agents out of scope; removal gated on the cross-runtime agents-dispatch migration). Byte-identical golden parity for all 16 runtimes. UPGRADE: SOLO stage/trigger metadata — emitted Trae SKILL.md now carries stage: workflow (descriptor-gated via hostBehaviors.soloStageMetadata) so Trae's SOLO Agent can auto-invoke GSD skills at the corresponding stage. Field shape is best-effort/inferred (Trae publishes no formal schema). trae.json golden regenerated. Tests: trae-imperative-reference (adapter/axes/fail-closed shouldFlattenDispatch + no runtime==='trae' source-grep, isTrae exempted for agents) + trae-upgrades (stage: workflow on installed SKILL.md, descriptor-gated). Matrix note + changeset added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1169,6 +1169,12 @@ function convertClaudeToTraeMarkdown(content) {
|
||||
return converted;
|
||||
}
|
||||
|
||||
// DEFECT.GENERATIVE-FIX: this body is mirrored in bin/install.js's
|
||||
// convertClaudeCommandToTraeSkill (dead for the live skills-install path,
|
||||
// which routes here via install-engine.cts's SKILLS_CONVERTER_REGISTRY; kept
|
||||
// for bin/install.js's own module-level export/test surface). Neither copy
|
||||
// re-exports the other — mirror any behavior change into both. Guarded by
|
||||
// the output-parity test in tests/runtime-converters.test.cjs (#2094).
|
||||
function convertClaudeCommandToTraeSkill(content, skillName) {
|
||||
const converted = convertClaudeToTraeMarkdown(content);
|
||||
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
||||
@@ -1183,7 +1189,16 @@ function convertClaudeCommandToTraeSkill(content, skillName) {
|
||||
const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
|
||||
// #2876: quote so YAML flow indicators (`[BETA] …`) don't break Trae's
|
||||
// frontmatter parser.
|
||||
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\n---\n${body}`;
|
||||
let fm = `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\n`;
|
||||
// #2094: emit `stage:` so Trae's SOLO agent can auto-invoke GSD skills at
|
||||
// the corresponding stage (docs.trae.ai/ide/agent). The field name/schema
|
||||
// is not formally documented (thin SPA docs) — descriptor-driven, single
|
||||
// fixed GSD-side value (runtime.hostBehaviors.soloStageMetadata), inferred/
|
||||
// best-effort.
|
||||
const soloStage = _hostBehaviors('trae').soloStageMetadata as string | undefined;
|
||||
if (soloStage) fm += `stage: ${soloStage}\n`;
|
||||
fm += '---';
|
||||
return `${fm}\n${body}`;
|
||||
}
|
||||
|
||||
function convertSlashCommandsToCodebuddySkillMentions(content) {
|
||||
@@ -2431,7 +2446,10 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix, isGlobal = false, a
|
||||
content = content.replace(/~\/\.claude\b/g, normalizedPathPrefix);
|
||||
content = content.replace(/\$HOME\/\.claude\b/g, normalizedPathPrefix);
|
||||
content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
|
||||
content = content.replace(/~\/\.trae\//g, pathPrefix);
|
||||
// #2094: descriptor-driven — dirName resolves to '.trae' via
|
||||
// getDirName()/localConfigDir, so this regex is built rather than
|
||||
// hardcoded as `/~\/\.trae\//g` (byte-identical output for trae).
|
||||
content = content.replace(new RegExp('~/' + escapeRegExp(dirName) + '/', 'g'), pathPrefix);
|
||||
content = processAttribution(content, attribution);
|
||||
break;
|
||||
|
||||
|
||||
@@ -266,6 +266,11 @@ export function getGlobalConfigHomeFragment(runtime: string): string {
|
||||
* function declaration block (the add-a-host tax ADR-1239 Phase B / #1679 AC2
|
||||
* removes).
|
||||
*/
|
||||
// #2094: 'trae' stays here — bin/install.js's agents-converter dispatch
|
||||
// (convertClaudeAgentToTraeAgent selection) still reads isTrae directly.
|
||||
// Removing it is gated on migrating that runtime-keyed `else if` chain to a
|
||||
// cross-runtime agents-dispatch table (out of scope for #2094, which only
|
||||
// folds the shared-hooks-install skip).
|
||||
const RUNTIME_FLAG_IDS = Object.freeze([
|
||||
'opencode', 'kilo', 'codex', 'copilot', 'antigravity', 'cursor',
|
||||
'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'kimi', 'zcode',
|
||||
|
||||
Reference in New Issue
Block a user