@ancleto/spec 0.12.0 → 0.12.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/README.md CHANGED
@@ -77,7 +77,7 @@ Ancleto resuelve esto centralizando las reglas de negocio en un motor de memoria
77
77
 
78
78
  **Wizard interactivo** — Banner animado y menús navegables con flechas en `init` e `install`, sin librerías pesadas.
79
79
 
80
- **Hosts e IDEs (7)** — opencode, claude, vscode, antigravity, cursor, roo y copilot, cada uno con su layout nativo (Copilot: prompts en `.github/prompts` + MCP en `copilot-mcp.json`).
80
+ **Hosts e IDEs (8)** — opencode, claude, vscode, antigravity, cursor, roo, copilot y commandcode, cada uno con su layout nativo (Copilot: prompts en `.github/prompts` + MCP en `copilot-mcp.json`; Command Code: `.commandcode/*` + MCP en `.mcp.json`).
81
81
 
82
82
  **Perfil de test automation** — `--profile test`: tester ampliado (planning/generation/healing/coverage), comandos `cleto-test-*` y estructura `testspec/` para proyectos Playwright.
83
83
 
@@ -151,6 +151,7 @@ del host (el CLI no deduplica ni elige un "ganador").
151
151
  | `cursor` | `.cursor/skills/<n>/SKILL.md` | — | — |
152
152
  | `roo` | `.roo/skills/<n>/SKILL.md` | — | — |
153
153
  | `copilot` | — | `.github/prompts/<n>.prompt.md` | `.github/prompts/<n>.prompt.md` |
154
+ | `commandcode` | `.commandcode/skills/<n>/SKILL.md` | `.commandcode/agents/<n>.md` (adaptado) | `.commandcode/commands/<n>.md` |
154
155
 
155
156
  Cada host recibe sus skills en **su** directorio nativo. **`.agents/skills` es un punto de lectura
156
157
  compartido** —lo leen opencode, Cursor, VS Code y Antigravity—, no un destino universal: Claude lee
