orquestra-mcp 1.7.1 → 1.7.9

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.mjs +77 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orquestra-mcp",
3
- "version": "1.7.1",
3
+ "version": "1.7.9",
4
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": {
package/src/server.mjs CHANGED
@@ -17,8 +17,19 @@ const PROJECT_ID_DESC = 'ID del proyecto dentro del workspace configurado. Si ha
17
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
- const DOC_TYPES = ['readme', 'usecase', 'diagram', 'env', 'adr', 'runbook', 'api']
20
+ // 'postmortem' faltaba acá (gap preexistente, encontrado 12 ago 2026) --
21
+ // mismo fix del lado de write.mjs en orquestra-infra.
22
+ const DOC_TYPES = ['readme', 'usecase', 'diagram', 'env', 'adr', 'runbook', 'postmortem', 'api']
21
23
  const ADR_STATUSES = ['propuesto', 'aceptado', 'deprecado', 'rechazado']
24
+ const DOC_FOLDERS = ['arquitectura', 'api', 'operaciones', 'decisiones']
25
+ const DOC_VISIBILITY = ['team', 'workspace', 'public']
26
+ // 'hecha' agregado 12 ago 2026 -- ver misma nota en write.mjs de
27
+ // orquestra-infra (convertToTask ya no borra la idea, la deja en 'hecha').
28
+ const IDEA_STATUSES = ['nueva', 'en_estudio', 'planeada', 'hecha', 'en_espera', 'descartada']
29
+ const IDEA_ORIGINS = ['cliente', 'soporte', 'equipo', 'ventas']
30
+ const IDEA_IMPACT_EFFORT = ['alto', 'bajo']
31
+ const NOTE_TYPES = ['decision', 'incidente', 'cliente', 'planificacion']
32
+ const NOTE_VISIBILITY = ['team', 'private']
22
33
  const SERVICE_TYPES = ['backend', 'database', 'auth', 'storage', 'push', 'email', 'payments', 'analytics', 'realtime', 'search', 'maps', 'flags', 'admin', 'cdn', 'queue']
23
34
  const PLATFORM_TYPES = ['android', 'ios', 'web', 'desktop-mac', 'desktop-win', 'backend', 'extension', 'other']
24
35
  const INFRA_STATUSES = ['operational', 'degraded', 'down']
@@ -56,11 +67,14 @@ Si get_project_context devuelve un proyecto vacío (sin módulos ni tareas), es
56
67
 
57
68
  Durante el trabajo normal de código, sin que el usuario te lo pida explícitamente y SIN ESPERAR a que lo mencione (si tienes esta tool disponible en la sesión, se usa por default en todo trabajo real, no solo cuando alguien dice "usa Orquestra"):
58
69
  - Si te pide una funcionalidad o arreglo real (no una pregunta rápida ni exploración de código sin cambios), revisa con list_tasks/search si ya existe una tarea relacionada; si no, créala con create_task ANTES de empezar a trabajar, en el estatus inicial del proyecto (ver list_statuses). Si no hay un proyecto default configurado para esta carpeta, no te rindas ahí -- usa get_workspace_summary para encontrar el proyecto correcto y pásalo explícito como projectId en cada llamada.
59
- - Mientras trabajas, mueve la tarea por los estatus reales según avanzas (no la dejes en el inicial hasta el final). Al terminar, muévela a su estatus final con update_task y registra una entrada de changelog con create_changelog_entry -- así el usuario nunca tiene que documentar esto a mano.
70
+ - Mientras trabajas, mueve la tarea por los estatus reales según avanzas (no la dejes en el inicial hasta el final). Al terminar, muévela a su estatus final con update_task y registra una entrada de changelog con create_changelog_entry -- así el usuario nunca tiene que documentar esto a mano. Si sabes en qué repo real estás trabajando (podés correr "git remote get-url origin" o ya lo sabes por la carpeta), pásalo SIEMPRE como "repo" ("owner/name") -- encontrado real (17 ago 2026): ni el hook local ni el GitHub Action ni la reconciliación de orquestra-infra lo estaban mandando pese a que el campo existe hace rato, así que ninguna entrada automática sabía de qué repo/plataforma venía. Si además la distribución de ese repo ya está identificada (ver list_distributions), pásala en "distributions" -- sin esto el changelog no puede filtrar/agrupar por plataforma.
60
71
  - Todo cambio de statusId vía update_task valida la transición (nextIds) y queda en el historial de la tarea -- pero ese historial no dice nada por sí solo. Manda SIEMPRE el parámetro "comment" cuando cambies statusId, explicando qué hiciste o por qué -- sin esto, el usuario ve que la tarea cambió pero no sabe por qué ni qué se hizo de verdad (encontrado real: quedaba invisible, a diferencia de cuando un humano mueve la tarjeta a mano en el kanban).
