@saulwade/swl-ses 1.6.1 → 1.6.5

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.
Files changed (85) hide show
  1. package/CLAUDE.md +3 -3
  2. package/README.md +4 -4
  3. package/agentes/_intent-spec.md +73 -0
  4. package/agentes/auto-evolucion-swl.md +24 -0
  5. package/agentes/cloud-infra-swl.md +25 -0
  6. package/agentes/datos-swl.md +23 -0
  7. package/agentes/devops-ci-swl.md +24 -0
  8. package/agentes/gh-fix-ci-swl.md +275 -0
  9. package/agentes/migrador-swl.md +22 -0
  10. package/agentes/nemesis-auditor-swl.md +90 -1
  11. package/agentes/pagos-swl.md +25 -0
  12. package/agentes/release-manager-swl.md +24 -0
  13. package/agentes/sre-swl.md +24 -0
  14. package/comandos/swl/exportar-vault.md +106 -14
  15. package/comandos/swl/nemesis.md +70 -3
  16. package/comandos/swl/planear-fase.md +16 -0
  17. package/comandos/swl/release.md +62 -2
  18. package/comandos/swl/salud.md +32 -0
  19. package/comandos/swl/verificar.md +116 -2
  20. package/habilidades/agent-browser/SKILL.md +111 -4
  21. package/habilidades/agent-deep-links/SKILL.md +148 -0
  22. package/habilidades/aprender-de-git-diff/SKILL.md +288 -0
  23. package/habilidades/backend-async-postgres-testing/SKILL.md +215 -0
  24. package/habilidades/backend-error-design/SKILL.md +221 -0
  25. package/habilidades/browser-interaction-patterns/SKILL.md +514 -0
  26. package/habilidades/browser-research-domains/SKILL.md +635 -0
  27. package/habilidades/changelog-generator/SKILL.md +172 -0
  28. package/habilidades/changelog-generator/scripts/parse-commits.js +354 -0
  29. package/habilidades/devsecops-pipeline-security/SKILL.md +3 -0
  30. package/habilidades/diseno-herramientas-agente/SKILL.md +17 -1
  31. package/habilidades/fastapi-experto/SKILL.md +49 -4
  32. package/habilidades/harness-claude-code/SKILL.md +4 -1
  33. package/habilidades/meta-skills-estandar/SKILL.md +6 -0
  34. package/habilidades/meta-skills-estandar/recursos/skill-judge-rubrica.md +281 -0
  35. package/habilidades/postgresql-experto/SKILL.md +80 -4
  36. package/habilidades/proceso-autoverificacion-evidencias/SKILL.md +258 -0
  37. package/habilidades/proceso-confianza-pre-implementacion/SKILL.md +246 -0
  38. package/habilidades/proceso-ddia-fundamentos/SKILL.md +255 -0
  39. package/habilidades/proceso-ddia-streaming/SKILL.md +231 -0
  40. package/habilidades/proceso-discovery-machote/SKILL.md +157 -0
  41. package/habilidades/proceso-intent-engineering/SKILL.md +269 -0
  42. package/habilidades/proceso-modular-split/SKILL.md +256 -0
  43. package/habilidades/reducir-entropia/SKILL.md +219 -0
  44. package/habilidades/tdd-workflow/SKILL.md +12 -5
  45. package/hooks/extraccion-aprendizajes.js +8 -0
  46. package/hooks/lib/deep-links.js +185 -0
  47. package/hooks/lib/evolution-tracker.js +115 -18
  48. package/hooks/lib/gateway-notify.js +70 -7
  49. package/hooks/lib/task-budget.js +218 -0
  50. package/hooks/validar-intent-spec.js +222 -0
  51. package/manifiestos/hooks-config.json +9 -0
  52. package/manifiestos/modulos.json +22 -3
  53. package/manifiestos/skills-lock.json +1247 -1142
  54. package/package.json +3 -3
  55. package/plugin.json +18 -2
  56. package/reglas/arquitectura.md +38 -0
  57. package/reglas/arreglar-al-detectar.md +93 -0
  58. package/reglas/auditorias-documentales-estructurales.md +38 -0
  59. package/reglas/fragmentos-compartidos.md +26 -0
  60. package/reglas/intent-engineering.md +214 -0
  61. package/reglas/registro-componentes-nuevos.md +52 -0
  62. package/reglas/tests-cleanup.md +220 -0
  63. package/schemas/agent-frontmatter.schema.json +294 -167
  64. package/schemas/agent-message.schema.json +73 -53
  65. package/schemas/agent-output-implementacion.schema.json +114 -85
  66. package/schemas/agent-output-planificacion.schema.json +150 -113
  67. package/schemas/agent-output-review.schema.json +98 -78
  68. package/schemas/diary-entry.schema.json +42 -10
  69. package/schemas/hook-profiles.schema.json +54 -39
  70. package/schemas/hooks-config.schema.json +89 -74
  71. package/schemas/instinct.schema.json +152 -115
  72. package/schemas/modulos.schema.json +38 -29
  73. package/schemas/perfiles.schema.json +36 -28
  74. package/schemas/plugin.schema.json +77 -64
  75. package/schemas/skill-evals.schema.json +119 -95
  76. package/schemas/skill-frontmatter.schema.json +245 -170
  77. package/scripts/generar-inventario.js +3 -1
  78. package/scripts/lib/mcp_config.py +29 -14
  79. package/scripts/lib/schema-version.js +164 -0
  80. package/scripts/mcp-orchestrator.py +153 -131
  81. package/scripts/mcp-pool-manager.py +132 -107
  82. package/scripts/mcp-telemetry.py +139 -120
  83. package/scripts/validar-manifest.js +1 -1
  84. package/scripts/validar.js +3 -2
  85. package/scripts/verificar-release.js +199 -1
