orquestra-mcp 1.6.5 → 1.7.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/package.json +2 -2
- package/src/server.mjs +259 -8
- package/src/setup.mjs +5 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orquestra-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Servidor MCP de Orquestra --
|
|
3
|
+
"version": "1.7.1",
|
|
4
|
+
"description": "Servidor MCP de Orquestra -- 51 tools para leer y escribir tareas, ideas, changelog, infraestructura, contenido, QA y más desde un agente de IA",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"orquestra-mcp": "./bin/orquestra-mcp.mjs"
|
package/src/server.mjs
CHANGED
|
@@ -14,10 +14,11 @@ function textResult(data) {
|
|
|
14
14
|
|
|
15
15
|
const PROJECT_ID_DESC = 'ID del proyecto dentro del workspace configurado. Si hay un proyecto default configurado (via "setup" o ORQUESTRA_DEFAULT_PROJECT_ID), este argumento se ignora -- el server queda fijo a ese proyecto y no puede leer/escribir otro.'
|
|
16
16
|
|
|
17
|
-
const TASK_TYPES = ['feature', 'bug', 'improvement', 'tech', 'docs', 'idea', 'meeting', 'call', 'followup', 'business']
|
|
17
|
+
const TASK_TYPES = ['feature', 'bug', 'improvement', 'tech', 'docs', 'idea', 'design', 'security', 'meeting', 'call', 'followup', 'business']
|
|
18
18
|
const TASK_PRIORITIES = ['high', 'medium', 'low']
|
|
19
19
|
const CHANGELOG_SECTIONS = ['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security']
|
|
20
20
|
const DOC_TYPES = ['readme', 'usecase', 'diagram', 'env', 'adr', 'runbook', 'api']
|
|
21
|
+
const ADR_STATUSES = ['propuesto', 'aceptado', 'deprecado', 'rechazado']
|
|
21
22
|
const SERVICE_TYPES = ['backend', 'database', 'auth', 'storage', 'push', 'email', 'payments', 'analytics', 'realtime', 'search', 'maps', 'flags', 'admin', 'cdn', 'queue']
|
|
22
23
|
const PLATFORM_TYPES = ['android', 'ios', 'web', 'desktop-mac', 'desktop-win', 'backend', 'extension', 'other']
|
|
23
24
|
const INFRA_STATUSES = ['operational', 'degraded', 'down']
|
|
@@ -62,6 +63,13 @@ Durante el trabajo normal de código, sin que el usuario te lo pida explícitame
|
|
|
62
63
|
- Si tienes acceso al repo real de una distribución (ej. puedes correr "git remote get-url origin" en ese código), llena repoUrl en create_distribution/update_distribution sin preguntar -- es solo informativo, no requiere credenciales. Esto es distinto de conectar el repo con PAT para el changelog automático (Workspace → Conexiones en orquestra-web): eso sí es un paso deliberado del usuario, con una credencial real -- nunca lo hagas tú solo ni sugieras que ya está conectado por haber llenado repoUrl.
|
|
63
64
|
- No todas las tareas necesitan una corrida de QA -- clasifica antes de disparar una: cambios que afectan comportamiento real (features, bugs, UI, lógica de negocio, endpoints/API) sí la necesitan; cambios que no alteran comportamiento (docs, comentarios, refactors sin cambio funcional, config/tooling interno, notas/reuniones/ideas) no. Si decides que sí, revisa primero con list_qa_agents que exista un agente para el stack que tocaste (si no hay ninguno, créalo con create_qa_agent -- necesita una máquina ya emparejada, ver Workspace → QA) y llama a create_qa_run en la rama real de ese trabajo (manda taskId si el trabajo era de una tarea puntual -- si el agente tiene la revisión con IA activada, la acota a eso en vez de "revisa todo el proyecto"), dejando la tarea en "En revisión" -- NUNCA la muevas a "Listo" ni digas que "QA lo revisó" sin haber confirmado con get_qa_run un estatus terminal "passed" de verdad (las corridas son asíncronas, tardan minutos en otra máquina). Si tarda, dile al usuario que quedó en cola (con el runId) y que confirmas cuando termine -- no lo des por hecho nunca.
|
|
64
65
|
|
|
66
|
+
Si el usuario pide "sincronizar"/"revisar que todo esté al día"/"verificar el proyecto" (o algo equivalente -- una auditoría manual, no el trabajo normal de arriba), hacé una "revisión de sincronización" completa, de cero, no un vistazo rápido:
|
|
67
|
+
1. Llamá a get_sync_report -- devuelve lo objetivo (tareas sin moduleId, tareas activas sin dueDate, referencias huérfanas a un módulo/categoría/tarea/estatus que ya no existe) y activeTasksSnapshot (TODAS las tareas activas, no solo las incompletas).
|
|
68
|
+
2. Para cada tarea en tasksMissingModule: buscá con list_modules si hay uno que aplique de verdad; si no, create_module antes de asignarlo -- nunca dejes una sin módulo (ver moduleId obligatorio en create_task, esto es limpiar deuda vieja de antes de esa regla).
|
|
69
|
+
3. Para CADA tarea de activeTasksSnapshot (no solo las que tienen dueDate/priority en blanco -- una prioridad "medium" puesta por default nunca se distingue de una elegida a propósito): revisá el contexto real (código, conversación, changelog, dependencias vía relatedTaskId) y asigná una fecha límite realista y un nivel de urgencia real con update_task si el valor actual no refleja la urgencia/plazo real -- no inventes una fecha arbitraria sin ninguna base ("en 3 días" porque sí); si genuinamente no hay forma de estimar una fecha con la información disponible, dejala en blanco en vez de inventar una.
|
|
70
|
+
4. Las referencias huérfanas (tasksWithOrphanModuleId/CategoryId/RelatedTaskId/StatusId) casi nunca se pueden arreglar solas con criterio -- no adivines a qué debería apuntar. Marcalas para el resumen final, y si es obvio por contexto a qué debió apuntar, proponéselo al usuario en vez de aplicarlo directo.
|
|
71
|
+
5. Cerrá SIEMPRE con un resumen estructurado en 3 grupos, aunque algún grupo quede vacío: **No tiene sentido / necesita tu decisión** (referencias huérfanas, contradicciones que encontraste), **Se llenó** (campos que estaban vacíos y ahora tienen un valor real, con qué tarea y qué campo), **Se completó** (lo que quedó totalmente resuelto en esta pasada -- ej. una tarea que ya tenía todo en orden, o un hueco que se cerró del todo). Si tocaste tareas de verdad (no solo leíste el reporte), dejá rastro con un comment en cada una vía update_task explicando qué cambiaste y por qué -- mismo criterio que un cambio de statusId, no debería quedar invisible.
|
|
72
|
+
|
|
65
73
|
No le preguntes al usuario por workspaceId ni projectId -- ya están configurados. Solo pregúntale por el proyecto si tiene más de uno y no queda claro a cuál te refieres.
|
|
66
74
|
|
|
67
75
|
No crees tareas ni entradas de changelog por preguntas triviales o exploración sin cambios reales -- solo por trabajo que de verdad avanza el proyecto.`
|
|
@@ -187,6 +195,14 @@ export function createServer() {
|
|
|
187
195
|
(args, projectId) => ({ projectId })
|
|
188
196
|
)
|
|
189
197
|
|
|
198
|
+
registerRead(
|
|
199
|
+
'get_sync_report',
|
|
200
|
+
'/v1/sync-report',
|
|
201
|
+
'Auditoría de integridad de un proyecto para una "revisión de sincronización" completa: tareas sin moduleId, tareas activas sin dueDate, y referencias huérfanas (moduleId/categoryId/relatedTaskId/statusId que ya no existen). También devuelve activeTasksSnapshot -- TODAS las tareas activas con su moduleId/priority/dueDate actuales, no solo las que tienen un campo en blanco, para que reevalúes fecha límite y urgencia con criterio real (ver flujo de "revisión de sincronización" en las instrucciones de este server).',
|
|
202
|
+
{},
|
|
203
|
+
(args, projectId) => ({ projectId })
|
|
204
|
+
)
|
|
205
|
+
|
|
190
206
|
server.registerTool(
|
|
191
207
|
'get_workspace_summary',
|
|
192
208
|
{
|
|
@@ -276,7 +292,7 @@ export function createServer() {
|
|
|
276
292
|
registerCreate(
|
|
277
293
|
'create_task',
|
|
278
294
|
'/v1/tasks',
|
|
279
|
-
'Crea una tarea en un proyecto de Orquestra.',
|
|
295
|
+
'Crea una tarea en un proyecto de Orquestra. moduleId es OBLIGATORIO -- revisa list_modules primero; si ninguno aplica, creá uno con create_module antes de crear la tarea (nunca la dejes sin agrupar).',
|
|
280
296
|
{
|
|
281
297
|
title: z.string().describe('Título de la tarea'),
|
|
282
298
|
description: z.string().optional(),
|
|
@@ -284,12 +300,26 @@ export function createServer() {
|
|
|
284
300
|
priority: z.enum(TASK_PRIORITIES).optional().describe('Default: medium'),
|
|
285
301
|
statusId: z.string().optional().describe('Default: el primer estatus del proyecto'),
|
|
286
302
|
categoryId: z.string().optional(),
|
|
287
|
-
moduleId: z.string().
|
|
303
|
+
moduleId: z.string().describe('OBLIGATORIO -- módulo al que pertenece esta tarea, para poder agruparla/revisarla después. Si no existe uno relacionado, llamá primero a list_modules (para no duplicar) y create_module.'),
|
|
288
304
|
dueDate: z.string().optional().describe('YYYY-MM-DD'),
|
|
289
305
|
assignedTo: z.string().optional().describe('uid del miembro asignado'),
|
|
290
306
|
tags: z.array(z.string()).optional(),
|
|
307
|
+
scope: z.array(z.object({ distId: z.string(), layer: z.string().optional(), done: z.boolean() })).optional()
|
|
308
|
+
.describe('Alcance por distribución (paridad multiplataforma) -- para setearlo desde la creación en vez de crear y después update_task. Usa get_parity_summary/list_distributions para ver qué distId existen.'),
|
|
291
309
|
relatedTaskId: z.string().optional().describe('"Bloqueada por" -- id de otra tarea. Esta tarea se muestra como bloqueada mientras esa otra no llegue a un estatus final (isFinal). Úsalo cuando el trabajo real depende de que otra tarea termine primero.'),
|
|
292
310
|
location: z.string().optional().describe('Solo relevante para type "meeting"/"call" (plataforma o lugar, ej. "Zoom", "Oficina") o "followup"/"business" (contexto libre) -- ignóralo para el resto de los tipos.'),
|
|
311
|
+
estimatedDays: z.number().optional().describe('Estimación en días de trabajo.'),
|
|
312
|
+
stack: z.array(z.string()).optional().describe('Tecnologías involucradas (mismo vocabulario que el stack de una distribución, ej. "node", "react", "postgres").'),
|
|
313
|
+
branch: z.string().optional().describe('Branch de código asociado a esta tarea.'),
|
|
314
|
+
prNumber: z.string().optional().describe('Número del Pull Request asociado (sin el "#").'),
|
|
315
|
+
prUrl: z.string().optional(),
|
|
316
|
+
prStatus: z.string().optional().describe('"open" | "merged" | "closed", o lo que corresponda -- texto libre.'),
|
|
317
|
+
commitCount: z.number().optional(),
|
|
318
|
+
lastCommitAt: z.number().optional().describe('Timestamp en ms del último commit conocido.'),
|
|
319
|
+
resolvedCommitSha: z.string().optional().describe('SHA del commit que de verdad resolvió esta tarea, si ya lo sabes (ej. lo corriste tú). No lo inventes ni lo copies de otra tarea -- si no estás seguro, mejor déjalo vacío y usa "Actualizar desde GitHub" en el detalle (busca por shortCode).'),
|
|
320
|
+
resolvedCommitMessage: z.string().optional(),
|
|
321
|
+
resolvedCommitUrl: z.string().optional(),
|
|
322
|
+
resolvedCommitAt: z.number().optional().describe('Timestamp en ms del commit resolutor.'),
|
|
293
323
|
}
|
|
294
324
|
)
|
|
295
325
|
|
|
@@ -313,6 +343,85 @@ export function createServer() {
|
|
|
313
343
|
relatedTaskId: z.string().optional().describe('"Bloqueada por" -- id de otra tarea, o "" para quitar el bloqueo. Ver create_task.'),
|
|
314
344
|
location: z.string().optional().describe('Solo relevante para type "meeting"/"call"/"followup"/"business". Ver create_task.'),
|
|
315
345
|
comment: z.string().optional().describe('Explica qué hiciste o por qué cambia la tarea -- queda como comentario visible en ella. Obligatorio en la práctica cuando mandas statusId: sin esto, alguien ve que la tarea cambió de estatus pero no por qué.'),
|
|
346
|
+
estimatedDays: z.number().optional().describe('Estimación en días de trabajo.'),
|
|
347
|
+
stack: z.array(z.string()).optional().describe('Tecnologías involucradas -- ver create_task.'),
|
|
348
|
+
branch: z.string().optional().describe('Branch de código asociado a esta tarea -- útil setearlo apenas empiezas a trabajar, así "Actualizar desde GitHub" en el detalle puede traer PR/commits reales después.'),
|
|
349
|
+
prNumber: z.string().optional().describe('Número del Pull Request asociado (sin el "#").'),
|
|
350
|
+
prUrl: z.string().optional(),
|
|
351
|
+
prStatus: z.string().optional().describe('"open" | "merged" | "closed", o lo que corresponda -- texto libre.'),
|
|
352
|
+
commitCount: z.number().optional(),
|
|
353
|
+
lastCommitAt: z.number().optional().describe('Timestamp en ms del último commit conocido.'),
|
|
354
|
+
resolvedCommitSha: z.string().optional().describe('SHA del commit que de verdad resolvió esta tarea, si ya lo sabes (ej. lo corriste tú). No lo inventes ni lo copies de otra tarea -- si no estás seguro, mejor déjalo vacío y usa "Actualizar desde GitHub" en el detalle (busca por shortCode).'),
|
|
355
|
+
resolvedCommitMessage: z.string().optional(),
|
|
356
|
+
resolvedCommitUrl: z.string().optional(),
|
|
357
|
+
resolvedCommitAt: z.number().optional().describe('Timestamp en ms del commit resolutor.'),
|
|
358
|
+
}
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
// Checklist de una tarea -- registradas directo con server.registerTool
|
|
362
|
+
// (no con registerCreate/registerUpdate) porque su ruta necesita DOS ids
|
|
363
|
+
// dinámicos (taskId + itemId del checklist), y esos helpers solo arman
|
|
364
|
+
// `${path}/${id}` con uno.
|
|
365
|
+
server.registerTool(
|
|
366
|
+
'list_checklist_items',
|
|
367
|
+
{
|
|
368
|
+
description: 'Lista los ítems del checklist de una tarea de Orquestra (texto, hecho/pendiente, responsable).',
|
|
369
|
+
inputSchema: {
|
|
370
|
+
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
371
|
+
taskId: z.string().describe('ID de la tarea'),
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
async ({ projectId, taskId }) => {
|
|
375
|
+
try {
|
|
376
|
+
const data = await apiGet(config, `/v1/tasks/${taskId}/checklist`, { projectId: resolveProjectId(projectId) })
|
|
377
|
+
return textResult(data)
|
|
378
|
+
} catch (err) {
|
|
379
|
+
return errorResult(err)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
server.registerTool(
|
|
385
|
+
'add_checklist_item',
|
|
386
|
+
{
|
|
387
|
+
description: 'Agrega un ítem al checklist de una tarea de Orquestra, opcionalmente con un responsable.',
|
|
388
|
+
inputSchema: {
|
|
389
|
+
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
390
|
+
taskId: z.string().describe('ID de la tarea'),
|
|
391
|
+
text: z.string().describe('Texto del ítem'),
|
|
392
|
+
assignedTo: z.string().optional().describe('uid del miembro responsable de este ítem (opcional)'),
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
async ({ projectId, taskId, ...body }) => {
|
|
396
|
+
try {
|
|
397
|
+
const data = await apiPost(config, `/v1/tasks/${taskId}/checklist`, { projectId: resolveProjectId(projectId), ...body })
|
|
398
|
+
return textResult(data)
|
|
399
|
+
} catch (err) {
|
|
400
|
+
return errorResult(err)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
server.registerTool(
|
|
406
|
+
'update_checklist_item',
|
|
407
|
+
{
|
|
408
|
+
description: 'Edita un ítem del checklist de una tarea de Orquestra: texto, hecho/pendiente, o responsable.',
|
|
409
|
+
inputSchema: {
|
|
410
|
+
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
411
|
+
taskId: z.string().describe('ID de la tarea'),
|
|
412
|
+
itemId: z.string().describe('ID del ítem del checklist a editar'),
|
|
413
|
+
text: z.string().optional(),
|
|
414
|
+
done: z.boolean().optional(),
|
|
415
|
+
assignedTo: z.string().optional().describe('uid del miembro responsable, o "" para quitarlo'),
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
async ({ projectId, taskId, itemId, ...body }) => {
|
|
419
|
+
try {
|
|
420
|
+
const data = await apiPatch(config, `/v1/tasks/${taskId}/checklist`, { projectId: resolveProjectId(projectId), id: itemId, ...body })
|
|
421
|
+
return textResult(data)
|
|
422
|
+
} catch (err) {
|
|
423
|
+
return errorResult(err)
|
|
424
|
+
}
|
|
316
425
|
}
|
|
317
426
|
)
|
|
318
427
|
|
|
@@ -361,6 +470,9 @@ export function createServer() {
|
|
|
361
470
|
type: z.enum(DOC_TYPES).describe('Tipo de documento'),
|
|
362
471
|
title: z.string().describe('Título del documento'),
|
|
363
472
|
content: z.string().optional(),
|
|
473
|
+
adrStatus: z.enum(ADR_STATUSES).optional().describe('Solo para type=\'adr\'. "aceptado" es lo único que alimenta Material (Contenido) como fuente de ángulos -- ver adrContext.'),
|
|
474
|
+
adrContext: z.string().optional().describe('Solo para type=\'adr\'. Qué problema/situación llevó a la decisión, en texto estructurado (no el markdown libre de content).'),
|
|
475
|
+
adrConsequences: z.string().optional().describe('Solo para type=\'adr\'. Qué cambia de ahora en adelante.'),
|
|
364
476
|
}
|
|
365
477
|
)
|
|
366
478
|
|
|
@@ -376,6 +488,10 @@ export function createServer() {
|
|
|
376
488
|
scope: SCOPE_SCHEMA.describe('Alcance por distribución, ej: [{distId, layer, done}]'),
|
|
377
489
|
tags: z.array(z.string()).optional(),
|
|
378
490
|
dueDate: z.string().optional().describe('YYYY-MM-DD'),
|
|
491
|
+
shippedVersion: z.string().optional().describe('Versión del changelog en la que se envió, ej: "1.4.0"'),
|
|
492
|
+
ownerUid: z.string().optional().describe('uid del miembro responsable del avance del módulo'),
|
|
493
|
+
dependsOnModuleIds: z.array(z.string()).optional().describe('IDs de módulos de los que este depende (dependencia planeada, manual)'),
|
|
494
|
+
closureCriteria: z.string().optional().describe('Texto libre: cuándo se considera verdaderamente cerrado'),
|
|
379
495
|
})
|
|
380
496
|
registerUpdate('update_module', '/v1/modules', 'Edita un módulo existente (incluido su scope multiplataforma).', {
|
|
381
497
|
name: z.string().optional(),
|
|
@@ -385,6 +501,10 @@ export function createServer() {
|
|
|
385
501
|
scope: SCOPE_SCHEMA,
|
|
386
502
|
tags: z.array(z.string()).optional(),
|
|
387
503
|
dueDate: z.string().optional().describe('YYYY-MM-DD'),
|
|
504
|
+
shippedVersion: z.string().optional().describe('Versión del changelog en la que se envió, ej: "1.4.0"'),
|
|
505
|
+
ownerUid: z.string().optional().describe('uid del miembro responsable del avance del módulo'),
|
|
506
|
+
dependsOnModuleIds: z.array(z.string()).optional().describe('IDs de módulos de los que este depende (dependencia planeada, manual)'),
|
|
507
|
+
closureCriteria: z.string().optional().describe('Texto libre: cuándo se considera verdaderamente cerrado'),
|
|
388
508
|
})
|
|
389
509
|
|
|
390
510
|
registerCreate('create_status', '/v1/statuses', 'Crea un estatus (columna del flujo de tareas) en un proyecto.', {
|
|
@@ -459,14 +579,20 @@ export function createServer() {
|
|
|
459
579
|
name: z.string().describe('Nombre del servicio'),
|
|
460
580
|
status: z.enum(INFRA_STATUSES).optional().describe('Default: operational'),
|
|
461
581
|
notes: z.string().optional(),
|
|
582
|
+
ownerUid: z.string().optional().describe('uid del miembro responsable del servicio'),
|
|
583
|
+
moduleId: z.string().optional().describe('a qué módulo de producto pertenece este servicio -- ver list_modules'),
|
|
584
|
+
affectedTaskIds: z.array(z.string()).optional().describe('IDs de tareas que este servicio bloquea/afecta -- ver list_tasks'),
|
|
462
585
|
...SERVICE_SPECIFIC_SCHEMA,
|
|
463
586
|
})
|
|
464
|
-
registerUpdate('update_service', '/v1/services', 'Edita un servicio de infraestructura existente (status, campos específicos por tipo, incidente...).', {
|
|
587
|
+
registerUpdate('update_service', '/v1/services', 'Edita un servicio de infraestructura existente (status, campos específicos por tipo, incidente...). Un cambio de `status` queda logueado solo en el historial real de incidentes del servicio -- no hace falta nada más para eso. Los demás campos editados quedan logueados en el registro de cambios real del servicio.', {
|
|
465
588
|
type: z.enum(SERVICE_TYPES).optional(),
|
|
466
589
|
name: z.string().optional(),
|
|
467
590
|
status: z.enum(INFRA_STATUSES).optional(),
|
|
468
591
|
notes: z.string().optional(),
|
|
469
592
|
lastIncident: z.string().optional(),
|
|
593
|
+
ownerUid: z.string().optional().describe('uid del miembro responsable del servicio'),
|
|
594
|
+
moduleId: z.string().optional().describe('a qué módulo de producto pertenece este servicio -- ver list_modules'),
|
|
595
|
+
affectedTaskIds: z.array(z.string()).optional().describe('IDs de tareas que este servicio bloquea/afecta -- ver list_tasks'),
|
|
470
596
|
...SERVICE_SPECIFIC_SCHEMA,
|
|
471
597
|
})
|
|
472
598
|
|
|
@@ -478,8 +604,11 @@ export function createServer() {
|
|
|
478
604
|
status: z.enum(INFRA_STATUSES).optional().describe('Default: operational'),
|
|
479
605
|
notes: z.string().optional(),
|
|
480
606
|
lastDeploy: z.string().optional().describe('Texto libre, ej. "hace 2 días" -- no es una fecha estructurada'),
|
|
607
|
+
lastDeployAt: z.number().optional().describe('Timestamp en ms del último deploy -- mándalo junto con lastDeployBy al confirmar un deploy real'),
|
|
608
|
+
lastDeployBy: z.string().optional().describe('uid de un miembro, o texto libre tipo "CI" para deploys automáticos'),
|
|
609
|
+
affectedTaskIds: z.array(z.string()).optional().describe('IDs de tareas que este entorno bloquea/afecta -- ver list_tasks'),
|
|
481
610
|
})
|
|
482
|
-
registerUpdate('update_environment', '/v1/environments', 'Edita un entorno existente (útil para actualizar su status/version tras un deploy).', {
|
|
611
|
+
registerUpdate('update_environment', '/v1/environments', 'Edita un entorno existente (útil para actualizar su status/version tras un deploy). Un cambio de `status` queda logueado solo en el historial real de incidentes del entorno.', {
|
|
483
612
|
name: z.string().optional(),
|
|
484
613
|
url: z.string().optional(),
|
|
485
614
|
version: z.string().optional(),
|
|
@@ -487,6 +616,9 @@ export function createServer() {
|
|
|
487
616
|
status: z.enum(INFRA_STATUSES).optional(),
|
|
488
617
|
notes: z.string().optional(),
|
|
489
618
|
lastDeploy: z.string().optional(),
|
|
619
|
+
lastDeployAt: z.number().optional().describe('Timestamp en ms del último deploy'),
|
|
620
|
+
lastDeployBy: z.string().optional().describe('uid de un miembro, o texto libre tipo "CI"'),
|
|
621
|
+
affectedTaskIds: z.array(z.string()).optional().describe('IDs de tareas que este entorno bloquea/afecta -- ver list_tasks'),
|
|
490
622
|
})
|
|
491
623
|
|
|
492
624
|
registerCreate('create_distribution', '/v1/distributions', 'Crea una distribución/plataforma (iOS, Android, Web, etc.) en un proyecto.', {
|
|
@@ -529,6 +661,115 @@ export function createServer() {
|
|
|
529
661
|
knowledgeSource: z.string().optional().describe('Doc libre tipo CLAUDE.md para el agente de QA de esta distribución.'),
|
|
530
662
|
})
|
|
531
663
|
|
|
664
|
+
// ═══ Contenido (Fase 1 del rediseño, 11 ago 2026) ═══════════════════════════
|
|
665
|
+
// Un canal = una cuenta real (o a conectar) donde se publica. Una
|
|
666
|
+
// publicación (content) = un canal (channelId), no varios -- para
|
|
667
|
+
// publicar en N canales a la vez, crea N piezas de contenido con
|
|
668
|
+
// create_content, una por channelId.
|
|
669
|
+
const CONTENT_STATUSES = ['idea', 'draft', 'review', 'approved', 'published']
|
|
670
|
+
|
|
671
|
+
registerRead('list_channels', '/v1/channels',
|
|
672
|
+
'Lista los canales (cuentas de redes/blog/newsletter, reales o a conectar) de un proyecto. Úsala antes de create_channel para no duplicar, y antes de create_content para saber qué channelId usar.',
|
|
673
|
+
{}, (args, projectId) => ({ projectId }))
|
|
674
|
+
|
|
675
|
+
registerCreate('create_channel', '/v1/channels', 'Crea un canal (cuenta de contenido) en un proyecto -- ej. "LinkedIn", "Blog de Atlas". No conecta ninguna cuenta real todavía (eso es OAuth, fuera de alcance por ahora); solo registra el canal para poder programar contenido en él.', {
|
|
676
|
+
name: z.string().describe('Ej. "LinkedIn"'),
|
|
677
|
+
type: z.string().optional().describe('linkedin | x | blog | newsletter | instagram | other -- libre'),
|
|
678
|
+
handle: z.string().optional().describe('Ej. "@atlaspagos" o una URL/dominio'),
|
|
679
|
+
color: z.string().optional(),
|
|
680
|
+
cadenceCount: z.number().optional().describe('Ritmo objetivo de publicaciones, ej. 2'),
|
|
681
|
+
cadenceUnit: z.enum(['week', 'month']).optional(),
|
|
682
|
+
})
|
|
683
|
+
|
|
684
|
+
registerUpdate('update_channel', '/v1/channels', 'Edita un canal existente.', {
|
|
685
|
+
name: z.string().optional(),
|
|
686
|
+
type: z.string().optional(),
|
|
687
|
+
handle: z.string().optional(),
|
|
688
|
+
color: z.string().optional(),
|
|
689
|
+
cadenceCount: z.number().optional(),
|
|
690
|
+
cadenceUnit: z.enum(['week', 'month']).optional(),
|
|
691
|
+
})
|
|
692
|
+
|
|
693
|
+
// Fase 3 de Contenido (11 ago 2026) -- una campaña es una pieza de
|
|
694
|
+
// contenido con plata atrás. budget/spent/leads son casi siempre
|
|
695
|
+
// entrada manual: Orquestra no tiene ninguna integración con
|
|
696
|
+
// plataformas de anuncios para traerlos solos.
|
|
697
|
+
const CAMPAIGN_STATUSES = ['active', 'paused', 'completed']
|
|
698
|
+
|
|
699
|
+
registerRead('list_campaigns', '/v1/campaigns',
|
|
700
|
+
'Lista las campañas (pauta paga) de un proyecto. Úsala antes de create_campaign para no duplicar.',
|
|
701
|
+
{}, (args, projectId) => ({ projectId }))
|
|
702
|
+
|
|
703
|
+
registerCreate('create_campaign', '/v1/campaigns', 'Crea una campaña de pauta paga en un proyecto.', {
|
|
704
|
+
name: z.string(),
|
|
705
|
+
channelId: z.string().optional().describe('Canal donde corre la campaña -- ver list_channels'),
|
|
706
|
+
budget: z.number().optional().describe('Presupuesto asignado a esta campaña'),
|
|
707
|
+
spent: z.number().optional().describe('Gastado hasta hoy -- entrada manual, no hay integración con la plataforma de ads'),
|
|
708
|
+
leads: z.number().optional().describe('"Registros" generados -- entrada manual'),
|
|
709
|
+
status: z.enum(CAMPAIGN_STATUSES).optional().describe('Default: active. Pasa a paused sola si spent llega al tope (ver update_campaign).'),
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
registerUpdate('update_campaign', '/v1/campaigns', 'Edita una campaña existente -- típicamente para actualizar spent/leads a medida que se revisan en la plataforma de ads real.', {
|
|
713
|
+
name: z.string().optional(),
|
|
714
|
+
channelId: z.string().optional(),
|
|
715
|
+
budget: z.number().optional(),
|
|
716
|
+
spent: z.number().optional(),
|
|
717
|
+
leads: z.number().optional(),
|
|
718
|
+
status: z.enum(CAMPAIGN_STATUSES).optional(),
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
registerRead('list_content', '/v1/content',
|
|
722
|
+
'Lista las publicaciones de contenido (posts programados/publicados) de un proyecto.',
|
|
723
|
+
{}, (args, projectId) => ({ projectId }))
|
|
724
|
+
|
|
725
|
+
registerCreate('create_content', '/v1/content', 'Crea una publicación de contenido en un canal (ver list_channels/create_channel). Para publicar la misma pieza en varios canales, llama a esto una vez por cada channelId.', {
|
|
726
|
+
channelId: z.string().describe('Canal donde se publica -- ver list_channels'),
|
|
727
|
+
title: z.string().describe('Referencia interna, no es lo que se publica'),
|
|
728
|
+
format: z.string().optional().describe('Ej. "Post", "Reel", "Hilo" -- texto libre según el canal'),
|
|
729
|
+
status: z.enum(CONTENT_STATUSES).optional().describe('Default: idea'),
|
|
730
|
+
caption: z.string().optional().describe('Texto real de la publicación'),
|
|
731
|
+
hashtags: z.array(z.string()).optional(),
|
|
732
|
+
scheduledAt: z.number().optional().describe('Timestamp en ms de cuándo se programa publicar'),
|
|
733
|
+
approvalRequired: z.boolean().optional(),
|
|
734
|
+
approverUid: z.string().optional().describe('uid de quien debe aprobar antes de publicar'),
|
|
735
|
+
changelogVersion: z.string().optional().describe('Versión del changelog que esta publicación anuncia ("Atada a") -- ver list_tasks/create_changelog_entry'),
|
|
736
|
+
moduleId: z.string().optional(),
|
|
737
|
+
ideaId: z.string().optional().describe('Si esta pieza viene de una idea existente'),
|
|
738
|
+
campaignId: z.string().optional().describe('Campaña de pauta a la que pertenece -- ver list_campaigns'),
|
|
739
|
+
organicRate: z.number().optional().describe('% de interacción, medido a mano por el usuario en la plataforma real -- solo tiene sentido con status published'),
|
|
740
|
+
leads: z.number().optional().describe('"Registros" atribuidos a esta pieza orgánica -- entrada manual'),
|
|
741
|
+
reach: z.number().optional().describe('Alcance absoluto -- entrada manual, Fase 4 de Resultados'),
|
|
742
|
+
clicksToSite: z.number().optional().describe('Clics al sitio/docs -- entrada manual'),
|
|
743
|
+
saves: z.number().optional().describe('Guardados -- entrada manual, típico de X/Instagram'),
|
|
744
|
+
avgReadMinutes: z.number().optional().describe('Minutos de lectura media -- entrada manual, típico de blog'),
|
|
745
|
+
language: z.string().optional().describe('\'en\' si esta pieza es la traducción de otra. Sin valor = idioma original.'),
|
|
746
|
+
translationOf: z.string().optional().describe('Si esta pieza es una traducción, id del content original'),
|
|
747
|
+
})
|
|
748
|
+
|
|
749
|
+
registerUpdate('update_content', '/v1/content', 'Edita una publicación de contenido existente. Al pasar status a "approved" o "published" el server estampa quién/cuándo solo -- no lo mandes tú.', {
|
|
750
|
+
channelId: z.string().optional(),
|
|
751
|
+
title: z.string().optional(),
|
|
752
|
+
format: z.string().optional(),
|
|
753
|
+
status: z.enum(CONTENT_STATUSES).optional(),
|
|
754
|
+
caption: z.string().optional(),
|
|
755
|
+
hashtags: z.array(z.string()).optional(),
|
|
756
|
+
scheduledAt: z.number().optional(),
|
|
757
|
+
approvalRequired: z.boolean().optional(),
|
|
758
|
+
approverUid: z.string().optional(),
|
|
759
|
+
changelogVersion: z.string().optional(),
|
|
760
|
+
moduleId: z.string().optional(),
|
|
761
|
+
ideaId: z.string().optional(),
|
|
762
|
+
campaignId: z.string().optional(),
|
|
763
|
+
organicRate: z.number().optional(),
|
|
764
|
+
leads: z.number().optional(),
|
|
765
|
+
reach: z.number().optional(),
|
|
766
|
+
clicksToSite: z.number().optional(),
|
|
767
|
+
saves: z.number().optional(),
|
|
768
|
+
avgReadMinutes: z.number().optional(),
|
|
769
|
+
language: z.string().optional(),
|
|
770
|
+
translationOf: z.string().optional(),
|
|
771
|
+
})
|
|
772
|
+
|
|
532
773
|
// ═══ QA (módulo de runners -- ver orquestra-qa-agent) ═══════════════════════
|
|
533
774
|
// Asíncronas: una corrida puede tardar minutos en otra máquina. Ver
|
|
534
775
|
// SERVER_INSTRUCTIONS para la regla de no asumir "passed" sin confirmar.
|
|
@@ -548,9 +789,14 @@ export function createServer() {
|
|
|
548
789
|
stack: z.enum(QA_AGENT_STACKS),
|
|
549
790
|
runnerId: z.string().describe('ID de la máquina emparejada -- ver list_qa_agents o Workspace → QA'),
|
|
550
791
|
localPath: z.string().describe('Ruta absoluta del checkout en esa máquina'),
|
|
551
|
-
branch: z.string().optional().describe('
|
|
792
|
+
branch: z.string().optional().describe('Rama de este agente -- default "main" si no se manda'),
|
|
552
793
|
distributionId: z.string().optional().describe('Distribución vinculada (repo/fuente de conocimiento)'),
|
|
553
794
|
enabled: z.boolean().optional(),
|
|
795
|
+
createTasks: z.boolean().optional().describe('Si los fallos de este agente generan tareas automáticas -- default true'),
|
|
796
|
+
openStatusId: z.string().optional().describe('Estatus donde se abren los bugs de este agente -- default: estatus inicial del proyecto'),
|
|
797
|
+
closeStatusId: z.string().optional().describe('Estatus donde se mueven los bugs de este agente al cerrarse solos -- default: primer estatus final'),
|
|
798
|
+
testUserEmail: z.string().optional().describe('Cuenta de prueba DEDICADA (no una credencial real) que la revisión con IA puede usar para iniciar sesión y revisar pantallas autenticadas'),
|
|
799
|
+
testUserPassword: z.string().optional().describe('Password de testUserEmail'),
|
|
554
800
|
})
|
|
555
801
|
|
|
556
802
|
registerUpdate('update_qa_agent', '/v1/qaAgents', 'Edita un agente de QA existente.', {
|
|
@@ -561,11 +807,16 @@ export function createServer() {
|
|
|
561
807
|
branch: z.string().optional(),
|
|
562
808
|
distributionId: z.string().optional(),
|
|
563
809
|
enabled: z.boolean().optional(),
|
|
810
|
+
createTasks: z.boolean().optional(),
|
|
811
|
+
openStatusId: z.string().optional(),
|
|
812
|
+
closeStatusId: z.string().optional(),
|
|
813
|
+
testUserEmail: z.string().optional().describe('Cuenta de prueba DEDICADA (no una credencial real) para que la revisión con IA inicie sesión'),
|
|
814
|
+
testUserPassword: z.string().optional(),
|
|
564
815
|
})
|
|
565
816
|
|
|
566
|
-
registerCreate('create_qa_run', '/v1/qa/runs', 'Dispara una corrida de QA (lint/tests/build/E2E según el stack) en la rama indicada.
|
|
817
|
+
registerCreate('create_qa_run', '/v1/qa/runs', 'Dispara una corrida de QA (lint/tests/build/E2E según el stack) en la rama indicada. Necesita al menos un agente de QA activo para el stack (o falla explícito -- ver list_qa_agents/create_qa_agent). La corrida queda "queued" -- nunca asumas que pasó, confirma con get_qa_run.', {
|
|
567
818
|
agentId: z.string().optional().describe('Agente específico -- si se manda, gana sobre stack'),
|
|
568
|
-
branch: z.string().optional().describe('Rama a probar -- default: la del agente, o
|
|
819
|
+
branch: z.string().optional().describe('Rama a probar -- default: la del agente, o "main"'),
|
|
569
820
|
commit: z.string().optional(),
|
|
570
821
|
trigger: z.enum(['manual', 'push', 'schedule']).optional().describe('Default: manual'),
|
|
571
822
|
stack: z.string().optional().describe('Stack a probar (ej. "web", "ios", "android") -- resuelve contra los agentes activos de ese stack. Falla si hay 0 (créalo con create_qa_agent) o más de 1 (usa agentId).'),
|
package/src/setup.mjs
CHANGED
|
@@ -75,9 +75,13 @@ export async function runSetup() {
|
|
|
75
75
|
const idToken = await idTokenPromise
|
|
76
76
|
console.log('Login recibido, configurando...')
|
|
77
77
|
|
|
78
|
+
const agentLabelAnswer = await prompt('¿Cómo se llama este agente/herramienta? [Claude Code]: ')
|
|
79
|
+
const agentLabel = agentLabelAnswer.trim() || 'Claude Code'
|
|
80
|
+
|
|
78
81
|
const res = await fetch(`${apiBase}/v1/bootstrap`, {
|
|
79
82
|
method: 'POST',
|
|
80
|
-
headers: { Authorization: `Bearer ${idToken}
|
|
83
|
+
headers: { Authorization: `Bearer ${idToken}`, 'Content-Type': 'application/json' },
|
|
84
|
+
body: JSON.stringify({ agentLabel }),
|
|
81
85
|
})
|
|
82
86
|
const body = await res.json()
|
|
83
87
|
if (!res.ok) throw new Error(body?.error || `HTTP ${res.status}`)
|