61
72
  - Antes de crear un módulo/estatus/categoría/servicio/entorno/distribución, revisa con el list_* correspondiente que no exista ya uno con ese nombre.
62
73
  - Si una nota que vas a crear (create_note) es claramente sobre un módulo o una tarea específica -- por ejemplo durante un setup guiado donde acabas de crear ambos -- mándale moduleId/taskId para ligarla, en vez de dejarla suelta. No inventes la relación si no es clara.
74
+ - create_changelog_entry SIEMPRE deja la entrada en Unreleased (nunca mandes version salvo que el usuario te haya dado un número real) -- cortar una versión es release_changelog_version, una acción DELIBERADA que solo se llama si el usuario la pide explícitamente ("cortá la versión", "hacé el release de X") o confirma un número/fecha real que vos propusiste. No la dispares solo porque terminaste una tarea o un lote de trabajo grande.
63
75
  - 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.
76
+ - Al crear o editar una tarea (o un módulo), llena TODO campo para el que tengas un dato real, no solo title/moduleId/dueDate/comment: type (no lo dejes en el default "feature" si en realidad es un bug/mejora/doc/etc.), tags, stack (tecnologías que genuinamente tocaste), scope (paridad multiplataforma, si aplica), y los campos de código (branch/prNumber/prUrl/prStatus/commitCount/resolvedCommitSha/resolvedCommitMessage/resolvedCommitUrl/resolvedCommitAt) cuando de verdad sepas esos valores (el branch en el que estás trabajando, el PR que abriste, el commit que resolvió la tarea). No dejes un campo en blanco solo porque no es obligatorio -- pero esto no es licencia para inventar: si no tenés el dato real (ej. no hay branch/PR porque el proyecto commitea directo a main), dejalo vacío en vez de rellenarlo con algo plausible pero falso -- mismo criterio que ya se pedía para resolvedCommitSha.
77
+ - Si una tarea tiene más de una actividad o paso concreto, no los amontones como texto corrido en description -- usa el checklist (add_checklist_item, un ítem por actividad, opcionalmente con responsable) para que cada paso se pueda marcar independientemente. description queda para el contexto/propósito general de la tarea (el qué y el por qué), no como lista de pasos. Si estás editando una tarea existente cuya description ya creció así, migra esos puntos a checklist en vez de seguir agregando ahí.
64
78
  - 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.
65
79
 
66
80
  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:
@@ -432,12 +446,27 @@ export function createServer() {
432
446
  {
433
447
  title: z.string().describe('Título de la idea'),
434
448
  description: z.string().optional(),
435
- priority: z.enum(TASK_PRIORITIES).optional().describe('Default: medium'),
449
+ priority: z.enum(TASK_PRIORITIES).optional().describe('Default: medium -- se puede derivar de impact+effort si los mandas (alto+bajo→high, alto+alto o bajo+bajo→medium, bajo+alto→low).'),
436
450
  categoryId: z.string().optional(),
437
451
  dueDate: z.string().optional().describe('YYYY-MM-DD'),
452
+ moduleId: z.string().optional(),
453
+ status: z.enum(IDEA_STATUSES).optional().describe('Default: nueva. "hecha" es a dónde llega una idea convertida en tarea (ver convertToTask en la web) -- no la mandes vos al crear, no tiene sentido crear una idea ya "hecha".'),
454
+ origin: z.enum(IDEA_ORIGINS).optional(),
455
+ requestedBy: z.array(z.object({
456
+ name: z.string().describe('Nombre del cliente, en texto libre (no hay entidad "cliente" en Orquestra)'),
457
+ quote: z.string().optional().describe('Cita textual de por qué lo pidió, si la tenés'),
458
+ })).optional().describe('Quién pidió esta idea -- cada uno suma +1 a voteCount. `requestedAt` se estampa solo, server-side, no lo mandes.'),
459
+ impact: z.enum(IDEA_IMPACT_EFFORT).optional(),
460
+ effort: z.enum(IDEA_IMPACT_EFFORT).optional(),
461
+ risk: z.enum(IDEA_IMPACT_EFFORT).optional().describe('Riesgo de esta idea (alto/bajo) -- agregado 12 ago 2026, alimenta la recomendación del detalle de idea junto con impact/effort.'),
462
+ voteCount: z.number().optional().describe('Default: 1 + requestedBy.length'),
438
463
  }
439
464
  )