@@ -29,6 +29,14 @@ from datetime import datetime, timezone
29
29
  from pathlib import Path
30
30
  from typing import Any
31
31
 
32
+ # Permitir importar mcp_config desde scripts/lib/ (consistente con
33
+ # mcp-orchestrator.py:34 y mcp-pool-manager.py:35). Sin esto, el import
34
+ # runtime dentro de MCPTelemetrySession.__aenter__ rompía con
35
+ # ModuleNotFoundError al usarse desde un consumidor que no había configurado
36
+ # scripts/lib/ en sys.path previamente.
37
+ sys.path.insert(0, str(Path(__file__).resolve().parent / "lib"))
38
+ from mcp_config import build_stdio_env # noqa: E402
39
+
32
40
  # ---------------------------------------------------------------------------
33
41
  # Dependencias — raw mcp SDK
34
42
  # ---------------------------------------------------------------------------
@@ -36,6 +44,7 @@ try:
36
44
  from mcp import ClientSession
37
45
  from mcp.client.stdio import stdio_client, StdioServerParameters
38
46
  from mcp.client.sse import sse_client
47
+
39
48
  HAS_MCP = True
40
49
  except ImportError:
41
50
  HAS_MCP = False
@@ -44,9 +53,9 @@ except ImportError:
44
53
  # Constantes
45
54
  # ---------------------------------------------------------------------------
46
55
 
47
- TRACES_DIR = Path('.planning') / 'traces'
48
- MCP_PREFIX = 'mcp' # prefijo de archivos de traza MCP
49
- TIMEOUT_S = 15
56
+ TRACES_DIR = Path(".planning") / "traces"
57
+ MCP_PREFIX = "mcp" # prefijo de archivos de traza MCP
58
+ TIMEOUT_S = 15
50
59
 
51
60
  # ---------------------------------------------------------------------------
52
61
  # Utilidades de traza OTLP-lite
@@ -54,11 +63,11 @@ TIMEOUT_S = 15
54
63
 
55
64
 
56
65
  def _trace_id() -> str:
57
- return secrets.token_hex(16) # 128 bits, 32 hex chars
66
+ return secrets.token_hex(16) # 128 bits, 32 hex chars
58
67
 
59
68
 
60
69
  def _span_id() -> str:
61
- return secrets.token_hex(8) # 64 bits, 16 hex chars
70
+ return secrets.token_hex(8) # 64 bits, 16 hex chars
62
71
 
