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:
Tom Boucher
2026-07-10 20:59:31 -04:00
parent d010ea13d4
commit f744635b3d
11 changed files with 466 additions and 88 deletions

View File

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

View File

@@ -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',