440
465
 
466
+ registerRead('list_changelog', '/v1/changelog',
467
+ 'Lista TODAS las entradas de changelog de un proyecto, con todos sus campos (id, section, text, version, distributions, repo, taskId, createdAt, createdBy) -- a diferencia de get_dashboard_summary/get_project_context, que solo traen un resumen recortado (texto/título, sin repo ni distributions). Útil para auditar (ej. "¿qué entradas viejas no tienen repo?") antes de update_changelog_entry.',
468
+ {}, (args, projectId) => ({ projectId }))
469
+
441
470
  registerCreate(
442
471
  'create_changelog_entry',
443
472
  '/v1/changelog',
@@ -446,6 +475,37 @@ export function createServer() {
446
475
  section: z.enum(CHANGELOG_SECTIONS).describe('Sección del changelog'),
447
476
  text: z.string().describe('Descripción de la entrada'),
448
477
  version: z.string().optional(),
478
+ distributions: z.array(z.object({
479
+ distId: z.string(),
480
+ version: z.string().optional(),
481
+ })).optional().describe('Distribuciones a las que aplica esta entrada (ver list_distributions) -- vacío/omitido = aplica a todo el proyecto. Cada una puede tener su propia versión dentro de esta entrada.'),
482
+ repo: z.string().optional().describe('"owner/name" del repo de origen del commit/PR que generó esta entrada, si se sabe -- alimenta el chip de repo en la tarjeta de versión.'),
483
+ taskId: z.string().optional().describe('Tarea que esta entrada documenta o cierra, si se sabe (ver list_tasks) -- alimenta el chip de tarea, clicable al detalle, en la tarjeta de versión.'),
484
+ }
485
+ )
486
+
487
+ registerUpdate('update_changelog_entry', '/v1/changelog',
488
+ 'Edita una entrada de changelog existente (section/text/version/distributions/repo/taskId) -- primera edición puntual por id que existe (antes solo había create + el bulk-update de release_changelog_version). Pensada sobre todo para backfill/corrección (ej. completar "repo" en entradas viejas que se crearon antes de que ese campo se mandara), no para reescribir historia -- si el usuario solo quiere corregir el texto de una entrada reciente por error de tipeo, esto también sirve.',
489
+ {
490
+ section: z.enum(CHANGELOG_SECTIONS).optional(),
491
+ text: z.string().optional(),
492
+ version: z.string().optional(),
493
+ distributions: z.array(z.object({
494
+ distId: z.string(),
495
+ version: z.string().optional(),
496
+ })).optional(),
497
+ repo: z.string().optional().describe('"owner/name" del repo de origen.'),
498
+ taskId: z.string().optional(),
499
+ }
500
+ )
501
+
502
+ registerCreate(
503
+ 'release_changelog_version',
504
+ '/v1/changelog/release',
505
+ 'Corta una versión del changelog: toma TODAS las entradas que están en Unreleased (o, si mandas distributionId, solo las de esa distribución) y les asigna un número de versión real, mismo efecto que el botón "+ Nueva versión" de la web -- también actualiza distribution.version de las distribuciones tocadas. NUNCA lo llames sin que el usuario lo haya pedido explícitamente (o confirmado un número/fecha de release real) -- cortar una versión es una decisión de producto, no un paso automático de "buena higiene" como sí lo es create_changelog_entry.',
506
+ {
507
+ version: z.string().optional().describe('Semver real, ej. "1.4.0". Si se omite, se sugiere solo (Removed/Security -> mayor, Added -> menor, resto -> parche) a partir de la versión conocida más alta -- decíselo al usuario ANTES de aplicar si vos elegiste el número, no lo apliques en silencio.'),
508
+ distributionId: z.string().optional().describe('Si se manda, solo libera las entradas de ESA distribución (ver list_distributions) -- las entradas globales/de otras distribuciones quedan en Unreleased. Sin esto, libera todo el proyecto.'),
449
509
  }
450
510
  )