63
72
 
64
73
  def _iso_now() -> str:
@@ -69,15 +78,19 @@ def _ruta_hoy(cwd: Path) -> Path:
69
78
  """Ruta del JSONL de trazas MCP del dia actual."""
70
79
  directorio = cwd / TRACES_DIR
71
80
  directorio.mkdir(parents=True, exist_ok=True)
72
- fecha = datetime.now(timezone.utc).strftime('%Y-%m-%d')
73
- return directorio / f'{MCP_PREFIX}-{fecha}.jsonl'
74
-
75
-
76
- def registrar_traza(cwd: Path, nombre: str, atributos: dict,
77
- estado: str = 'OK',
78
- inicio_iso: str | None = None,
79
- fin_iso: str | None = None,
80
- duracion_ms: int = 0) -> dict:
81
+ fecha = datetime.now(timezone.utc).strftime("%Y-%m-%d")
82
+ return directorio / f"{MCP_PREFIX}-{fecha}.jsonl"
83
+
84
+
85
+ def registrar_traza(
86
+ cwd: Path,
87
+ nombre: str,
88
+ atributos: dict,
89
+ estado: str = "OK",
90
+ inicio_iso: str | None = None,
91
+ fin_iso: str | None = None,
92
+ duracion_ms: int = 0,
93
+ ) -> dict:
81
94
  """Escribe una entrada OTLP-lite al JSONL de trazas del dia.
82
95
 
83
96
  Parametros:
@@ -93,23 +106,24 @@ def registrar_traza(cwd: Path, nombre: str, atributos: dict,
93
106
  """
94
107
  ahora = _iso_now()
95
108
  traza = {
96
- 'traceId': _trace_id(),
97
- 'spanId': _span_id(),
98
- 'nombre': nombre,
99
- 'inicio': inicio_iso or ahora,
100
- 'fin': fin_iso or ahora,
101
- 'duracionMs': duracion_ms,
102
- 'estado': estado,
103
- 'atributos': atributos,
109
+ "traceId": _trace_id(),
110
+ "spanId": _span_id(),
111
+ "nombre": nombre,
112
+ "inicio": inicio_iso or ahora,
113
+ "fin": fin_iso or ahora,
114
+ "duracionMs": duracion_ms,
115
+ "estado": estado,
116
+ "atributos": atributos,
104
117
  }
105
118
  try:
106
119
  ruta = _ruta_hoy(cwd)
107
- with open(ruta, 'a', encoding='utf-8') as fh:
108
- fh.write(json.dumps(traza, ensure_ascii=False) + '\n')
120
+ with open(ruta, "a", encoding="utf-8") as fh:
121
+ fh.write(json.dumps(traza, ensure_ascii=False) + "\n")
109
122
  except Exception as exc:
110
- sys.stderr.write(f'[mcp-telemetry] No se pudo escribir traza: {exc}\n')
123
+ sys.stderr.write(f"[mcp-telemetry] No se pudo escribir traza: {exc}\n")
111
124
  return traza
112
125
 
126
+
113
127
  # ---------------------------------------------------------------------------
114
128
  # Context manager: MCPTelemetrySession
115
129
  # ---------------------------------------------------------------------------
