@saulwade/swl-ses 1.5.0 → 1.5.2
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/CLAUDE.md +19 -2
- package/README.md +561 -561
- package/agentes/arquitecto-swl.md +33 -1
- package/agentes/nemesis-auditor-swl.md +59 -19
- package/bin/swl-mcp-server.js +214 -214
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/nemesis.md +230 -56
- package/gateway/lib/event-channel.js +191 -191
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/SKILL.md +171 -171
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/feynman-auditor-swl/SKILL.md +123 -123
- package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
- package/habilidades/harness-claude-code/SKILL.md +299 -299
- package/habilidades/infra-github-actions/SKILL.md +166 -166
- package/habilidades/legacy-code-rescue/SKILL.md +267 -267
- package/habilidades/manejo-errores/.evolved.json +8 -8
- package/habilidades/meta-skills-estandar/SKILL.md +225 -1
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
- package/habilidades/nemesis-evaluacion-json/SKILL.md +266 -0
- package/habilidades/nemesis-redistribuir/SKILL.md +341 -0
- package/habilidades/node-experto/SKILL.md +105 -4
- package/habilidades/patrones-python/SKILL.md +229 -229
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/planear-fase/SKILL.md +319 -319
- package/habilidades/protocolo-revision-swl/SKILL.md +350 -276
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -166
- package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
- package/habilidades/tdd-workflow/SKILL.md +150 -4
- package/habilidades/testing-python/SKILL.md +340 -340
- package/habilidades/verificar-trabajo/SKILL.md +8 -3
- package/habilidades/web-fetcher-routing/SKILL.md +75 -75
- package/hooks/check-update.js +31 -3
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/provenance-tracker.js +191 -191
- package/hooks/lib/rate-limit-tracker.js +253 -253
- package/hooks/lib/resource-quota.js +122 -122
- package/hooks/lib/retry-jitter.js +165 -165
- package/hooks/lib/security-net.js +201 -201
- package/hooks/lib/skill-auditor.js +588 -588
- package/hooks/lib/sync-status.js +228 -228
- package/hooks/lib/taint-tracker.js +107 -107
- package/hooks/lib/text-similarity.js +241 -241
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/registro-turnos.js +209 -209
- package/hooks/sugerir-regenerar-inventario.js +170 -170
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-memoria-hook.js +218 -218
- package/instintos/prompt-appendices.yaml +57 -57
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/modulos.json +1324 -1321
- package/manifiestos/skills-lock.json +1114 -1114
- package/package.json +2 -2
- package/plantillas/auditor-veto-template.md +105 -105
- package/plantillas/github-workflows/README.md +47 -47
- package/plantillas/github-workflows/release-please.yml +44 -44
- package/plantillas/github-workflows/swl-ci.yml +107 -107
- package/plantillas/github-workflows/swl-security.yml +51 -51
- package/plugin.json +353 -351
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/arreglar-al-detectar.md +147 -147
- package/reglas/fragmentos-compartidos.md +152 -152
- package/reglas/harness-claude-code.md +213 -213
- package/reglas/registro-componentes-nuevos.md +192 -0
- package/reglas/usar-context7.md +226 -226
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/actualizar.js +110 -1
- package/scripts/audit-tools/audit-history.js +330 -330
- package/scripts/audit-tools/bundle-tracker.js +290 -290
- package/scripts/audit-tools/canary-monitor.js +352 -352
- package/scripts/audit-tools/code-profiler.js +605 -605
- package/scripts/audit-tools/dep-doctor.js +320 -320
- package/scripts/audit-tools/env-validator.js +206 -206
- package/scripts/audit-tools/lib/fs-walk.js +48 -48
- package/scripts/audit-tools/lib/output.js +23 -23
- package/scripts/audit-tools/migration-checker.js +392 -392
- package/scripts/audit-tools/pentest-scanner.js +1436 -1436
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/derivar-feature-list.js +489 -489
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/doctor.js +58 -4
- package/scripts/field-report.js +199 -199
- package/scripts/generar-checklists-consolidados.js +273 -273
- package/scripts/generar-inventario.js +420 -420
- package/scripts/generar-matriz-lenguajes.js +271 -271
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/diary-entry.js +234 -234
- package/scripts/lib/eval-metrics-store.js +218 -218
- package/scripts/lib/eval-quality.js +171 -171
- package/scripts/lib/eval-schemas.js +144 -144
- package/scripts/lib/eval-self-correct.js +106 -106
- package/scripts/lib/eval-validator.js +185 -185
- package/scripts/lib/expandir-targets.js +71 -71
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/mcp_config.py +127 -0
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/lib/toml-merge.js +204 -204
- package/scripts/lib/transformadores/codex.js +375 -375
- package/scripts/lib/transformadores/cursor.js +359 -359
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-orchestrator.py +8 -18
- package/scripts/mcp-pool-manager.py +12 -23
- package/scripts/mcp-server/README.md +170 -170
- package/scripts/mcp-server/auth.js +105 -105
- package/scripts/mcp-server/cache.js +106 -106
- package/scripts/mcp-server/telemetry.js +78 -78
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +201 -201
- package/scripts/publicar.js +511 -511
- package/scripts/run-eval.js +141 -141
- package/scripts/validar-userland-vacio.js +110 -110
|
@@ -1,375 +1,375 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Transformador para OpenAI Codex CLI.
|
|
5
|
-
*
|
|
6
|
-
* Codex consume:
|
|
7
|
-
* - `AGENTS.md` por proyecto (cwd) Y/O `~/.codex/AGENTS.md` global.
|
|
8
|
-
* - `~/.codex/config.toml` para configurar MCP servers, sandbox y profiles.
|
|
9
|
-
* - NO carga skills, comandos slash propios (a 2026-05-15, no confirmado en docs oficiales),
|
|
10
|
-
* ni hooks como Claude Code los entiende.
|
|
11
|
-
*
|
|
12
|
-
* Estrategia v1.5.0 (ADR-0019):
|
|
13
|
-
* - `--global` → AGENTS.md va a `~/.codex/AGENTS.md`. Sub-fase 1 ADR-0019 punto 1.
|
|
14
|
-
* - `--local` → AGENTS.md va a `<proyecto>/AGENTS.md`. Default.
|
|
15
|
-
* - Bloque SWL delimitado por marcadores `<!-- SWL-BEGIN -->` / `<!-- SWL-END -->`
|
|
16
|
-
* para preservar contenido pre-existente del usuario en AGENTS.md (parche a la versión previa,
|
|
17
|
-
* que sobrescribía el archivo completo destruyendo notas del equipo).
|
|
18
|
-
* - `withMcp: true` autoregistra `swl-memory` en `~/.codex/config.toml` bajo
|
|
19
|
-
* `[mcp_servers.swl-memory]` mediante `lib/toml-merge.js` zero-deps.
|
|
20
|
-
* - Skills y comandos slash: descartados en este target. Documentado en INSTALACION.md §3.5.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
const path = require('path');
|
|
24
|
-
const TransformadorBase = require('./base');
|
|
25
|
-
const { upsertMcpServer } = require('../toml-merge');
|
|
26
|
-
|
|
27
|
-
class TransformadorCodex extends TransformadorBase {
|
|
28
|
-
/**
|
|
29
|
-
* Sub-fase 11 v1.5.0: Codex CLI SÍ acepta agentes individuales en formato
|
|
30
|
-
* TOML (developers.openai.com/codex/subagents). Path: `~/.codex/agents/<name>.toml`
|
|
31
|
-
* (global) o `.codex/agents/<name>.toml` (project).
|
|
32
|
-
*
|
|
33
|
-
* Campos del TOML:
|
|
34
|
-
* - name (required)
|
|
35
|
-
* - description (required)
|
|
36
|
-
* - developer_instructions (required) — multi-line literal del cuerpo Markdown
|
|
37
|
-
* - model (opcional) — heredado del frontmatter SWL si existe
|
|
38
|
-
* - sandbox_mode (opcional) — mapeado desde permisosEscritura=false → "read-only"
|
|
39
|
-
*
|
|
40
|
-
* El cuerpo del .md SWL completo (después del frontmatter) se inserta como
|
|
41
|
-
* developer_instructions usando TOML literal multi-line strings `'''...'''`
|
|
42
|
-
* para preservar el contenido sin escapes complejos.
|
|
43
|
-
*
|
|
44
|
-
* AGENTS.md sigue conteniendo tabla referencial (redundante pero útil como
|
|
45
|
-
* índice rápido). Sub-fase 11 es ADITIVA — no afecta el flujo existente.
|
|
46
|
-
*/
|
|
47
|
-
transformarAgente(contenidoMd, metadatos) {
|
|
48
|
-
const { frontmatter, cuerpo } = this.parsearFrontmatter(contenidoMd);
|
|
49
|
-
|
|
50
|
-
const name = frontmatter.name || (metadatos.nombreArchivo || '').replace(/\.md$/i, '');
|
|
51
|
-
if (!name) {
|
|
52
|
-
// Sin nombre, no podemos generar TOML válido — fallback a AGENTS.md consolidado.
|
|
53
|
-
return {
|
|
54
|
-
contenido: contenidoMd,
|
|
55
|
-
consolidar: true,
|
|
56
|
-
rutaRelativa: 'AGENTS.md',
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const description = String(frontmatter.description || '').replace(/\s+/g, ' ').trim();
|
|
61
|
-
const developerInstructions = String(cuerpo || '').trim();
|
|
62
|
-
|
|
63
|
-
const toml = this._construirTomlAgente({
|
|
64
|
-
name,
|
|
65
|
-
description,
|
|
66
|
-
developer_instructions: developerInstructions,
|
|
67
|
-
model: frontmatter.model || null,
|
|
68
|
-
sandbox_mode: (frontmatter.permisosEscritura === false) ? 'read-only' : null,
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const baseNombre = (metadatos.nombreArchivo || `${name}.md`).replace(/\.md$/i, '');
|
|
72
|
-
return {
|
|
73
|
-
contenido: toml,
|
|
74
|
-
rutaRelativa: `agents/${baseNombre}.toml`,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Serializa un agente Codex a TOML. Helper privado.
|
|
80
|
-
* Usa literal multi-line strings `'''...'''` para developer_instructions
|
|
81
|
-
* (no procesa escapes — preserva Markdown crudo). Si el cuerpo contiene
|
|
82
|
-
* `'''` literal, hace fallback a basic multi-line `"""..."""` con escape.
|
|
83
|
-
*/
|
|
84
|
-
_construirTomlAgente(campos) {
|
|
85
|
-
const lineas = [];
|
|
86
|
-
lineas.push(`# Generado por swl-ses — formato Codex agent TOML (Sub-fase 11 v1.5.0)`);
|
|
87
|
-
lineas.push(`# Fuente: developers.openai.com/codex/subagents`);
|
|
88
|
-
lineas.push('');
|
|
89
|
-
lineas.push(`name = ${this._tomlBasicString(campos.name)}`);
|
|
90
|
-
lineas.push(`description = ${this._tomlBasicString(campos.description)}`);
|
|
91
|
-
if (campos.model) lineas.push(`model = ${this._tomlBasicString(campos.model)}`);
|
|
92
|
-
if (campos.sandbox_mode) lineas.push(`sandbox_mode = ${this._tomlBasicString(campos.sandbox_mode)}`);
|
|
93
|
-
lineas.push('');
|
|
94
|
-
lineas.push(`developer_instructions = ${this._tomlMultiline(campos.developer_instructions)}`);
|
|
95
|
-
return lineas.join('\n') + '\n';
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
_tomlBasicString(s) {
|
|
99
|
-
const escapado = String(s)
|
|
100
|
-
.replace(/\\/g, '\\\\')
|
|
101
|
-
.replace(/"/g, '\\"')
|
|
102
|
-
.replace(/\n/g, '\\n')
|
|
103
|
-
.replace(/\r/g, '\\r')
|
|
104
|
-
.replace(/\t/g, '\\t');
|
|
105
|
-
return `"${escapado}"`;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
_tomlMultiline(s) {
|
|
109
|
-
const contenido = String(s);
|
|
110
|
-
if (!contenido.includes("'''")) {
|
|
111
|
-
// Literal multi-line: preserva contenido sin escapes.
|
|
112
|
-
return `'''\n${contenido}\n'''`;
|
|
113
|
-
}
|
|
114
|
-
// Fallback a basic multi-line con escape de \ y """.
|
|
115
|
-
const escapado = contenido.replace(/\\/g, '\\\\').replace(/"""/g, '\\"""');
|
|
116
|
-
return `"""\n${escapado}\n"""`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
transformarSkill(dirSkill, metadatos) {
|
|
120
|
-
// Sub-fase 10 v1.5.0: corrección del path. La doc oficial OpenAI
|
|
121
|
-
// (developers.openai.com/codex/skills) declara `$HOME/.agents/skills/` y
|
|
122
|
-
// `.agents/skills/` como ubicaciones válidas — NO `~/.codex/skills/`.
|
|
123
|
-
// El path `~/.codex/skills/` que apareció en Sub-fase 8 era de una sample
|
|
124
|
-
// sub-doc del repo (no canónica). Ahora apunta al path correcto vía dirBase.
|
|
125
|
-
const dirBaseGlobal = path.join(require('os').homedir(), '.agents');
|
|
126
|
-
return {
|
|
127
|
-
tipo: 'directorio',
|
|
128
|
-
rutaRelativa: `skills/${metadatos.nombreDirectorio}`,
|
|
129
|
-
dirBase: dirBaseGlobal,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
transformarRegla(contenidoMd, metadatos) {
|
|
134
|
-
return {
|
|
135
|
-
contenido: contenidoMd,
|
|
136
|
-
consolidar: true,
|
|
137
|
-
rutaRelativa: 'AGENTS.md',
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
transformarComando(contenidoMd, metadatos) {
|
|
142
|
-
// Codex NO soporta comandos slash custom desde filesystem (verificado
|
|
143
|
-
// 2026-05-15 en developers.openai.com/codex/cli/slash-commands — todos
|
|
144
|
-
// los /comandos son built-in). En Cursor el equivalente es Skills.
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Sub-fase 10 v1.5.0: transforma hooks SWL a formato Codex hooks.json.
|
|
150
|
-
*
|
|
151
|
-
* Schema Codex (developers.openai.com/codex/hooks):
|
|
152
|
-
* {
|
|
153
|
-
* "hooks": {
|
|
154
|
-
* "PreToolUse": [{ "matcher": "^Bash$", "hooks": [{ "type": "command", "command": "...", "timeout": 30 }] }]
|
|
155
|
-
* }
|
|
156
|
-
* }
|
|
157
|
-
*
|
|
158
|
-
* Eventos válidos (case-sensitive): SessionStart, PreToolUse, PermissionRequest,
|
|
159
|
-
* PostToolUse, UserPromptSubmit, Stop. `Notification` de Claude se descarta.
|
|
160
|
-
*
|
|
161
|
-
* @param {object} hooksConfig - Mapa { hookFile: { events: [...], matcher?, timeout? } }
|
|
162
|
-
* @param {object} [opciones] - { hookFiles?: string[] filtro, rutaHooksDir?: string base }
|
|
163
|
-
* @returns {object|null} { formato, destino, config, descartados }
|
|
164
|
-
*/
|
|
165
|
-
transformarHooks(hooksConfig, opciones) {
|
|
166
|
-
if (!hooksConfig || typeof hooksConfig !== 'object') return null;
|
|
167
|
-
const opts = opciones || {};
|
|
168
|
-
const hookFilesFiltro = Array.isArray(opts.hookFiles) ? new Set(opts.hookFiles) : null;
|
|
169
|
-
const rutaHooksDir = opts.rutaHooksDir || 'hooks';
|
|
170
|
-
|
|
171
|
-
const EVENTOS_VALIDOS = new Set([
|
|
172
|
-
'SessionStart', 'PreToolUse', 'PermissionRequest',
|
|
173
|
-
'PostToolUse', 'UserPromptSubmit', 'Stop',
|
|
174
|
-
]);
|
|
175
|
-
|
|
176
|
-
const grupos = {};
|
|
177
|
-
const descartados = [];
|
|
178
|
-
|
|
179
|
-
for (const [hookFile, def] of Object.entries(hooksConfig)) {
|
|
180
|
-
if (hookFilesFiltro && !hookFilesFiltro.has(hookFile)) continue;
|
|
181
|
-
if (!def || !Array.isArray(def.events)) continue;
|
|
182
|
-
|
|
183
|
-
for (const event of def.events) {
|
|
184
|
-
if (!EVENTOS_VALIDOS.has(event)) {
|
|
185
|
-
descartados.push({ hookFile, event, motivo: 'evento no soportado por Codex' });
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
if (!grupos[event]) grupos[event] = [];
|
|
189
|
-
const entry = {
|
|
190
|
-
hooks: [{
|
|
191
|
-
type: 'command',
|
|
192
|
-
command: `node ${rutaHooksDir}/${hookFile}`,
|
|
193
|
-
...(def.timeout ? { timeout: def.timeout } : {}),
|
|
194
|
-
}],
|
|
195
|
-
};
|
|
196
|
-
if (def.matcher) entry.matcher = def.matcher;
|
|
197
|
-
grupos[event].push(entry);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return {
|
|
202
|
-
formato: 'codex-hooks-json',
|
|
203
|
-
destino: 'hooks.json',
|
|
204
|
-
config: { hooks: grupos },
|
|
205
|
-
descartados,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Genera el bloque AGENTS.md.
|
|
211
|
-
*
|
|
212
|
-
* @param {object} contexto
|
|
213
|
-
* @param {string} contexto.version - Versión del paquete swl-ses
|
|
214
|
-
* @param {string} contexto.perfil - Perfil instalado (core, completo, etc.)
|
|
215
|
-
* @param {Array} contexto.agentes - Agentes a consolidar
|
|
216
|
-
* @param {Array} contexto.reglas - Reglas a consolidar
|
|
217
|
-
* @param {Array} contexto.skills - Skills (solo para tabla referencial)
|
|
218
|
-
* @param {boolean} [contexto.esGlobal] - Si true escribe en ~/.codex/, si no en cwd
|
|
219
|
-
* @param {string} [contexto.dirRuntimeGlobal] - Path absoluto de ~/.codex/ (de RUNTIMES.codex.global)
|
|
220
|
-
* @param {boolean} [contexto.withMcp] - Si true autoregistra swl-memory en config.toml
|
|
221
|
-
* @param {string} [contexto.swlBinPath] - Path absoluto al CLI swl-ses (para args del MCP)
|
|
222
|
-
* @returns {object} Objeto con contenido y metadata de escritura
|
|
223
|
-
*/
|
|
224
|
-
generarArchivoInstrucciones(contexto) {
|
|
225
|
-
const lineas = [];
|
|
226
|
-
const fecha = new Date().toISOString().split('T')[0];
|
|
227
|
-
|
|
228
|
-
lineas.push('## Sistema SWL — uso obligatorio (gestionado por swl-ses)');
|
|
229
|
-
lineas.push('');
|
|
230
|
-
lineas.push(`> Generado por swl-ses v${contexto.version} — ${fecha}`);
|
|
231
|
-
lineas.push(`> Perfil: \`${contexto.perfil || 'default'}\`. Scope: \`${contexto.esGlobal ? 'global (~/.codex/)' : 'proyecto'}\`.`);
|
|
232
|
-
lineas.push('> NO editar el contenido entre los marcadores SWL-BEGIN/END — regenerar con:');
|
|
233
|
-
lineas.push(`> \`npx @saulwade/swl-ses@latest install --target codex${contexto.esGlobal ? ' --global' : ''} --force\``);
|
|
234
|
-
lineas.push('');
|
|
235
|
-
|
|
236
|
-
// Reglas de máxima prioridad
|
|
237
|
-
lineas.push('### Reglas de máxima prioridad');
|
|
238
|
-
lineas.push('');
|
|
239
|
-
lineas.push('1. **Idioma: español de México** — respuestas, comentarios, commits, docs.');
|
|
240
|
-
lineas.push('2. **Investigar antes de editar** — leer el archivo completo antes de modificar.');
|
|
241
|
-
lineas.push('3. **Sistema SWL transversal** — la memoria SWL (aprendizajes, instintos, sesiones) está accesible vía MCP server `swl-memory` si está configurado. Ver sección "Memoria SWL" abajo.');
|
|
242
|
-
lineas.push('');
|
|
243
|
-
|
|
244
|
-
// Tabla de agentes (referencial — Codex no los carga individualmente)
|
|
245
|
-
if (contexto.agentes && contexto.agentes.length > 0) {
|
|
246
|
-
lineas.push('### Agentes disponibles (referencia)');
|
|
247
|
-
lineas.push('');
|
|
248
|
-
lineas.push(`> Total: ${contexto.agentes.length} agentes. Codex no carga agentes individuales — esta tabla es referencia operativa de qué rol invocar conceptualmente.`);
|
|
249
|
-
lineas.push('');
|
|
250
|
-
lineas.push('| Agente | Descripción |');
|
|
251
|
-
lineas.push('|--------|-------------|');
|
|
252
|
-
for (const ag of contexto.agentes) {
|
|
253
|
-
const { frontmatter } = this.parsearFrontmatter(ag.contenido || '');
|
|
254
|
-
const nombre = frontmatter.name || ag.nombre || 'sin-nombre';
|
|
255
|
-
const desc = String(frontmatter.description || '').replace(/\|/g, '\\|');
|
|
256
|
-
const descCorta = desc.length > 100 ? desc.slice(0, 97) + '...' : desc;
|
|
257
|
-
lineas.push(`| \`${nombre}\` | ${descCorta} |`);
|
|
258
|
-
}
|
|
259
|
-
lineas.push('');
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Reglas concatenadas
|
|
263
|
-
if (contexto.reglas && contexto.reglas.length > 0) {
|
|
264
|
-
lineas.push('### Reglas concatenadas');
|
|
265
|
-
lineas.push('');
|
|
266
|
-
for (const regla of contexto.reglas) {
|
|
267
|
-
lineas.push(regla.contenido);
|
|
268
|
-
lineas.push('');
|
|
269
|
-
lineas.push('---');
|
|
270
|
-
lineas.push('');
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Skills (tabla referencial)
|
|
275
|
-
if (contexto.skills && contexto.skills.length > 0) {
|
|
276
|
-
lineas.push('### Skills disponibles (referencia)');
|
|
277
|
-
lineas.push('');
|
|
278
|
-
lineas.push(`> Total: ${contexto.skills.length} skills. Codex no carga skills nativamente. Para invocarlos vía MCP, ver sección "Memoria SWL".`);
|
|
279
|
-
lineas.push('');
|
|
280
|
-
lineas.push('| Skill | Descripción |');
|
|
281
|
-
lineas.push('|-------|-------------|');
|
|
282
|
-
for (const sk of contexto.skills) {
|
|
283
|
-
const desc = String(sk.descripcion || '').replace(/\|/g, '\\|');
|
|
284
|
-
const descCorta = desc.length > 80 ? desc.slice(0, 77) + '...' : desc;
|
|
285
|
-
lineas.push(`| \`${sk.nombre}\` | ${descCorta} |`);
|
|
286
|
-
}
|
|
287
|
-
lineas.push('');
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Memoria SWL — MCP
|
|
291
|
-
lineas.push('### Memoria SWL (acceso vía MCP)');
|
|
292
|
-
lineas.push('');
|
|
293
|
-
if (contexto.withMcp) {
|
|
294
|
-
lineas.push('El servidor `swl-memory` está autoregistrado en `~/.codex/config.toml`. Codex puede consultar:');
|
|
295
|
-
lineas.push('');
|
|
296
|
-
lineas.push('- `swl_memory_search` — búsqueda híbrida sobre aprendizajes + sesiones + instintos.');
|
|
297
|
-
lineas.push('- `swl_aprendizajes_recientes` — últimos N aprendizajes del proyecto.');
|
|
298
|
-
lineas.push('- `swl_instintos_activos` — patrones validados con confianza efectiva ≥ umbral.');
|
|
299
|
-
lineas.push('');
|
|
300
|
-
lineas.push('Para verificar: `codex mcp list` debe mostrar `swl-memory`.');
|
|
301
|
-
} else {
|
|
302
|
-
lineas.push('Para habilitar la memoria SWL accesible desde Codex, reinstalar con `--with-mcp`:');
|
|
303
|
-
lineas.push('');
|
|
304
|
-
lineas.push('```bash');
|
|
305
|
-
lineas.push(`npx @saulwade/swl-ses@latest install --target codex${contexto.esGlobal ? ' --global' : ''} --with-mcp --force`);
|
|
306
|
-
lineas.push('```');
|
|
307
|
-
}
|
|
308
|
-
lineas.push('');
|
|
309
|
-
|
|
310
|
-
// Convenciones de commits
|
|
311
|
-
lineas.push('### Convenciones de commits');
|
|
312
|
-
lineas.push('');
|
|
313
|
-
lineas.push('- SemVer estricto.');
|
|
314
|
-
lineas.push('- Mensajes en español: `<tipo>(<scope>): <descripción imperativa>`.');
|
|
315
|
-
lineas.push('- Sin `Co-Authored-By` ni referencias a IA (regla global del usuario).');
|
|
316
|
-
lineas.push('');
|
|
317
|
-
|
|
318
|
-
let cuerpo = lineas.join('\n');
|
|
319
|
-
|
|
320
|
-
// Limitar tamaño total a 50 KB para no saturar el contexto de Codex
|
|
321
|
-
const MAX_SIZE = 50 * 1024;
|
|
322
|
-
if (Buffer.byteLength(cuerpo, 'utf-8') > MAX_SIZE) {
|
|
323
|
-
const aviso = '\n\n> **Nota**: contenido truncado por límite de 50 KB. Instalar con perfil más específico (no `completo`) para reducir tamaño.\n';
|
|
324
|
-
cuerpo = cuerpo.slice(0, MAX_SIZE - 200) + aviso;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Resolver dirBase según scope
|
|
328
|
-
const dirBase = contexto.esGlobal && contexto.dirRuntimeGlobal
|
|
329
|
-
? contexto.dirRuntimeGlobal
|
|
330
|
-
: '.';
|
|
331
|
-
|
|
332
|
-
// Si el caller pidió MCP, registrar el server en config.toml ANTES de devolver
|
|
333
|
-
// la metadata de escritura del AGENTS.md. Side effect — pero idempotente y zero-impact
|
|
334
|
-
// si la escritura del AGENTS.md falla después.
|
|
335
|
-
if (contexto.withMcp) {
|
|
336
|
-
try {
|
|
337
|
-
const configToml = path.join(contexto.dirRuntimeGlobal, 'config.toml');
|
|
338
|
-
const swlBin = contexto.swlBinPath || 'swl-mcp-server';
|
|
339
|
-
upsertMcpServer(configToml, 'swl-memory', {
|
|
340
|
-
command: 'node',
|
|
341
|
-
args: [swlBin],
|
|
342
|
-
env: {
|
|
343
|
-
SWL_MCP_BASE_DIR: process.cwd(),
|
|
344
|
-
},
|
|
345
|
-
});
|
|
346
|
-
} catch (err) {
|
|
347
|
-
// No bloquear instalación si el registro del MCP falla — solo loggear
|
|
348
|
-
// El usuario verá el error y puede registrar manualmente con `codex mcp add`.
|
|
349
|
-
console.warn(`[codex] No se pudo registrar swl-memory en config.toml: ${err.message}`);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return {
|
|
354
|
-
contenido: cuerpo,
|
|
355
|
-
rutaRelativa: 'AGENTS.md',
|
|
356
|
-
dirBase,
|
|
357
|
-
// Usar merge con marcadores para preservar contenido del usuario en AGENTS.md
|
|
358
|
-
merge: {
|
|
359
|
-
tipo: 'marcadores',
|
|
360
|
-
beginTag: `<!-- SWL-BEGIN v${contexto.version} — bloque gestionado por swl-ses, no editar -->`,
|
|
361
|
-
endTag: '<!-- SWL-END -->',
|
|
362
|
-
beginPrefix: '<!-- SWL-BEGIN',
|
|
363
|
-
},
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
soportaTipo(tipo) {
|
|
368
|
-
// Sub-fase 8 (v1.5.0): Codex ahora soporta habilidades nativamente
|
|
369
|
-
// (~/.codex/skills/<name>/SKILL.md). Agentes y reglas siguen consolidados
|
|
370
|
-
// en AGENTS.md por limitación de Codex (no expone agents/ individual).
|
|
371
|
-
return ['agentes', 'habilidades', 'reglas'].includes(tipo);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
module.exports = TransformadorCodex;
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Transformador para OpenAI Codex CLI.
|
|
5
|
+
*
|
|
6
|
+
* Codex consume:
|
|
7
|
+
* - `AGENTS.md` por proyecto (cwd) Y/O `~/.codex/AGENTS.md` global.
|
|
8
|
+
* - `~/.codex/config.toml` para configurar MCP servers, sandbox y profiles.
|
|
9
|
+
* - NO carga skills, comandos slash propios (a 2026-05-15, no confirmado en docs oficiales),
|
|
10
|
+
* ni hooks como Claude Code los entiende.
|
|
11
|
+
*
|
|
12
|
+
* Estrategia v1.5.0 (ADR-0019):
|
|
13
|
+
* - `--global` → AGENTS.md va a `~/.codex/AGENTS.md`. Sub-fase 1 ADR-0019 punto 1.
|
|
14
|
+
* - `--local` → AGENTS.md va a `<proyecto>/AGENTS.md`. Default.
|
|
15
|
+
* - Bloque SWL delimitado por marcadores `<!-- SWL-BEGIN -->` / `<!-- SWL-END -->`
|
|
16
|
+
* para preservar contenido pre-existente del usuario en AGENTS.md (parche a la versión previa,
|
|
17
|
+
* que sobrescribía el archivo completo destruyendo notas del equipo).
|
|
18
|
+
* - `withMcp: true` autoregistra `swl-memory` en `~/.codex/config.toml` bajo
|
|
19
|
+
* `[mcp_servers.swl-memory]` mediante `lib/toml-merge.js` zero-deps.
|
|
20
|
+
* - Skills y comandos slash: descartados en este target. Documentado en INSTALACION.md §3.5.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const TransformadorBase = require('./base');
|
|
25
|
+
const { upsertMcpServer } = require('../toml-merge');
|
|
26
|
+
|
|
27
|
+
class TransformadorCodex extends TransformadorBase {
|
|
28
|
+
/**
|
|
29
|
+
* Sub-fase 11 v1.5.0: Codex CLI SÍ acepta agentes individuales en formato
|
|
30
|
+
* TOML (developers.openai.com/codex/subagents). Path: `~/.codex/agents/<name>.toml`
|
|
31
|
+
* (global) o `.codex/agents/<name>.toml` (project).
|
|
32
|
+
*
|
|
33
|
+
* Campos del TOML:
|
|
34
|
+
* - name (required)
|
|
35
|
+
* - description (required)
|
|
36
|
+
* - developer_instructions (required) — multi-line literal del cuerpo Markdown
|
|
37
|
+
* - model (opcional) — heredado del frontmatter SWL si existe
|
|
38
|
+
* - sandbox_mode (opcional) — mapeado desde permisosEscritura=false → "read-only"
|
|
39
|
+
*
|
|
40
|
+
* El cuerpo del .md SWL completo (después del frontmatter) se inserta como
|
|
41
|
+
* developer_instructions usando TOML literal multi-line strings `'''...'''`
|
|
42
|
+
* para preservar el contenido sin escapes complejos.
|
|
43
|
+
*
|
|
44
|
+
* AGENTS.md sigue conteniendo tabla referencial (redundante pero útil como
|
|
45
|
+
* índice rápido). Sub-fase 11 es ADITIVA — no afecta el flujo existente.
|
|
46
|
+
*/
|
|
47
|
+
transformarAgente(contenidoMd, metadatos) {
|
|
48
|
+
const { frontmatter, cuerpo } = this.parsearFrontmatter(contenidoMd);
|
|
49
|
+
|
|
50
|
+
const name = frontmatter.name || (metadatos.nombreArchivo || '').replace(/\.md$/i, '');
|
|
51
|
+
if (!name) {
|
|
52
|
+
// Sin nombre, no podemos generar TOML válido — fallback a AGENTS.md consolidado.
|
|
53
|
+
return {
|
|
54
|
+
contenido: contenidoMd,
|
|
55
|
+
consolidar: true,
|
|
56
|
+
rutaRelativa: 'AGENTS.md',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const description = String(frontmatter.description || '').replace(/\s+/g, ' ').trim();
|
|
61
|
+
const developerInstructions = String(cuerpo || '').trim();
|
|
62
|
+
|
|
63
|
+
const toml = this._construirTomlAgente({
|
|
64
|
+
name,
|
|
65
|
+
description,
|
|
66
|
+
developer_instructions: developerInstructions,
|
|
67
|
+
model: frontmatter.model || null,
|
|
68
|
+
sandbox_mode: (frontmatter.permisosEscritura === false) ? 'read-only' : null,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const baseNombre = (metadatos.nombreArchivo || `${name}.md`).replace(/\.md$/i, '');
|
|
72
|
+
return {
|
|
73
|
+
contenido: toml,
|
|
74
|
+
rutaRelativa: `agents/${baseNombre}.toml`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Serializa un agente Codex a TOML. Helper privado.
|
|
80
|
+
* Usa literal multi-line strings `'''...'''` para developer_instructions
|
|
81
|
+
* (no procesa escapes — preserva Markdown crudo). Si el cuerpo contiene
|
|
82
|
+
* `'''` literal, hace fallback a basic multi-line `"""..."""` con escape.
|
|
83
|
+
*/
|
|
84
|
+
_construirTomlAgente(campos) {
|
|
85
|
+
const lineas = [];
|
|
86
|
+
lineas.push(`# Generado por swl-ses — formato Codex agent TOML (Sub-fase 11 v1.5.0)`);
|
|
87
|
+
lineas.push(`# Fuente: developers.openai.com/codex/subagents`);
|
|
88
|
+
lineas.push('');
|
|
89
|
+
lineas.push(`name = ${this._tomlBasicString(campos.name)}`);
|
|
90
|
+
lineas.push(`description = ${this._tomlBasicString(campos.description)}`);
|
|
91
|
+
if (campos.model) lineas.push(`model = ${this._tomlBasicString(campos.model)}`);
|
|
92
|
+
if (campos.sandbox_mode) lineas.push(`sandbox_mode = ${this._tomlBasicString(campos.sandbox_mode)}`);
|
|
93
|
+
lineas.push('');
|
|
94
|
+
lineas.push(`developer_instructions = ${this._tomlMultiline(campos.developer_instructions)}`);
|
|
95
|
+
return lineas.join('\n') + '\n';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
_tomlBasicString(s) {
|
|
99
|
+
const escapado = String(s)
|
|
100
|
+
.replace(/\\/g, '\\\\')
|
|
101
|
+
.replace(/"/g, '\\"')
|
|
102
|
+
.replace(/\n/g, '\\n')
|
|
103
|
+
.replace(/\r/g, '\\r')
|
|
104
|
+
.replace(/\t/g, '\\t');
|
|
105
|
+
return `"${escapado}"`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_tomlMultiline(s) {
|
|
109
|
+
const contenido = String(s);
|
|
110
|
+
if (!contenido.includes("'''")) {
|
|
111
|
+
// Literal multi-line: preserva contenido sin escapes.
|
|
112
|
+
return `'''\n${contenido}\n'''`;
|
|
113
|
+
}
|
|
114
|
+
// Fallback a basic multi-line con escape de \ y """.
|
|
115
|
+
const escapado = contenido.replace(/\\/g, '\\\\').replace(/"""/g, '\\"""');
|
|
116
|
+
return `"""\n${escapado}\n"""`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
transformarSkill(dirSkill, metadatos) {
|
|
120
|
+
// Sub-fase 10 v1.5.0: corrección del path. La doc oficial OpenAI
|
|
121
|
+
// (developers.openai.com/codex/skills) declara `$HOME/.agents/skills/` y
|
|
122
|
+
// `.agents/skills/` como ubicaciones válidas — NO `~/.codex/skills/`.
|
|
123
|
+
// El path `~/.codex/skills/` que apareció en Sub-fase 8 era de una sample
|
|
124
|
+
// sub-doc del repo (no canónica). Ahora apunta al path correcto vía dirBase.
|
|
125
|
+
const dirBaseGlobal = path.join(require('os').homedir(), '.agents');
|
|
126
|
+
return {
|
|
127
|
+
tipo: 'directorio',
|
|
128
|
+
rutaRelativa: `skills/${metadatos.nombreDirectorio}`,
|
|
129
|
+
dirBase: dirBaseGlobal,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
transformarRegla(contenidoMd, metadatos) {
|
|
134
|
+
return {
|
|
135
|
+
contenido: contenidoMd,
|
|
136
|
+
consolidar: true,
|
|
137
|
+
rutaRelativa: 'AGENTS.md',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
transformarComando(contenidoMd, metadatos) {
|
|
142
|
+
// Codex NO soporta comandos slash custom desde filesystem (verificado
|
|
143
|
+
// 2026-05-15 en developers.openai.com/codex/cli/slash-commands — todos
|
|
144
|
+
// los /comandos son built-in). En Cursor el equivalente es Skills.
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Sub-fase 10 v1.5.0: transforma hooks SWL a formato Codex hooks.json.
|
|
150
|
+
*
|
|
151
|
+
* Schema Codex (developers.openai.com/codex/hooks):
|
|
152
|
+
* {
|
|
153
|
+
* "hooks": {
|
|
154
|
+
* "PreToolUse": [{ "matcher": "^Bash$", "hooks": [{ "type": "command", "command": "...", "timeout": 30 }] }]
|
|
155
|
+
* }
|
|
156
|
+
* }
|
|
157
|
+
*
|
|
158
|
+
* Eventos válidos (case-sensitive): SessionStart, PreToolUse, PermissionRequest,
|
|
159
|
+
* PostToolUse, UserPromptSubmit, Stop. `Notification` de Claude se descarta.
|
|
160
|
+
*
|
|
161
|
+
* @param {object} hooksConfig - Mapa { hookFile: { events: [...], matcher?, timeout? } }
|
|
162
|
+
* @param {object} [opciones] - { hookFiles?: string[] filtro, rutaHooksDir?: string base }
|
|
163
|
+
* @returns {object|null} { formato, destino, config, descartados }
|
|
164
|
+
*/
|
|
165
|
+
transformarHooks(hooksConfig, opciones) {
|
|
166
|
+
if (!hooksConfig || typeof hooksConfig !== 'object') return null;
|
|
167
|
+
const opts = opciones || {};
|
|
168
|
+
const hookFilesFiltro = Array.isArray(opts.hookFiles) ? new Set(opts.hookFiles) : null;
|
|
169
|
+
const rutaHooksDir = opts.rutaHooksDir || 'hooks';
|
|
170
|
+
|
|
171
|
+
const EVENTOS_VALIDOS = new Set([
|
|
172
|
+
'SessionStart', 'PreToolUse', 'PermissionRequest',
|
|
173
|
+
'PostToolUse', 'UserPromptSubmit', 'Stop',
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
const grupos = {};
|
|
177
|
+
const descartados = [];
|
|
178
|
+
|
|
179
|
+
for (const [hookFile, def] of Object.entries(hooksConfig)) {
|
|
180
|
+
if (hookFilesFiltro && !hookFilesFiltro.has(hookFile)) continue;
|
|
181
|
+
if (!def || !Array.isArray(def.events)) continue;
|
|
182
|
+
|
|
183
|
+
for (const event of def.events) {
|
|
184
|
+
if (!EVENTOS_VALIDOS.has(event)) {
|
|
185
|
+
descartados.push({ hookFile, event, motivo: 'evento no soportado por Codex' });
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (!grupos[event]) grupos[event] = [];
|
|
189
|
+
const entry = {
|
|
190
|
+
hooks: [{
|
|
191
|
+
type: 'command',
|
|
192
|
+
command: `node ${rutaHooksDir}/${hookFile}`,
|
|
193
|
+
...(def.timeout ? { timeout: def.timeout } : {}),
|
|
194
|
+
}],
|
|
195
|
+
};
|
|
196
|
+
if (def.matcher) entry.matcher = def.matcher;
|
|
197
|
+
grupos[event].push(entry);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
formato: 'codex-hooks-json',
|
|
203
|
+
destino: 'hooks.json',
|
|
204
|
+
config: { hooks: grupos },
|
|
205
|
+
descartados,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Genera el bloque AGENTS.md.
|
|
211
|
+
*
|
|
212
|
+
* @param {object} contexto
|
|
213
|
+
* @param {string} contexto.version - Versión del paquete swl-ses
|
|
214
|
+
* @param {string} contexto.perfil - Perfil instalado (core, completo, etc.)
|
|
215
|
+
* @param {Array} contexto.agentes - Agentes a consolidar
|
|
216
|
+
* @param {Array} contexto.reglas - Reglas a consolidar
|
|
217
|
+
* @param {Array} contexto.skills - Skills (solo para tabla referencial)
|
|
218
|
+
* @param {boolean} [contexto.esGlobal] - Si true escribe en ~/.codex/, si no en cwd
|
|
219
|
+
* @param {string} [contexto.dirRuntimeGlobal] - Path absoluto de ~/.codex/ (de RUNTIMES.codex.global)
|
|
220
|
+
* @param {boolean} [contexto.withMcp] - Si true autoregistra swl-memory en config.toml
|
|
221
|
+
* @param {string} [contexto.swlBinPath] - Path absoluto al CLI swl-ses (para args del MCP)
|
|
222
|
+
* @returns {object} Objeto con contenido y metadata de escritura
|
|
223
|
+
*/
|
|
224
|
+
generarArchivoInstrucciones(contexto) {
|
|
225
|
+
const lineas = [];
|
|
226
|
+
const fecha = new Date().toISOString().split('T')[0];
|
|
227
|
+
|
|
228
|
+
lineas.push('## Sistema SWL — uso obligatorio (gestionado por swl-ses)');
|
|
229
|
+
lineas.push('');
|
|
230
|
+
lineas.push(`> Generado por swl-ses v${contexto.version} — ${fecha}`);
|
|
231
|
+
lineas.push(`> Perfil: \`${contexto.perfil || 'default'}\`. Scope: \`${contexto.esGlobal ? 'global (~/.codex/)' : 'proyecto'}\`.`);
|
|
232
|
+
lineas.push('> NO editar el contenido entre los marcadores SWL-BEGIN/END — regenerar con:');
|
|
233
|
+
lineas.push(`> \`npx @saulwade/swl-ses@latest install --target codex${contexto.esGlobal ? ' --global' : ''} --force\``);
|
|
234
|
+
lineas.push('');
|
|
235
|
+
|
|
236
|
+
// Reglas de máxima prioridad
|
|
237
|
+
lineas.push('### Reglas de máxima prioridad');
|
|
238
|
+
lineas.push('');
|
|
239
|
+
lineas.push('1. **Idioma: español de México** — respuestas, comentarios, commits, docs.');
|
|
240
|
+
lineas.push('2. **Investigar antes de editar** — leer el archivo completo antes de modificar.');
|
|
241
|
+
lineas.push('3. **Sistema SWL transversal** — la memoria SWL (aprendizajes, instintos, sesiones) está accesible vía MCP server `swl-memory` si está configurado. Ver sección "Memoria SWL" abajo.');
|
|
242
|
+
lineas.push('');
|
|
243
|
+
|
|
244
|
+
// Tabla de agentes (referencial — Codex no los carga individualmente)
|
|
245
|
+
if (contexto.agentes && contexto.agentes.length > 0) {
|
|
246
|
+
lineas.push('### Agentes disponibles (referencia)');
|
|
247
|
+
lineas.push('');
|
|
248
|
+
lineas.push(`> Total: ${contexto.agentes.length} agentes. Codex no carga agentes individuales — esta tabla es referencia operativa de qué rol invocar conceptualmente.`);
|
|
249
|
+
lineas.push('');
|
|
250
|
+
lineas.push('| Agente | Descripción |');
|
|
251
|
+
lineas.push('|--------|-------------|');
|
|
252
|
+
for (const ag of contexto.agentes) {
|
|
253
|
+
const { frontmatter } = this.parsearFrontmatter(ag.contenido || '');
|
|
254
|
+
const nombre = frontmatter.name || ag.nombre || 'sin-nombre';
|
|
255
|
+
const desc = String(frontmatter.description || '').replace(/\|/g, '\\|');
|
|
256
|
+
const descCorta = desc.length > 100 ? desc.slice(0, 97) + '...' : desc;
|
|
257
|
+
lineas.push(`| \`${nombre}\` | ${descCorta} |`);
|
|
258
|
+
}
|
|
259
|
+
lineas.push('');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Reglas concatenadas
|
|
263
|
+
if (contexto.reglas && contexto.reglas.length > 0) {
|
|
264
|
+
lineas.push('### Reglas concatenadas');
|
|
265
|
+
lineas.push('');
|
|
266
|
+
for (const regla of contexto.reglas) {
|
|
267
|
+
lineas.push(regla.contenido);
|
|
268
|
+
lineas.push('');
|
|
269
|
+
lineas.push('---');
|
|
270
|
+
lineas.push('');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Skills (tabla referencial)
|
|
275
|
+
if (contexto.skills && contexto.skills.length > 0) {
|
|
276
|
+
lineas.push('### Skills disponibles (referencia)');
|
|
277
|
+
lineas.push('');
|
|
278
|
+
lineas.push(`> Total: ${contexto.skills.length} skills. Codex no carga skills nativamente. Para invocarlos vía MCP, ver sección "Memoria SWL".`);
|
|
279
|
+
lineas.push('');
|
|
280
|
+
lineas.push('| Skill | Descripción |');
|
|
281
|
+
lineas.push('|-------|-------------|');
|
|
282
|
+
for (const sk of contexto.skills) {
|
|
283
|
+
const desc = String(sk.descripcion || '').replace(/\|/g, '\\|');
|
|
284
|
+
const descCorta = desc.length > 80 ? desc.slice(0, 77) + '...' : desc;
|
|
285
|
+
lineas.push(`| \`${sk.nombre}\` | ${descCorta} |`);
|
|
286
|
+
}
|
|
287
|
+
lineas.push('');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Memoria SWL — MCP
|
|
291
|
+
lineas.push('### Memoria SWL (acceso vía MCP)');
|
|
292
|
+
lineas.push('');
|
|
293
|
+
if (contexto.withMcp) {
|
|
294
|
+
lineas.push('El servidor `swl-memory` está autoregistrado en `~/.codex/config.toml`. Codex puede consultar:');
|
|
295
|
+
lineas.push('');
|
|
296
|
+
lineas.push('- `swl_memory_search` — búsqueda híbrida sobre aprendizajes + sesiones + instintos.');
|
|
297
|
+
lineas.push('- `swl_aprendizajes_recientes` — últimos N aprendizajes del proyecto.');
|
|
298
|
+
lineas.push('- `swl_instintos_activos` — patrones validados con confianza efectiva ≥ umbral.');
|
|
299
|
+
lineas.push('');
|
|
300
|
+
lineas.push('Para verificar: `codex mcp list` debe mostrar `swl-memory`.');
|
|
301
|
+
} else {
|
|
302
|
+
lineas.push('Para habilitar la memoria SWL accesible desde Codex, reinstalar con `--with-mcp`:');
|
|
303
|
+
lineas.push('');
|
|
304
|
+
lineas.push('```bash');
|
|
305
|
+
lineas.push(`npx @saulwade/swl-ses@latest install --target codex${contexto.esGlobal ? ' --global' : ''} --with-mcp --force`);
|
|
306
|
+
lineas.push('```');
|
|
307
|
+
}
|
|
308
|
+
lineas.push('');
|
|
309
|
+
|
|
310
|
+
// Convenciones de commits
|
|
311
|
+
lineas.push('### Convenciones de commits');
|
|
312
|
+
lineas.push('');
|
|
313
|
+
lineas.push('- SemVer estricto.');
|
|
314
|
+
lineas.push('- Mensajes en español: `<tipo>(<scope>): <descripción imperativa>`.');
|
|
315
|
+
lineas.push('- Sin `Co-Authored-By` ni referencias a IA (regla global del usuario).');
|
|
316
|
+
lineas.push('');
|
|
317
|
+
|
|
318
|
+
let cuerpo = lineas.join('\n');
|
|
319
|
+
|
|
320
|
+
// Limitar tamaño total a 50 KB para no saturar el contexto de Codex
|
|
321
|
+
const MAX_SIZE = 50 * 1024;
|
|
322
|
+
if (Buffer.byteLength(cuerpo, 'utf-8') > MAX_SIZE) {
|
|
323
|
+
const aviso = '\n\n> **Nota**: contenido truncado por límite de 50 KB. Instalar con perfil más específico (no `completo`) para reducir tamaño.\n';
|
|
324
|
+
cuerpo = cuerpo.slice(0, MAX_SIZE - 200) + aviso;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Resolver dirBase según scope
|
|
328
|
+
const dirBase = contexto.esGlobal && contexto.dirRuntimeGlobal
|
|
329
|
+
? contexto.dirRuntimeGlobal
|
|
330
|
+
: '.';
|
|
331
|
+
|
|
332
|
+
// Si el caller pidió MCP, registrar el server en config.toml ANTES de devolver
|
|
333
|
+
// la metadata de escritura del AGENTS.md. Side effect — pero idempotente y zero-impact
|
|
334
|
+
// si la escritura del AGENTS.md falla después.
|
|
335
|
+
if (contexto.withMcp) {
|
|
336
|
+
try {
|
|
337
|
+
const configToml = path.join(contexto.dirRuntimeGlobal, 'config.toml');
|
|
338
|
+
const swlBin = contexto.swlBinPath || 'swl-mcp-server';
|
|
339
|
+
upsertMcpServer(configToml, 'swl-memory', {
|
|
340
|
+
command: 'node',
|
|
341
|
+
args: [swlBin],
|
|
342
|
+
env: {
|
|
343
|
+
SWL_MCP_BASE_DIR: process.cwd(),
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
} catch (err) {
|
|
347
|
+
// No bloquear instalación si el registro del MCP falla — solo loggear
|
|
348
|
+
// El usuario verá el error y puede registrar manualmente con `codex mcp add`.
|
|
349
|
+
console.warn(`[codex] No se pudo registrar swl-memory en config.toml: ${err.message}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
contenido: cuerpo,
|
|
355
|
+
rutaRelativa: 'AGENTS.md',
|
|
356
|
+
dirBase,
|
|
357
|
+
// Usar merge con marcadores para preservar contenido del usuario en AGENTS.md
|
|
358
|
+
merge: {
|
|
359
|
+
tipo: 'marcadores',
|
|
360
|
+
beginTag: `<!-- SWL-BEGIN v${contexto.version} — bloque gestionado por swl-ses, no editar -->`,
|
|
361
|
+
endTag: '<!-- SWL-END -->',
|
|
362
|
+
beginPrefix: '<!-- SWL-BEGIN',
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
soportaTipo(tipo) {
|
|
368
|
+
// Sub-fase 8 (v1.5.0): Codex ahora soporta habilidades nativamente
|
|
369
|
+
// (~/.codex/skills/<name>/SKILL.md). Agentes y reglas siguen consolidados
|
|
370
|
+
// en AGENTS.md por limitación de Codex (no expone agents/ individual).
|
|
371
|
+
return ['agentes', 'habilidades', 'reglas'].includes(tipo);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
module.exports = TransformadorCodex;
|