451
511
 
@@ -459,6 +519,11 @@ export function createServer() {
459
519
  pinned: z.boolean().optional(),
460
520
  moduleId: z.string().optional().describe('Módulo relacionado, si la nota es sobre uno específico -- ligarla ayuda a encontrarla después (ver list_modules)'),
461
521
  taskId: z.string().optional().describe('Tarea relacionada, si la nota es sobre una específica (ver list_tasks)'),
522
+ type: z.enum(NOTE_TYPES).optional().describe('Default: planificacion (nota sin tipo especial). "decision" habilita alternativesConsidered/wouldRevertIf.'),
523
+ alternativesConsidered: z.string().optional().describe('Solo para type=\'decision\'. Qué más se evaluó y por qué no.'),
524
+ wouldRevertIf: z.string().optional().describe('Solo para type=\'decision\'. La condición que haría cambiar de opinión -- sin esto la decisión se vuelve dogma.'),
525
+ visibility: z.enum(NOTE_VISIBILITY).optional().describe('Default: team. "private" restringe la lectura a quien la creó (enforcement real en firestore.rules, no solo la UI).'),
526
+ relatedNoteId: z.string().optional().describe('Otra nota relacionada, si es clara la relación (ver search) -- agregado 12 ago 2026. No inventes el vínculo si no es evidente.'),
462
527
  }
463
528
  )
464
529
 
@@ -473,6 +538,11 @@ export function createServer() {
473
538
  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
539
  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
540
  adrConsequences: z.string().optional().describe('Solo para type=\'adr\'. Qué cambia de ahora en adelante.'),
541
+ moduleId: z.string().optional().describe('Módulo al que pertenece (ver list_modules) -- vincularlo es lo que permite detectar "puede estar desactualizado" cuando el módulo acumula actividad después de la última edición.'),
542
+ folder: z.enum(DOC_FOLDERS).optional().describe('Carpeta donde se archiva en la UI -- independiente de `type` (ej. un ADR se puede archivar en cualquier carpeta).'),
543
+ visibility: z.enum(DOC_VISIBILITY).optional().describe('"team" (default) | "workspace" | "public". "public" expone el doc sin sesión en una URL propia.'),
544
+ reviewOwnerUid: z.string().optional().describe('uid de quién recibe el aviso cuando el doc queda desactualizado.'),
545
+ reviewIntervalDays: z.number().optional().describe('Cada cuántos días revisar por calendario (30/90/180) -- sin esto, solo se detecta vencimiento por actividad del módulo vinculado, nunca por fecha.'),
476
546
  }
477
547
  )
478
548
 
@@ -531,10 +601,14 @@ export function createServer() {
531
601
  registerCreate('create_category', '/v1/categories', 'Crea una categoría (agrupación/audiencia) en un proyecto.', {
532
602
  name: z.string().describe('Nombre de la categoría'),
533
603
  color: z.string().optional().describe('Color hex'),
604
+ description: z.string().optional().describe('Para qué sirve esta categoría, en pocas palabras -- agregado 12 ago 2026.'),
605
+ order: z.number().optional().describe('Orden manual de aparición (más chico = más arriba) -- agregado 12 ago 2026.'),
534
606
  })
535
607
  registerUpdate('update_category', '/v1/categories', 'Edita una categoría existente.', {
536
608
  name: z.string().optional(),
537
609
  color: z.string().optional(),
610
+ description: z.string().optional(),
611
+ order: z.number().optional(),
538
612
  })
539
613
 
540
614
  // Campos específicos por tipo de servicio -- unión deduplicada de las