@@ -128,33 +142,32 @@ class MCPTelemetrySession:
128
142
  """
129
143
 
130
144
  def __init__(self, cwd: Path, server_name: str, server_cfg: dict) -> None:
131
- self.cwd = cwd
145
+ self.cwd = cwd
132
146
  self.server_name = server_name
133
- self.server_cfg = server_cfg
147
+ self.server_cfg = server_cfg
134
148
  self._session: ClientSession | None = None
135
149
  self._ctx_stack: list = []
136
150
 
137
- async def __aenter__(self) -> 'MCPTelemetrySession':
151
+ async def __aenter__(self) -> "MCPTelemetrySession":
138
152
  if not HAS_MCP:
139
- raise RuntimeError('La libreria mcp no esta instalada. Ejecuta: pip install mcp')
153
+ raise RuntimeError(
154
+ "La libreria mcp no esta instalada. Ejecuta: pip install mcp"
155
+ )
140
156
 
141
157
  cfg = self.server_cfg
142
- if 'url' in cfg:
143
- transport = sse_client(cfg['url'])
158
+ if "url" in cfg:
159
+ transport = sse_client(cfg["url"])
144
160
  else:
145
- env: dict | None = None
146
- if extra := cfg.get('env'):
147
- env = {**os.environ, **extra}
148
161
  params = StdioServerParameters(
149
- command=cfg['command'],
150
- args=cfg.get('args', []),
151
- env=env,
152
- cwd=cfg.get('cwd'),
162
+ command=cfg["command"],
163
+ args=cfg.get("args", []),
164
+ env=build_stdio_env(cfg),
165
+ cwd=cfg.get("cwd"),
153
166
  )
154
167
  transport = stdio_client(params)
155
168
 
156
169
  t_ctx = transport
157
- rw = await t_ctx.__aenter__()
170
+ rw = await t_ctx.__aenter__()
158
171
  self._ctx_stack.append(t_ctx)
159
172
 
160
173
  sess_ctx = ClientSession(rw[0], rw[1])
@@ -176,7 +189,7 @@ class MCPTelemetrySession:
176
189
  async def list_tools(self) -> list:
177
190
  """Lista herramientas disponibles en el servidor."""
178
191
  if not self._session:
179
- raise RuntimeError('Sesion no inicializada — usar como context manager')
192
+ raise RuntimeError("Sesion no inicializada — usar como context manager")
180
193
  resp = await asyncio.wait_for(self._session.list_tools(), timeout=TIMEOUT_S)
181
194
  return resp.tools or []
182
195
 
@@ -186,51 +199,51 @@ class MCPTelemetrySession:
186
199
  Retorna dict con: result (list[str]), is_error (bool), duration_ms (int).
187
200
  """
188
201
  if not self._session:
189
- raise RuntimeError('Sesion no inicializada — usar como context manager')
202
+ raise RuntimeError("Sesion no inicializada — usar como context manager")
190
203
 
191
204
  inicio_ts = time.time()
192
205
  inicio_iso = _iso_now()
193
- estado = 'OK'
194
- resultado: dict = {'result': [], 'is_error': False, 'duration_ms': 0}
206
+ estado = "OK"
207
+ resultado: dict = {"result": [], "is_error": False, "duration_ms": 0}
195
208
 
196
209
  try:
197
210
  resp = await asyncio.wait_for(
198
211
  self._session.call_tool(tool, arguments or {}),
199
212
  timeout=TIMEOUT_S,
200
213
  )