@@ -172,18 +173,36 @@ for agent '<n>'`): un id inexistente cuelga el subagent. Los ids que sólo exist
172
173
  de MCP se expresa por `mcpServers`/`.agents/mcp_config.json`, nunca como tool id. El campo `skill` no
173
174
  es una tool: se cubre con el surfaceo automático de skills de Antigravity.
174
175
 
176
+ **Command Code** (CLI `cmd`) usa su layout nativo por asset bajo `.commandcode/`: skills en
177
+ `.commandcode/skills/<n>/SKILL.md` (Agent Skills, verbatim), agents en `.commandcode/agents/<n>.md`
178
+ (frontmatter adaptado) y commands en `.commandcode/commands/<n>.md` (el body es el prompt, copia
179
+ verbatim). Sus agents adaptan el frontmatter de opencode: `name` inyectado, `description` preservado,
180
+ `mode`/`color`/`temperature`/`permission` eliminados, `model` **omitido** (el host hereda el modelo de
181
+ sesión; elegilo con `/model`, el tier no lo reescribe) y `tools` como **lista** de ids verificados
182
+ (`read`→`read_file`, `write`→`write_file`, `edit`→`edit_file`, `bash`→`shell_command`, `grep`→`grep`,
183
+ `glob`→`glob`, `webfetch`→`web_fetch`, `websearch`→`web_search`, `todowrite`→`todo_write`). Las tools
184
+ de memoria (`searchMemory`/`recordRule`/`recordDecision`) se emiten con la convención MCP de Command
185
+ Code (`mcp__ancleto-memory__<tool>`). Una clave sin id verificado se omite con aviso
186
+ (`skip tool '<k>': no verified Command Code id for agent '<n>'`). A diferencia de otros hosts, en
187
+ Command Code `tools` **omitido** significa "ninguna tool": si el agent de origen no declara `tools`,
188
+ se emite `tools: "*"` (el default de opencode es "todas"). El body de los commands invoca "the Skill
189
+ tool" (no se transforma); en Command Code el equivalente es `/skill:<name>`.
190
+
175
191
  Los assets que un host no soporta (por ejemplo `agents`/`commands` de `cursor` y `roo`)
176
192
  **no se escriben** y el instalador avisa por `stderr`
177
193
  (`skip agents: not supported by host 'cursor'`) sin abortar. La instalación es **aditiva y no
178
194
  destructiva**: instalar con otro agente escribe en su base y preserva los layouts previos, y el
179
195
  manifiesto `.ancletorc.installedPaths` acumula la unión de los destinos realmente escritos.
180
196
 
181
- La **configuración MCP específica de host** está fuera de alcance con **una excepción**: para
197
+ La **configuración MCP específica de host** está fuera de alcance con **dos excepciones**: para
182
198
  `agent: antigravity` el CLI genera o mergea (no destructivo) el MCP de workspace
183
- `.agents/mcp_config.json` con esquema `{ "mcpServers": { "<n>": { "command", "args", "env" } } }`,
184
- incluyendo el servidor de memoria `ancleto-memory`; preserva `mcpServers` preexistentes y no pisa
185
- homónimos. `.mcp.json` (Claude) y `.vscode/mcp.json` (VS Code) siguen fuera de alcance, al igual que el
186
- MCP global de Antigravity. `claude` se soporta con adaptación de frontmatter obligatoria para `agents`
199
+ `.agents/mcp_config.json` con esquema `{ "mcpServers": { "<n>": { "command", "args", "env" } } }`, y
200
+ para `agent: commandcode` el MCP de proyecto `.mcp.json` con esquema
201
+ `{ "mcpServers": { "<n>": { "transport": "stdio", "command", "args", "env" } } }`; en ambos casos
202
+ incluye el servidor de memoria `ancleto-memory`, preserva `mcpServers` preexistentes y no pisa
203
+ homónimos. `.mcp.json` sólo lo gestiona `commandcode` (para `claude` sigue fuera de alcance), al igual
204
+ que `.vscode/mcp.json` (VS Code) y el MCP global de Antigravity. `claude` se soporta con adaptación de
205
+ frontmatter obligatoria para `agents`
187
206
  (se omiten `model`/`tools` sin equivalencia verificada y las claves
188
207
  `mode`/`color`/`temperature`/`permission`), sin MCP propio. **Copilot** instala agents y commands
189
208
  como prompt files en `.github/prompts/*.prompt.md` (mismo adapter, sin `model:`) y su MCP en
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ancleto/spec",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Orquestador SDD liviano con subagentes optimizados para costo/tokens",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/index.js CHANGED
@@ -36,13 +36,15 @@ Uso:
36
36
  ancleto install --tier <nivel> normal | minimo | gratis (wizard interactivo en TTY)
37
37
  ancleto install --lang <codigo> auto | es | en | pt (idioma de los artifacts; auto = idioma de la conversacion)
38
38
  ancleto install --exclude <globs> Paths excluidos del discovery, coma-separados (ej: "**/*.png,docs")
39
- ancleto install --agent <nombre> opencode | claude | vscode | antigravity | cursor | roo | copilot (wizard si no esta guardado)
39
+ ancleto install --agent <nombre> opencode | claude | vscode | antigravity | cursor | roo | copilot | commandcode (wizard si no esta guardado)
40
40
  Elige el layout nativo a materializar (directorio + convencion de
41
41
  nombre) y el adapter de frontmatter del host. NO arbitra el
42
42
  descubrimiento entre hosts: si dos leen el mismo directorio, eso es
43
43
  efecto del host (no se deduplica ni se elige "ganador").
44
44
  antigravity instala skills, agents adaptados y commands como skills