201
- resultado['is_error'] = bool(getattr(resp, 'isError', False))
202
- resultado['result'] = [
203
- c.text if hasattr(c, 'text') else str(c)
204
- for c in (resp.content or [])
214
+ resultado["is_error"] = bool(getattr(resp, "isError", False))
215
+ resultado["result"] = [
216
+ c.text if hasattr(c, "text") else str(c) for c in (resp.content or [])
205
217
  ]
206
- if resultado['is_error']:
207
- estado = 'ERROR'
218
+ if resultado["is_error"]:
219
+ estado = "ERROR"
208
220
  except Exception as exc:
209
- resultado['error'] = str(exc)
210
- estado = 'ERROR'
221
+ resultado["error"] = str(exc)
222
+ estado = "ERROR"
211
223
  finally:
212
- fin_ts = time.time()
213
- dur_ms = int((fin_ts - inicio_ts) * 1000)
224
+ fin_ts = time.time()
225
+ dur_ms = int((fin_ts - inicio_ts) * 1000)
214
226
  fin_iso = _iso_now()
215
- resultado['duration_ms'] = dur_ms
227
+ resultado["duration_ms"] = dur_ms
216
228
 
217
229
  registrar_traza(
218
- cwd = self.cwd,
219
- nombre = f'mcp:call_tool',
220
- atributos = {
221
- 'server': self.server_name,
222
- 'tool': tool,
223
- 'args_keys': list((arguments or {}).keys()),
224
- 'duration_ms': dur_ms,
230
+ cwd=self.cwd,
231
+ nombre=f"mcp:call_tool",
232
+ atributos={
233
+ "server": self.server_name,
234
+ "tool": tool,
235
+ "args_keys": list((arguments or {}).keys()),
236
+ "duration_ms": dur_ms,
225
237
  },
226
- estado = estado,
227
- inicio_iso = inicio_iso,
228
- fin_iso = fin_iso,
229
- duracion_ms = dur_ms,
238
+ estado=estado,
239
+ inicio_iso=inicio_iso,
240
+ fin_iso=fin_iso,
241
+ duracion_ms=dur_ms,
230
242
  )
231
243
 
232
244
  return resultado
233
245
 
246
+
234
247
  # ---------------------------------------------------------------------------
235
248
  # CLI helpers
236
249
  # ---------------------------------------------------------------------------
@@ -243,10 +256,10 @@ def _leer_trazas_mcp(cwd: Path, dias: int = 1) -> list:
243
256
  return []
244
257
 
245
258
  trazas: list = []
246
- archivos = sorted(directorio.glob(f'{MCP_PREFIX}-*.jsonl'), reverse=True)[:dias]
259
+ archivos = sorted(directorio.glob(f"{MCP_PREFIX}-*.jsonl"), reverse=True)[:dias]
247
260
  for archivo in archivos:
248
261
  try:
249
- for linea in archivo.read_text(encoding='utf-8').splitlines():
262
+ for linea in archivo.read_text(encoding="utf-8").splitlines():
250
263
  linea = linea.strip()
251
264
  if linea:
252
265
  try:
@@ -259,79 +272,81 @@ def _leer_trazas_mcp(cwd: Path, dias: int = 1) -> list:
259
272
 
260
273
 
261
274
  def _cmd_report(cwd: Path, dias: int, as_json: bool) -> None:
262
- out = sys.stdout.write
275
+ out = sys.stdout.write
263
276
  trazas = _leer_trazas_mcp(cwd, dias)
264
277
 
265
278
  if as_json:
266
- out(json.dumps(trazas, ensure_ascii=False, indent=2) + '\n')
279
+ out(json.dumps(trazas, ensure_ascii=False, indent=2) + "\n")
267
280
  return
268
281
 
269
282
  if not trazas:
270
- out(f'Sin trazas MCP en los ultimos {dias} dia(s).\n')
271
- out(f'Directorio: {cwd / TRACES_DIR}\n')
283
+ out(f"Sin trazas MCP en los ultimos {dias} dia(s).\n")
284
+ out(f"Directorio: {cwd / TRACES_DIR}\n")
272
285
  return
273
286
 
274
- out(f'\nTrazas MCP ({len(trazas)} entradas, ultimos {dias} dia(s)):\n')
275
- out('-' * 72 + '\n')
276
- for t in trazas[-50:]: # mostrar ultimas 50
277
- estado = t.get('estado', '?')
278
- nombre = t.get('nombre', '?')
279
- dur = t.get('duracionMs', 0)
280
- attrs = t.get('atributos', {})
281
- server = attrs.get('server', '')
282
- tool = attrs.get('tool', '')
283
- ts = t.get('inicio', '')[:19]
284
- out(f' {ts} {estado:<5} {nombre:<20} {server}/{tool} {dur}ms\n')
287
+ out(f"\nTrazas MCP ({len(trazas)} entradas, ultimos {dias} dia(s)):\n")
288
+ out("-" * 72 + "\n")
289
+ for t in trazas[-50:]: # mostrar ultimas 50
290
+ estado = t.get("estado", "?")
291
+ nombre = t.get("nombre", "?")
292
+ dur = t.get("duracionMs", 0)
293
+ attrs = t.get("atributos", {})
294
+ server = attrs.get("server", "")
295
+ tool = attrs.get("tool", "")
296
+ ts = t.get("inicio", "")[:19]
297
+ out(f" {ts} {estado:<5} {nombre:<20} {server}/{tool} {dur}ms\n")
285
298
 
286
299
 
287
300
  def _cmd_stats(cwd: Path, dias: int, as_json: bool) -> None:
288
- out = sys.stdout.write
301
+ out = sys.stdout.write
289
302
  trazas = _leer_trazas_mcp(cwd, dias)
290
303
 
291
304
  # Agregar por servidor y herramienta
292
- stats: dict = {} # server -> {tool -> {count, ok, errors, total_ms}}
305
+ stats: dict = {} # server -> {tool -> {count, ok, errors, total_ms}}
293
306
  for t in trazas:
294
- attrs = t.get('atributos', {})
295
- server = attrs.get('server', 'desconocido')
296
- tool = attrs.get('tool', 'desconocido')
297
- estado = t.get('estado', 'OK')
298
- dur = t.get('duracionMs', 0)
307
+ attrs = t.get("atributos", {})
308
+ server = attrs.get("server", "desconocido")
309
+ tool = attrs.get("tool", "desconocido")
310
+ estado = t.get("estado", "OK")
311
+ dur = t.get("duracionMs", 0)
299
312
 
300
313
  if server not in stats:
301
314
  stats[server] = {}
302
315
  if tool not in stats[server]:
303
- stats[server][tool] = {'count': 0, 'ok': 0, 'errors': 0, 'total_ms': 0}
316
+ stats[server][tool] = {"count": 0, "ok": 0, "errors": 0, "total_ms": 0}
304
317
 
305
318
  s = stats[server][tool]
306
- s['count'] += 1
307
- s['total_ms'] += dur
308
- if estado == 'OK':
309
- s['ok'] += 1
319
+ s["count"] += 1
320
+ s["total_ms"] += dur
321
+ if estado == "OK":
322
+ s["ok"] += 1
310
323
  else:
311
- s['errors'] += 1
324
+ s["errors"] += 1
312
325
 
313
326
  # Calcular promedio
314
327
  for server in stats:
315
328
  for tool in stats[server]:
316
329
  s = stats[server][tool]
317
- s['avg_ms'] = int(s['total_ms'] / s['count']) if s['count'] else 0
330
+ s["avg_ms"] = int(s["total_ms"] / s["count"]) if s["count"] else 0
318
331
 
319
332
  if as_json:
320
- out(json.dumps(stats, ensure_ascii=False, indent=2) + '\n')
333
+ out(json.dumps(stats, ensure_ascii=False, indent=2) + "\n")
321
334
  return
322
335
 
323
336
  if not stats:
324
- out(f'Sin datos de uso MCP en los ultimos {dias} dia(s).\n')
337
+ out(f"Sin datos de uso MCP en los ultimos {dias} dia(s).\n")
325
338
  return
326
339
 
327
- out(f'\nEstadisticas MCP — ultimos {dias} dia(s) ({len(trazas)} trazas):\n')
328
- out('-' * 72 + '\n')
329
- out(f' {"Servidor":<22} {"Herramienta":<24} {"Calls":>6} {"OK":>4} {"Err":>4} {"Avg ms":>7}\n')
330
- out('-' * 72 + '\n')
340
+ out(f"\nEstadisticas MCP — ultimos {dias} dia(s) ({len(trazas)} trazas):\n")
341
+ out("-" * 72 + "\n")
342
+ out(
343
+ f' {"Servidor":<22} {"Herramienta":<24} {"Calls":>6} {"OK":>4} {"Err":>4} {"Avg ms":>7}\n'
344
+ )
345
+ out("-" * 72 + "\n")
331
346
  for server, tools in sorted(stats.items()):
332
347
  for tool, s in sorted(tools.items()):
333
348
  out(
334
- f' {server:<22} {tool:<24}'
349
+ f" {server:<22} {tool:<24}"
335
350
  f' {s["count"]:>6} {s["ok"]:>4} {s["errors"]:>4} {s["avg_ms"]:>7}\n'
336
351
  )
337
352
 
@@ -342,37 +357,41 @@ def _cmd_stats(cwd: Path, dias: int, as_json: bool) -> None:
342
357
 
343
358
 
344
359
  def main() -> None:
345
- if hasattr(sys.stdout, 'reconfigure'):
346
- sys.stdout.reconfigure(encoding='utf-8', errors='replace')
360
+ if hasattr(sys.stdout, "reconfigure"):
361
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
347
362
 
348
363
  parser = argparse.ArgumentParser(
349
- description='Trazabilidad OTLP-lite para sesiones MCP en swl-ses'
364
+ description="Trazabilidad OTLP-lite para sesiones MCP en swl-ses"
350
365
  )
351
- parser.add_argument('--json', action='store_true', help='Salida en formato JSON')
352
- parser.add_argument('--cwd', default='.', help='Directorio raiz del proyecto')
366
+ parser.add_argument("--json", action="store_true", help="Salida en formato JSON")
367
+ parser.add_argument("--cwd", default=".", help="Directorio raiz del proyecto")
353
368
 
354
- sub = parser.add_subparsers(dest='cmd')
369
+ sub = parser.add_subparsers(dest="cmd")
355
370
 
356
- p_rep = sub.add_parser('report', help='Muestra trazas MCP recientes')
357
- p_rep.add_argument('--days', type=int, default=1,
358
- help='Numero de dias hacia atras (default: 1)')
371
+ p_rep = sub.add_parser("report", help="Muestra trazas MCP recientes")
372
+ p_rep.add_argument(
373
+ "--days", type=int, default=1, help="Numero de dias hacia atras (default: 1)"
374
+ )
359
375
 
360
- p_stats = sub.add_parser('stats', help='Estadisticas de uso por servidor/herramienta')
361
- p_stats.add_argument('--days', type=int, default=7,
362
- help='Numero de dias hacia atras (default: 7)')
376
+ p_stats = sub.add_parser(
377
+ "stats", help="Estadisticas de uso por servidor/herramienta"
378
+ )
379
+ p_stats.add_argument(
380
+ "--days", type=int, default=7, help="Numero de dias hacia atras (default: 7)"
381
+ )
363
382
 
364
383
  args = parser.parse_args()
365
- cwd = Path(args.cwd).resolve()
384
+ cwd = Path(args.cwd).resolve()
366
385
 
367
- as_json = bool(getattr(args, 'json', False))
386
+ as_json = bool(getattr(args, "json", False))
368
387
 
369
- if args.cmd == 'report':
388
+ if args.cmd == "report":
370
389
  _cmd_report(cwd, args.days, as_json)
371
- elif args.cmd == 'stats':
390
+ elif args.cmd == "stats":
372
391
  _cmd_stats(cwd, args.days, as_json)
373
392
  else:
374
393
  parser.print_help()
375
394
 
376
395
 
377
- if __name__ == '__main__':
396
+ if __name__ == "__main__":
378
397
  main()
@@ -188,7 +188,7 @@ function main() {
188
188
  const categorias = [
189
189
  { etiqueta: 'comandos/swl/', archivos: listarArchivos('comandos/swl', '.md') },
190
190
  { etiqueta: 'hooks/ (ejecutables)', archivos: hooksEjecutables },
191
- { etiqueta: 'agentes/', archivos: listarArchivos('agentes', '.md') },
191
+ { etiqueta: 'agentes/', archivos: listarArchivos('agentes', '.md').filter(a => !path.basename(a).startsWith('_')) },
192
192
  { etiqueta: 'habilidades/ (dirs)', archivos: listarDirectoriosHabilidades() },
193
193
  { etiqueta: 'reglas/', archivos: listarArchivos('reglas', '.md', true) },
194
194
  ];
@@ -63,8 +63,9 @@ if (fs.existsSync(hooksConfigPath)) {
63
63
  }
64
64
  }
65
65
 
66
- // 5. Agentes (mínimo 12)
67
- const agentes = fs.readdirSync(path.join(RAIZ, 'agentes')).filter(f => f.endsWith('.md'));
66
+ // 5. Agentes (mínimo 12) — excluye fragmentos compartidos (`_*.md`, ver `reglas/fragmentos-compartidos.md`)
67
+ const agentes = fs.readdirSync(path.join(RAIZ, 'agentes'))
68
+ .filter(f => f.endsWith('.md') && !f.startsWith('_'));
68
69
  verificar(agentes.length >= 12, `Agentes: ${agentes.length} (mínimo 12)`);
69
70
 
70
71
  // Verificar frontmatter en agentes