45
45
  (.agents/*) y mergea el MCP de workspace .agents/mcp_config.json.
46
+ commandcode instala skills, agents adaptados y commands en
47
+ .commandcode/* y mergea el MCP de proyecto .mcp.json.
46
48
  ancleto install --profile <perfil> general | test (perfil de test automation con Playwright; persiste en .ancletorc)
47
49
  ancleto update [--project <dir>] Alias de install (re-instala sobre lo existente)
48
50
  Parado en un proyecto con .ancletorc opera sobre ese proyecto;
@@ -612,7 +614,7 @@ async function applyTier(agentsDir, tier, models = null) {
612
614
  }
613
615
  }
614
616
 
615
- const SUPPORTED_AGENTS = ['opencode', 'claude', 'vscode', 'antigravity', 'cursor', 'roo', 'copilot']
617
+ const SUPPORTED_AGENTS = ['opencode', 'claude', 'vscode', 'antigravity', 'cursor', 'roo', 'copilot', 'commandcode']
616
618
  const DEFAULT_AGENT = 'opencode'
617
619
 
618
620
  function askAgent() {
@@ -855,12 +857,48 @@ async function mergeCopilotMcp(projectDir, mcpMap) {
855
857
  return { file, added }
856
858
  }
857
859
 
860
+ // Command Code: MCP de proyecto en `.mcp.json` (scope `project`, versionado con
861
+ // el repo). Esquema del host: `{ mcpServers: { <name>: { transport: 'stdio',
862
+ // command, args, env? } } }` (se descartan `type`/`enabled` de opencode). Merge NO
863
+ // destructivo: se preservan `mcpServers` y demas claves top-level, no se pisa un
864
+ // servidor homonimo, y un JSON invalido se avisa sin sobrescribir.
865
+ function toCommandCodeServer(def) {
866
+ const command = Array.isArray(def.command) ? def.command : [def.command]
867
+ const server = { transport: 'stdio', command: command[0], args: command.slice(1) }
868
+ if (def.env) server.env = def.env
869
+ return server
870
+ }
871
+
872
+ async function mergeCommandCodeMcp(projectDir, mcpMap) {
873
+ if (Object.keys(mcpMap).length === 0) return { file: null, added: [] }
874
+ const file = join(projectDir, '.mcp.json')
875
+ let cfg = {}
876
+ if (await exists(file)) {
877
+ try {
878
+ cfg = JSON.parse((await readFile(file, 'utf8')).replace(/^\uFEFF/, ''))
879
+ } catch {
880
+ console.warn('ancleto: no se pudo leer .mcp.json como JSON; MCP de Command Code no se modifico')
881
+ return { file, added: [] }
882
+ }
883
+ }
884
+ cfg.mcpServers = cfg.mcpServers || {}
885
+ const added = []
886
+ for (const [name, def] of Object.entries(mcpMap)) {
887
+ if (cfg.mcpServers[name]) continue
888
+ cfg.mcpServers[name] = toCommandCodeServer(def)
889
+ added.push(name)
890
+ }
891
+ await writeFile(file, JSON.stringify(cfg, null, 2) + '\n')
892
+ return { file, added }
893
+ }
894
+
858
895
  // D6: dueno unico del MCP de host. Antigravity mergea `.agents/mcp_config.json`;
859
- // el resto conserva `mergeMcp` sobre opencode.json (targetDir = .opencode del
860
- // proyecto o config global).
896
+ // Command Code mergea `.mcp.json`; el resto conserva `mergeMcp` sobre opencode.json
897
+ // (targetDir = .opencode del proyecto o config global).
861
898
  async function setupHostMcp(targetDir, mcpMap, { projectDir = null, agent = null } = {}) {
862
899
  if (Object.keys(mcpMap).length === 0) return { file: null, added: [] }
863
900
  if (agent === 'antigravity' && projectDir) return mergeAntigravityMcp(projectDir, mcpMap)
901
+ if (agent === 'commandcode' && projectDir) return mergeCommandCodeMcp(projectDir, mcpMap)
864
902
  await mkdir(targetDir, { recursive: true })
865
903
  return mergeMcp(targetDir, mcpMap)
866
904
  }
@@ -895,6 +933,39 @@ async function refreshCopilotMcp(projectDir) {
895
933
  return { file, fixed }
896
934
  }
897
935
 
936
+ // Regenera entradas rotas y agrega ausentes en `.mcp.json` de Command Code (para
937
+ // upgrade). Mismo criterio que `refreshCopilotMcp`, con el esquema del host.
938
+ async function refreshCommandCodeMcp(projectDir) {
939
+ const file = join(projectDir, '.mcp.json')
940
+ let cfg = {}
941
+ if (await exists(file)) {
942
+ try {
943
+ cfg = JSON.parse((await readFile(file, 'utf8')).replace(/^\uFEFF/, ''))
944
+ } catch {
945
+ cfg = {}
946
+ }
947
+ }
948
+ cfg.mcpServers = cfg.mcpServers || {}
949
+ const fresh = buildDefaultMcp({ withEngram: Boolean(cfg.mcpServers.engram) })
950
+ const fixed = []
951
+ for (const [name, def] of Object.entries(cfg.mcpServers)) {
952
+ if (!fresh[name]) continue
953
+ const asArray = { command: [def.command, ...(Array.isArray(def.args) ? def.args : [])] }
954
+ if (mcpCommandBroken(asArray)) {
955
+ Object.assign(cfg.mcpServers[name], toCommandCodeServer(fresh[name]))
956
+ fixed.push(name)
957
+ }
958
+ }
959
+ for (const [name, def] of Object.entries(fresh)) {
960
+ if (!cfg.mcpServers[name]) {
961
+ cfg.mcpServers[name] = toCommandCodeServer(def)
962
+ fixed.push(`${name} (agregado)`)
963
+ }
964
+ }
965
+ await writeFile(file, JSON.stringify(cfg, null, 2) + '\n')
966
+ return { file, fixed }
967
+ }
968
+
898
969
  async function copyAssets(dest) {
899
970
  await mkdir(dest, { recursive: true })
900
971
  for (const d of ASSETS) {
@@ -940,6 +1011,16 @@ const AGENT_TARGETS = {
940
1011
  skills: null,
941
1012
  agents: { dir: '.github/prompts', ext: '.prompt.md' },
942
1013
  commands: { dir: '.github/prompts', ext: '.prompt.md' }
1014
+ },
1015
+ // Command Code: layout nativo por asset bajo `.commandcode/`. Los tres assets
1016
+ // son nativos: skills (Agent Skills, identity), agents (frontmatter adaptado,
1017
+ // ver `adaptCommandCodeFrontmatter`) y commands (custom slash commands, el body
1018
+ // es el prompt, copia verbatim). El MCP de proyecto vive en `.mcp.json`
1019
+ // (ver `mergeCommandCodeMcp`).
1020
+ commandcode: {
1021
+ skills: { dir: '.commandcode/skills' },
1022
+ agents: { dir: '.commandcode/agents', ext: MD_EXT },
1023
+ commands: { dir: '.commandcode/commands', ext: MD_EXT }
943
1024
  }
944
1025
  }
945
1026
 
@@ -956,6 +1037,18 @@ normal = flujo completo, minimo = pasos agrupados y económicos, gratis = modelo
956
1037
  gratis disponible en tu cuenta.
957
1038
  `
958
1039
 
1040
+ // Solo Command Code: los tiers tampoco cambian modelos (el agent no fija `model`,
1041
+ // hereda el de la sesión); la nota lo recuerda al empezar.
1042
+ const COMMANDCODE_MODEL_NOTE = `
1043
+ ## Nota de modelo (solo Command Code)
1044
+
1045
+ Elegí el modelo con \`/model\` antes de empezar: los tiers de ancleto
1046
+ (normal|minimo|gratis) no cambian modelos en Command Code, solo el nivel de
1047
+ esfuerzo (normal = flujo completo, minimo = pasos agrupados y económicos,
1048
+ gratis = modelo gratis disponible en tu cuenta). Los subagentes heredan el
1049
+ modelo de la sesión porque su frontmatter no fija \`model\`.
1050
+ `
1051
+
959
1052
  // D12: asset no soportado -> no se escribe y se avisa a stderr (no bloqueante).
960
1053
  function warnUnsupportedAsset(asset, agent) {
961
1054
  console.error(`skip ${asset}: not supported by host '${agent}'`)
@@ -985,7 +1078,11 @@ async function installAssetFiles(cat, projectDir, spec, host, srcDir = null) {
985
1078
  const base = e.name.slice(0, -MD_EXT.length)
986
1079
  const content = await readFile(join(origin, e.name), 'utf8')
987
1080
  const adapted = adaptFrontmatter(content, host, cat, base)
988
- const body = (host === 'copilot' && cat === 'agents' && base === 'orchestrator') ? adapted + COPILOT_MODEL_NOTE : adapted
1081
+ let body = adapted
1082
+ if (cat === 'agents' && base === 'orchestrator') {
1083
+ if (host === 'copilot') body += COPILOT_MODEL_NOTE
1084
+ else if (host === 'commandcode') body += COMMANDCODE_MODEL_NOTE
1085
+ }
989
1086
  await writeFile(join(destDir, `${base}${spec.ext}`), body)
990
1087
  }
991
1088
  }
@@ -1490,6 +1587,12 @@ async function upgradeCmd(args) {
1490
1587
  console.log(fixed.length
1491
1588
  ? ` ✔ MCP regenerados en ${file}: ${fixed.join(', ')}`
1492
1589
  : ` ✔ MCP sanos en ${file}, sin cambios`)
1590
+ } else if (agent === 'commandcode') {
1591
+ const { file, fixed } = await refreshCommandCodeMcp(projectDir)
1592
+ console.log(` ✔ skills actualizadas en ${skillsDir} (${ANCLETO_SKILLS.length} skills)`)
1593
+ console.log(fixed.length
1594
+ ? ` ✔ MCP regenerados en ${file}: ${fixed.join(', ')}`
1595
+ : ` ✔ MCP sanos en ${file}, sin cambios`)
1493
1596
  } else {
1494
1597
  console.log(` ✔ skills actualizadas en ${skillsDir} (${ANCLETO_SKILLS.length} skills)`)
1495
1598
  }
@@ -2265,8 +2368,9 @@ async function checkAgentsFrontmatter(cwd, dirRel, actual, expected, fileExt) {
2265
2368
  source = source.replace(/^model: .*$/m, `model: ${tierModelsMap[name]}`)
2266
2369
  }
2267
2370
  let expectedContent = adaptFrontmatter(source, host, 'agents', name)
2268
- if (host === 'copilot' && name === 'orchestrator') {
2269
- expectedContent += COPILOT_MODEL_NOTE
2371
+ if (name === 'orchestrator') {
2372
+ if (host === 'copilot') expectedContent += COPILOT_MODEL_NOTE
2373
+ else if (host === 'commandcode') expectedContent += COMMANDCODE_MODEL_NOTE
2270
2374
  }
2271
2375
  const installed = await readFile(join(cwd, dirRel, f), 'utf8')
2272
2376
  if (installed !== expectedContent) {
@@ -12,12 +12,12 @@ export const AGENT_ADAPTER_DROP = new Set(['mode', 'color', 'temperature', 'perm
12
12
  // Hosts conocidos por el modulo. Cualquier host fuera de este conjunto cae en la
13
13
  // rama `unknown` (no aborta; emite aviso a stderr y retorna el contenido sin
14
14
  // cambios).
15
- const KNOWN_HOSTS = new Set(['opencode', 'claude', 'vscode', 'antigravity', 'cursor', 'roo', 'copilot'])
15
+ const KNOWN_HOSTS = new Set(['opencode', 'claude', 'vscode', 'antigravity', 'cursor', 'roo', 'copilot', 'commandcode'])
16
16
 
17
17
  // Hosts para los que existe una transformacion documentada de agents
18
18
  // (distinta de identidad). Cursor y Roo viven fuera: no hay formato de frontmatter
19
19
  // propio, así que la adaptación es passthrough con aviso.
20
- const TRANSFORM_AGENTS = new Set(['claude', 'vscode', 'antigravity', 'copilot'])
20
+ const TRANSFORM_AGENTS = new Set(['claude', 'vscode', 'antigravity', 'copilot', 'commandcode'])
21
21
 
22
22
  // Tabla unica (fijada contra la tabla oficial de frontmatter de Custom
23
23
  // Subagents) de ids de Antigravity. Conjunto CERRADO: solo se emiten estos ids.
@@ -35,6 +35,31 @@ export const ANTIGRAVITY_TOOL_MAP = {
35
35
  todowrite: 'manage_task'
36
36
  }
37
37
 
38
+ // Tabla unica (fijada contra la documentacion oficial de Command Code) de ids de
39
+ // tools. Conjunto CERRADO: solo se emiten estos ids. Toda clave del mapa `tools`
40
+ // de opencode sin id aqui se omite con aviso a stderr (nunca en silencio).
41
+ export const COMMANDCODE_TOOL_MAP = {
42
+ read: 'read_file',
43
+ write: 'write_file',
44
+ edit: 'edit_file',
45
+ bash: 'shell_command',
46
+ grep: 'grep',
47
+ glob: 'glob',
48
+ webfetch: 'web_fetch',
49
+ websearch: 'web_search',
50
+ todowrite: 'todo_write'
51
+ }
52
+
53
+ // Tools de la memoria propia del framework. Command Code expone los servers MCP
54
+ // con la convencion documentada `mcp__<server>__<tool>`; `ancleto-memory` es el
55
+ // server que registra `buildDefaultMcp`. Estas claves del mapa `tools` de opencode
56
+ // son sus tools, asi que se emiten con el nombre MCP completo.
57
+ export const COMMANDCODE_MCP_TOOL_MAP = {
58
+ searchMemory: 'mcp__ancleto-memory__searchMemory',
59
+ recordRule: 'mcp__ancleto-memory__recordRule',
60
+ recordDecision: 'mcp__ancleto-memory__recordDecision'
61
+ }
62
+
38
63
  // Parser propio sin dependencias. Cada entrada conserva su clave y sus lineas
39
64
  // crudas (incluye block scalars y mapas anidados), preservando orden.
40
65
  export function parseFrontmatter(content) {
@@ -147,6 +172,45 @@ function adaptAntigravityFrontmatter(content, name) {
147
172
  return serializeFrontmatter(parsed)
148
173
  }
149
174
 
175
+ // Adapta el frontmatter de un agent de opencode al de Command Code. `name` (el
176
+ // host lo requiere; default filename) se inyecta desde el nombre del archivo.
177
+ // `description` se preserva. `mode`/`color`/`temperature`/`permission` se eliminan
178
+ // y `model` se omite: Command Code hereda el modelo de sesion cuando no se declara
179
+ // (nunca se mapea un id de `opencode-go/*`). `tools` sale como lista de ids
180
+ // verificados (o `"*"` si el origen no declara tools: el default de opencode es
181
+ // "todas" y el de Command Code es "ninguna"). Una clave sin id verificado se omite
182
+ // con aviso a stderr (nunca en silencio).
183
+ function adaptCommandCodeFrontmatter(content, name) {
184
+ const parsed = parseFrontmatter(content)
185
+ if (!parsed.hasFrontmatter) return content
186
+ const byKey = (k) => parsed.entries.find((e) => e.key === k)
187
+
188
+ const managed = new Set([...AGENT_ADAPTER_DROP, 'name', 'description', 'tools'])
189
+ const kept = parsed.entries.filter((e) => e.key && !managed.has(e.key))
190
+
191
+ const toolsEntry = byKey('tools')
192
+ const toolIds = []
193
+ if (toolsEntry) {
194
+ for (const { key, value } of parseToolFlags(toolsEntry)) {
195
+ if (value === 'false') continue
196
+ const id = COMMANDCODE_TOOL_MAP[key] || COMMANDCODE_MCP_TOOL_MAP[key]
197
+ if (id) {
198
+ toolIds.push(id)
199
+ continue
200
+ }
201
+ console.error(`skip tool '${key}': no verified Command Code id for agent '${name}'`)
202
+ }
203
+ }
204
+
205
+ const out = [{ key: 'name', lines: [`name: ${name}`] }]
206
+ const description = byKey('description')
207
+ if (description) out.push(description)
208
+ out.push(toolsEntry ? { key: 'tools', lines: [`tools: [${toolIds.join(', ')}]`] } : { key: 'tools', lines: ['tools: "*"'] })
209
+ out.push(...kept)
210
+ parsed.entries = out
211
+ return serializeFrontmatter(parsed)
212
+ }
213
+
150
214
  // Elimina las claves gestionadas por el adaptador (mode/color/temperature/
151
215
  // permission/model/tools) preservando el resto y el body. Para claude, vscode y
152
216
  // copilot. Si no hay frontmatter, retorna el contenido sin cambios.
@@ -166,6 +230,8 @@ function dropManagedKeys(content) {
166
230
  // opencode → identity (passthrough)
167
231
  // antigravity + agents → adaptAntigravityFrontmatter(content, name)
168
232
  // antigravity + (skills/cmds) → identity
233
+ // commandcode + agents → adaptCommandCodeFrontmatter(content, name)
234
+ // commandcode + (skills/cmds) → identity
169
235
  // claude/vscode/copilot + ags → dropManagedKeys(content)
170
236
  // claude/vscode/copilot + sk → identity
171
237
  // cursor/roo → identity + aviso stderr (sin formato documentado)
@@ -182,5 +248,6 @@ export function adaptFrontmatter(content, host, assetKind, name) {
182
248
  if (assetKind !== 'agents') return content
183
249
  if (!TRANSFORM_AGENTS.has(host)) return content // opencode u otro conocido sin transform
184
250
  if (host === 'antigravity') return adaptAntigravityFrontmatter(content, name)
251
+ if (host === 'commandcode') return adaptCommandCodeFrontmatter(content, name)
185
252
  return dropManagedKeys(content)
186
253
  }