orquestra-mcp 1.52.0 → 1.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/server.mjs +84 -10
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# orquestra-mcp
|
|
2
2
|
|
|
3
|
-
Servidor MCP (Model Context Protocol) para Orquestra. Expone **
|
|
3
|
+
Servidor MCP (Model Context Protocol) para Orquestra. Expone **148 tools**
|
|
4
4
|
(conteo al 24 oct 2026, v1.49.0; la lista real está en `src/server.mjs`)
|
|
5
5
|
que llaman a la API de `orquestra-infra`: lectura de contexto/dashboard/
|
|
6
6
|
workspace/infra/paridad/listados, escritura de tareas/ideas/changelog/
|
|
@@ -229,7 +229,7 @@ create/update sigue siendo criterio del agente y del humano.
|
|
|
229
229
|
|
|
230
230
|
## Estado
|
|
231
231
|
|
|
232
|
-
|
|
232
|
+
148 tools (ver arriba): lectura (contexto, dashboard, workspace, infra, paridad, standup,
|
|
233
233
|
sync report, setup guide, playbooks de etapa y de plataforma, catálogo de
|
|
234
234
|
skills, search) + `list_*` de cada colección + creación/edición de tareas
|
|
235
235
|
(con checklist, verificaciones por plataforma, tiempo, adjuntos, merge), ideas (con comentarios), notas,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orquestra-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.54.0",
|
|
4
4
|
"description": "Servidor MCP de Orquestra -- 135 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
|
@@ -35,6 +35,11 @@ const TASK_TYPES = ['feature', 'bug', 'improvement', 'tech', 'docs', 'idea', 'de
|
|
|
35
35
|
const ACTIVITY_TYPES = ['meeting', 'call', 'followup']
|
|
36
36
|
const ACTIVITY_STATUSES = ['scheduled', 'done', 'cancelled']
|
|
37
37
|
const TASK_PRIORITIES = ['high', 'medium', 'low']
|
|
38
|
+
const WORK_CATEGORIES = ['plan', 'build', 'delivery', 'review']
|
|
39
|
+
const PHASE_STATUSES = ['pending', 'active', 'done']
|
|
40
|
+
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/
|
|
41
|
+
const START_DATE_DESC = 'YYYY-MM-DD, fecha de inicio planeada (opcional). Debe ser <= dueDate (400 si no). "" la quita.'
|
|
42
|
+
const WORK_CATEGORY_DESC = 'Categoría de trabajo: plan | build | delivery | review. Si no la mandas, la API la deriva del type de la tarea; mandarla fija un override. "" quita el override y vuelve a la derivada.'
|
|
38
43
|
// Relaciones (ORQ-326, ORQ-347) -- misma lista que orquestra-infra/schema/enums/RelationItemType.json.
|
|
39
44
|
const RELATION_TYPES = ['task', 'activity', 'note', 'doc', 'idea', 'risk', 'content', 'changelog', 'distribution', 'service']
|
|
40
45
|
// `relatedTo` en los create_* (ORQ-347): de dónde sale lo que se crea. El server valida el destino
|
|
@@ -134,7 +139,7 @@ Durante el trabajo normal de código, sin que el usuario te lo pida explícitame
|
|
|
134
139
|
- Paridad por producto: las distribuciones que son el mismo producto en distintas plataformas (típicamente Web/iOS/Android) comparten un productKey (list_distributions lo muestra). Si al crear una tarea con scope en una de ellas la respuesta trae parityAddedDistIds, el server sumó las hermanas como pendientes -- dícelo al usuario ("también quedó pendiente en iOS y Android") y no lo deshagas salvo que el trabajo sea genuinamente de una sola plataforma (entonces skipParity:true, o quítalas con update_task/scope explicando por qué). Al cerrar una tarea con scope multiplataforma, marca done:true SOLO en la plataforma donde de verdad se hizo (update_task/scope) -- el estatus "Listo" no mueve esos flags solo; cuando un commit o PR del repo de otra plataforma menciona el código de la tarea (ej. "(ORQ-123)"), el servidor propone solo una sugerencia scope_done_proposal (list_suggestions) para marcar esa plataforma como hecha: si la ves, confirma con el código real y acéptala con accept_suggestion en vez de editar el scope a mano. Si el trabajo de otra plataforma ya vive en OTRA tarea (una tarea gemela, ej. "[iOS] ..." y "[Android] ..."), no dupliques scope: liga la hermana con update_task parityOf=<id de la tarea origen> y en paridad cuentan como un solo item; al cerrar la hermana su plataforma queda hecha sola. Dejar las hermanas en done:false es exactamente lo que hace que el desfase aparezca en get_parity_summary y en la revisión de sincronización (tasksClosedWithParityPending), que es el punto: que nunca se olvide el trabajo del otro lado. Si el proyecto tiene 2+ plataformas y ninguna tiene productKey (get_parity_summary → productGroups.configured=false), pregúntale al usuario cuáles son el mismo producto y configúralo con update_distribution -- nunca lo infieras solo del tipo de plataforma.
|
|
135
140
|
- 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.
|
|
136
141
|
- 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í.
|
|
137
|
-
- Una tarea = UN entregable; un ítem del checklist = UN paso corto de ese entregable. Si el título junta varias cosas con comas o "y" ("X, Y y Z"), o un ítem del checklist tendría su propio deploy, commits, pruebas o alertas (es una integración/función entera, no un paso), son tareas separadas: créalas aparte en el mismo módulo (si son muchas del mismo tema, ese tema es un módulo, no una tarea con 14 ítems). Lo que depende de algo externo o futuro (un contrato, un proveedor, una versión que todavía no sale, la respuesta de una tienda) va en su propia tarea -- en Bloqueado o con reviewDate --, nunca dentro de una tarea que ya está lista, porque la deja abierta indefinidamente. El texto de un ítem es la acción ("Configurar cron diario de la CSL"), no la bitácora: commits, "probado en vivo", tiempos y tamaños van en un comment de la tarea, en resolvedCommitSha o en el changelog. Algo que ya no aplica no se deja como ítem pendiente con "ya no hace falta" en el texto: díselo al usuario y
|
|
142
|
+
- Una tarea = UN entregable; un ítem del checklist = UN paso corto de ese entregable. Si el título junta varias cosas con comas o "y" ("X, Y y Z"), o un ítem del checklist tendría su propio deploy, commits, pruebas o alertas (es una integración/función entera, no un paso), son tareas separadas: créalas aparte en el mismo módulo (si son muchas del mismo tema, ese tema es un módulo, no una tarea con 14 ítems). Lo que depende de algo externo o futuro (un contrato, un proveedor, una versión que todavía no sale, la respuesta de una tienda) va en su propia tarea -- en Bloqueado o con reviewDate --, nunca dentro de una tarea que ya está lista, porque la deja abierta indefinidamente. El texto de un ítem es la acción ("Configurar cron diario de la CSL"), no la bitácora: commits, "probado en vivo", tiempos y tamaños van en un comment de la tarea, en resolvedCommitSha o en el changelog. Algo que ya no aplica no se deja como ítem pendiente con "ya no hace falta" en el texto ni se marca done: díselo al usuario y descártalo con update_checklist_item skipped:true + skipReason (queda tachado con su motivo, no bloquea el cierre y no cuenta como hecho); delete_checklist_item solo si el ítem fue un error. No reorganices tareas que ya existen así salvo que el usuario lo pida -- la regla es para lo nuevo.
|
|
138
143
|
- Al cerrar una tarea en la que trabajaste de verdad (o al final de una sesión de trabajo real sobre ella), registrá las horas con log_time -- el tiempo REAL que llevó ese trabajo en esta sesión (aproximalo con honestidad: minutos de trabajo real, no la duración del chat ni la estimación de la tarea), con una nota corta de qué se hizo. Ese registro alimenta "Horas del equipo" en Inversión de Contenido y es la única forma de que el costo real del proyecto no dependa de que el usuario lo cargue a mano. No lo inventes ni lo infles; si genuinamente no podés estimar el tiempo de la sesión, no registres nada.
|
|
139
144
|
- 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_agents que exista un agente (kind="qa") para el stack que tocaste (si no hay ninguno, créalo con create_agent -- necesita una máquina ya emparejada, ver Workspace → Máquinas) y llama a create_agent_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_agent_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.
|
|
140
145
|
|
|
@@ -287,6 +292,7 @@ export function createServer() {
|
|
|
287
292
|
priority: z.enum(TASK_PRIORITIES).optional().describe('Prioridad para filtrar (opcional)'),
|
|
288
293
|
assignedTo: z.string().optional().describe('uid del responsable para filtrar (opcional)'),
|
|
289
294
|
tag: z.string().optional().describe('Un tag exacto para filtrar -- solo uno, no una lista (opcional)'),
|
|
295
|
+
workCategory: z.enum(WORK_CATEGORIES).optional().describe('Categoría de trabajo para filtrar (opcional): plan | build | delivery | review'),
|
|
290
296
|
includeClosed: z.boolean().optional().describe('Default false -- sin esto, tareas en un estatus isFinal/isCancel/isArchive quedan afuera. Ponelo en true solo si de verdad necesitás el historial cerrado (auditoría, "¿cuándo se hizo X?").'),
|
|
291
297
|
},
|
|
292
298
|
(args, projectId) => ({
|
|
@@ -297,6 +303,7 @@ export function createServer() {
|
|
|
297
303
|
priority: args.priority,
|
|
298
304
|
assignedTo: args.assignedTo,
|
|
299
305
|
tag: args.tag,
|
|
306
|
+
workCategory: args.workCategory,
|
|
300
307
|
includeClosed: args.includeClosed,
|
|
301
308
|
})
|
|
302
309
|
)
|
|
@@ -771,6 +778,8 @@ export function createServer() {
|
|
|
771
778
|
categoryId: z.string().optional(),
|
|
772
779
|
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.'),
|
|
773
780
|
dueDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Formato esperado: YYYY-MM-DD').describe('OBLIGATORIO -- fecha estimada de término (YYYY-MM-DD), calculada con criterio real según prioridad/tamaño del trabajo (mismo criterio que la "revisión de sincronización" de get_sync_report), nunca un valor arbitrario.'),
|
|
781
|
+
startDate: z.string().regex(DATE_RE, 'Formato esperado: YYYY-MM-DD').optional().describe(`Inicio planeado de la tarea. ${START_DATE_DESC}`),
|
|
782
|
+
workCategory: z.enum(WORK_CATEGORIES).optional().describe(WORK_CATEGORY_DESC),
|
|
774
783
|
reviewDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Formato esperado: YYYY-MM-DD').optional().describe('Fecha para volver a MIRAR esta tarea -- recordatorio, NO un entregable. Para un aviso CON HORA (ej. en 5 horas) usa set_task_reminder. Úsalo para tareas de espera sin deadline real (ej. "esperar respuesta de Apple y revisar en 5 días"): en ese caso NO llenes dueDate, llena reviewDate. Distinto de dueDate (fecha de término real de un entregable) -- una tarea puede tener ambos, uno solo, o ninguno. Dispara un aviso (notificación + correo) al assignedTo el día que llega, un solo disparo.'),
|
|
775
784
|
assignedTo: z.string().optional().describe('uid del miembro asignado'),
|
|
776
785
|
tags: z.array(z.string()).optional(),
|
|
@@ -806,7 +815,7 @@ export function createServer() {
|
|
|
806
815
|
registerUpdate(
|
|
807
816
|
'update_task',
|
|
808
817
|
'/v1/tasks',
|
|
809
|
-
'Edita una tarea existente (incluido su scope multiplataforma, para resolver desfases de paridad). Si cambias statusId, el server valida que la transición sea válida (nextIds del estatus actual -- ver list_statuses) y deja rastro en el historial de la tarea (quién/cuándo/de qué a qué) -- pásale SIEMPRE `comment` cuando cambies statusId para que ese rastro también diga QUÉ hiciste o POR QUÉ, no solo el cambio de id. No deja pasar a un estatus final (isFinal) con ítems del checklist sin terminar (400 "Quedan X de Y..."): marca primero los ítems que de verdad hiciste con update_checklist_item; usa completeChecklist:true solo si el usuario confirma que lo pendiente ya
|
|
818
|
+
'Edita una tarea existente (incluido su scope multiplataforma, para resolver desfases de paridad). Si cambias statusId, el server valida que la transición sea válida (nextIds del estatus actual -- ver list_statuses) y deja rastro en el historial de la tarea (quién/cuándo/de qué a qué) -- pásale SIEMPRE `comment` cuando cambies statusId para que ese rastro también diga QUÉ hiciste o POR QUÉ, no solo el cambio de id. No deja pasar a un estatus final (isFinal) con ítems del checklist sin terminar (400 "Quedan X de Y..."): marca primero los ítems que de verdad hiciste con update_checklist_item y descarta con skipped:true + skipReason los que ya no aplican (no bloquean); usa completeChecklist:true solo si el usuario confirma que lo pendiente ya está hecho. Tampoco deja cerrar con verificaciones por plataforma abiertas (pending/failed, ver list_verifications): no cierres a mano -- deja la tarea en "En revisión" y se cierra sola cuando todas quedan aprobadas; waiveVerifications:true solo con confirmación explícita del usuario.',
|
|
810
819
|
{
|
|
811
820
|
title: z.string().optional(),
|
|
812
821
|
description: z.string().optional(),
|
|
@@ -818,6 +827,8 @@ export function createServer() {
|
|
|
818
827
|
categoryId: z.string().optional(),
|
|
819
828
|
moduleId: z.string().optional(),
|
|
820
829
|
dueDate: z.string().optional().describe('YYYY-MM-DD'),
|
|
830
|
+
startDate: z.string().optional().describe(`Inicio planeado de la tarea. ${START_DATE_DESC}`),
|
|
831
|
+
workCategory: z.union([z.enum(WORK_CATEGORIES), z.literal('')]).optional().describe(WORK_CATEGORY_DESC),
|
|
821
832
|
reviewDate: z.string().optional().describe('YYYY-MM-DD. Ver create_task -- recordatorio para volver a revisar, distinto de dueDate. Mandá "" para quitarlo.'),
|
|
822
833
|
assignedTo: z.string().optional(),
|
|
823
834
|
tags: z.array(z.string()).optional(),
|
|
@@ -861,8 +872,8 @@ export function createServer() {
|
|
|
861
872
|
location: z.string().optional().describe('Plataforma o lugar ("Zoom", "Oficina del cliente") o contexto libre para un seguimiento.'),
|
|
862
873
|
participants: z.array(z.string()).optional().describe('Nombres libres de quienes participan (cliente, equipo) -- no uids.'),
|
|
863
874
|
description: z.string().optional().describe('Notas previas.'),
|
|
864
|
-
agendaItems: z.array(z.union([z.string(), z.object({ id: z.string().optional(), text: z.string() })])).optional()
|
|
865
|
-
.describe('Puntos a revisar en la reunión (ej. "Revisar [[ORQ-123]]"). Lista simple, NO checklist. Reemplaza la lista COMPLETA: para agregar uno, lee la actual con list_activities y manda todos
|
|
875
|
+
agendaItems: z.array(z.union([z.string(), z.object({ id: z.string().optional(), text: z.string(), group: z.string().max(60).optional(), createdAt: z.number().optional(), createdBy: z.string().optional() })])).optional()
|
|
876
|
+
.describe('Puntos a revisar en la reunión (ej. "Revisar [[ORQ-123]]"). Lista simple, NO checklist. Cada punto puede llevar group (agrupa puntos bajo un encabezado, máx. 60 caracteres). Reemplaza la lista COMPLETA: para agregar uno, lee la actual con list_activities y manda todos, con sus id para conservarlos Y con su group -- si reenvías un punto sin group, pierde su grupo.'),
|
|
866
877
|
outcome: z.string().optional().describe('Resultado / minuta corta -- qué se acordó. Llénalo al marcarla hecha.'),
|
|
867
878
|
nextStep: z.string().optional().describe('Siguiente paso acordado. Si es trabajo real, además crea la tarea con create_task y liga su id en nextStepTaskId.'),
|
|
868
879
|
nextStepTaskId: z.string().optional().describe('id de la tarea creada desde nextStep.'),
|
|
@@ -1570,7 +1581,7 @@ export function createServer() {
|
|
|
1570
1581
|
server.registerTool(
|
|
1571
1582
|
'list_checklist_items',
|
|
1572
1583
|
{
|
|
1573
|
-
description: 'Lista los ítems del checklist de una tarea de Orquestra (texto, hecho/pendiente, responsable).',
|
|
1584
|
+
description: 'Lista los ítems del checklist de una tarea de Orquestra (texto, hecho/pendiente, responsable, y si está descartado como «No aplica»: skipped + skipReason). Un ítem descartado no bloquea el cierre ni cuenta como hecho.',
|
|
1574
1585
|
inputSchema: {
|
|
1575
1586
|
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
1576
1587
|
taskId: z.string().describe('ID de la tarea'),
|
|
@@ -1610,7 +1621,7 @@ export function createServer() {
|
|
|
1610
1621
|
server.registerTool(
|
|
1611
1622
|
'update_checklist_item',
|
|
1612
1623
|
{
|
|
1613
|
-
description: 'Edita un ítem del checklist de una tarea de Orquestra: texto, hecho/pendiente, o
|
|
1624
|
+
description: 'Edita un ítem del checklist de una tarea de Orquestra: texto, hecho/pendiente, responsable, o descartarlo como «No aplica». Para un paso que ya no hace falta usa skipped:true + skipReason (queda tachado con su motivo, no bloquea el cierre y no cuenta como hecho) en vez de marcarlo done o borrarlo; skipped:false lo devuelve a pendiente. Cada descarte queda comentado en la tarea.',
|
|
1614
1625
|
inputSchema: {
|
|
1615
1626
|
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
1616
1627
|
taskId: z.string().describe('ID de la tarea'),
|
|
@@ -1618,6 +1629,8 @@ export function createServer() {
|
|
|
1618
1629
|
text: z.string().optional(),
|
|
1619
1630
|
done: z.boolean().optional(),
|
|
1620
1631
|
assignedTo: z.string().optional().describe('uid del miembro responsable, o "" para quitarlo'),
|
|
1632
|
+
skipped: z.boolean().optional().describe('true = «No aplica» (exige skipReason; fuerza done:false); false = volver a pendiente'),
|
|
1633
|
+
skipReason: z.string().optional().describe('Motivo del descarte -- obligatorio con skipped:true, concreto ("se resolvió con X", "el proveedor ya lo hace")'),
|
|
1621
1634
|
},
|
|
1622
1635
|
},
|
|
1623
1636
|
async ({ projectId, taskId, itemId, ...body }) => {
|
|
@@ -1641,7 +1654,7 @@ export function createServer() {
|
|
|
1641
1654
|
server.registerTool(
|
|
1642
1655
|
'delete_checklist_item',
|
|
1643
1656
|
{
|
|
1644
|
-
description: 'Borra un ítem del checklist de una tarea de Orquestra. Único borrado real que expone este MCP -- usalo solo cuando
|
|
1657
|
+
description: 'Borra un ítem del checklist de una tarea de Orquestra. Único borrado real que expone este MCP -- usalo solo cuando el ítem fue un error y no tiene sentido dejar rastro (ej. como parte de convert_checklist_item_to_task). Para un paso que ya no aplica NO lo borres ni lo marques done: usa update_checklist_item con skipped:true + skipReason, que conserva el historial.',
|
|
1645
1658
|
inputSchema: {
|
|
1646
1659
|
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
1647
1660
|
taskId: z.string().describe('ID de la tarea'),
|
|
@@ -2395,6 +2408,7 @@ export function createServer() {
|
|
|
2395
2408
|
scope: SCOPE_SCHEMA.describe('Alcance por distribución, ej: [{distId, layer, done}]'),
|
|
2396
2409
|
tags: z.array(z.string()).optional(),
|
|
2397
2410
|
dueDate: z.string().optional().describe('YYYY-MM-DD'),
|
|
2411
|
+
startDate: z.string().optional().describe(`Inicio planeado del módulo. ${START_DATE_DESC}`),
|
|
2398
2412
|
shippedVersion: z.string().optional().describe('Versión del changelog en la que se envió, ej: "1.4.0"'),
|
|
2399
2413
|
ownerUid: z.string().optional().describe('uid del miembro responsable del avance del módulo'),
|
|
2400
2414
|
dependsOnModuleIds: z.array(z.string()).optional().describe('IDs de módulos de los que este depende (dependencia planeada, manual)'),
|
|
@@ -2409,6 +2423,7 @@ export function createServer() {
|
|
|
2409
2423
|
scope: SCOPE_SCHEMA,
|
|
2410
2424
|
tags: z.array(z.string()).optional(),
|
|
2411
2425
|
dueDate: z.string().optional().describe('YYYY-MM-DD'),
|
|
2426
|
+
startDate: z.string().optional().describe(`Inicio planeado del módulo. ${START_DATE_DESC}`),
|
|
2412
2427
|
shippedVersion: z.string().optional().describe('Versión del changelog en la que se envió, ej: "1.4.0"'),
|
|
2413
2428
|
ownerUid: z.string().optional().describe('uid del miembro responsable del avance del módulo'),
|
|
2414
2429
|
dependsOnModuleIds: z.array(z.string()).optional().describe('IDs de módulos de los que este depende (dependencia planeada, manual)'),
|
|
@@ -2416,6 +2431,64 @@ export function createServer() {
|
|
|
2416
2431
|
key: MODULE_KEY.describe(`${MODULE_KEY.description} Poner o cambiar la clave numera las tareas del módulo que todavía no tienen una (por orden de creación; la respuesta trae assignedCodes). "" la quita: las tareas conservan la suya.`),
|
|
2417
2432
|
})
|
|
2418
2433
|
|
|
2434
|
+
// ═══ FASES DEL PLAN (ORQ-438) ═══════════════════════════════════════════════
|
|
2435
|
+
// Fases con fechas relativas al startDate del proyecto (offsetDays/durationDays);
|
|
2436
|
+
// la API calcula effectiveStartDate/effectiveEndDate. milestones y
|
|
2437
|
+
// acceptanceCriteria se REEMPLAZAN completos en cada PATCH.
|
|
2438
|
+
const PHASE_MILESTONES = z.array(z.object({
|
|
2439
|
+
id: z.string().optional(),
|
|
2440
|
+
title: z.string(),
|
|
2441
|
+
offsetDays: z.number().int().min(0).optional().describe('Días desde el inicio de la fase'),
|
|
2442
|
+
fixedDate: z.string().regex(DATE_RE, 'Formato esperado: YYYY-MM-DD').optional().describe('Fecha fija (sustituye al offsetDays)'),
|
|
2443
|
+
done: z.boolean().optional(),
|
|
2444
|
+
})).optional().describe('Hitos de la fase. REEMPLAZA la lista completa: para marcar uno como hecho, lee la actual con list_phases y reenvía todos (con sus id) cambiando solo done.')
|
|
2445
|
+
const PHASE_CRITERIA = z.array(z.union([z.string(), z.object({ id: z.string().optional(), text: z.string(), done: z.boolean().optional() })])).optional()
|
|
2446
|
+
.describe('Criterios de aceptación de la fase (strings sueltos o {id,text,done}). REEMPLAZA la lista completa: para marcar uno como hecho, lee la actual con list_phases y reenvía todos (con sus id) cambiando solo done.')
|
|
2447
|
+
const PHASE_FIELDS = {
|
|
2448
|
+
name: z.string().describe('Nombre de la fase'),
|
|
2449
|
+
description: z.string().optional(),
|
|
2450
|
+
order: z.number().optional().describe('Posición de la fase en el plan'),
|
|
2451
|
+
offsetDays: z.number().int().min(0).optional().describe('Días desde el startDate del proyecto hasta que arranca la fase (>= 0)'),
|
|
2452
|
+
durationDays: z.number().int().min(1).optional().describe('Duración en días (>= 1)'),
|
|
2453
|
+
status: z.enum(PHASE_STATUSES).optional().describe('pending | active | done'),
|
|
2454
|
+
fixedStartDate: z.string().regex(DATE_RE, 'Formato esperado: YYYY-MM-DD').nullable().optional()
|
|
2455
|
+
.describe('Mueve la fase a una fecha de inicio fija (YYYY-MM-DD). EXIGE fixedStartReason (el motivo del cambio de fecha). null la quita y la fase vuelve a calcularse desde el startDate del proyecto.'),
|
|
2456
|
+
fixedStartReason: z.string().optional().describe('Motivo de mover la fecha de inicio (obligatorio al mandar fixedStartDate; la API guarda la fecha previa en fixedStartPrev)'),
|
|
2457
|
+
milestones: PHASE_MILESTONES,
|
|
2458
|
+
acceptanceCriteria: PHASE_CRITERIA,
|
|
2459
|
+
}
|
|
2460
|
+
const { name: _phaseName, ...PHASE_FIELDS_OPTIONAL } = PHASE_FIELDS
|
|
2461
|
+
|
|
2462
|
+
registerRead('list_phases', '/v1/phases',
|
|
2463
|
+
'Lista las fases del plan del proyecto (ordenadas) con sus hitos y criterios de aceptación. Cada fase trae fechas calculadas: effectiveStartDate, effectiveEndDate, dateIsFixed (true si tiene fecha fija movida a mano) y milestones[].effectiveDate. Se calculan desde el startDate del proyecto (update_project) + offsetDays; si el proyecto no tiene startDate no hay fechas efectivas. Úsala antes de create_phase/update_phase para no duplicar y para leer los hitos/criterios completos que vas a reenviar.',
|
|
2464
|
+
{}, (args, projectId) => ({ projectId }))
|
|
2465
|
+
|
|
2466
|
+
registerCreate('create_phase', '/v1/phases',
|
|
2467
|
+
'Crea una fase del plan de un proyecto de Orquestra (ej. "Diseño", "Beta cerrada"). Sus fechas se calculan desde el startDate del proyecto + offsetDays + durationDays; para una fecha fija usa fixedStartDate con fixedStartReason. Revisa list_phases primero para no duplicar.',
|
|
2468
|
+
PHASE_FIELDS)
|
|
2469
|
+
|
|
2470
|
+
registerUpdate('update_phase', '/v1/phases',
|
|
2471
|
+
'Edita una fase del plan: nombre, orden, duración, estatus (pending | active | done), hitos y criterios de aceptación. Para mover su fecha manda fixedStartDate + fixedStartReason (obligatorio, queda registrado con la fecha previa); fixedStartDate null la devuelve al cálculo automático. milestones y acceptanceCriteria REEMPLAZAN la lista completa: para marcar uno hecho, lee list_phases y reenvía todos con sus id cambiando solo done.',
|
|
2472
|
+
PHASE_FIELDS_OPTIONAL)
|
|
2473
|
+
|
|
2474
|
+
server.registerTool(
|
|
2475
|
+
'delete_phase',
|
|
2476
|
+
{
|
|
2477
|
+
description: 'Borra una fase del plan del proyecto. Es un borrado real: úsalo solo si la fase fue un error o el usuario lo pidió; para una fase que ya terminó usa update_phase con status "done".',
|
|
2478
|
+
inputSchema: {
|
|
2479
|
+
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
2480
|
+
phaseId: z.string().describe('ID de la fase a borrar (ver list_phases)'),
|
|
2481
|
+
},
|
|
2482
|
+
},
|
|
2483
|
+
async ({ projectId, phaseId }) => {
|
|
2484
|
+
try {
|
|
2485
|
+
return textResult(await apiDelete(config, '/v1/phases', { projectId: resolveProjectId(projectId), id: phaseId }))
|
|
2486
|
+
} catch (err) {
|
|
2487
|
+
return errorResult(err)
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
)
|
|
2491
|
+
|
|
2419
2492
|
registerCreate('create_status', '/v1/statuses', 'Crea un estatus (columna del flujo de tareas) en un proyecto.', {
|
|
2420
2493
|
name: z.string().describe('Nombre del estatus'),
|
|
2421
2494
|
color: z.string().describe('Color hex, ej: #5E6AD2'),
|
|
@@ -2749,9 +2822,9 @@ export function createServer() {
|
|
|
2749
2822
|
translationOf: z.string().optional().describe('Si esta pieza es una traducción, id del content original'),
|
|
2750
2823
|
})
|
|
2751
2824
|
|
|
2752
|
-
registerUpdate('update_content', '/v1/content', 'Edita una pieza de contenido existente. Al pasar status a "approved" o "published" el server estampa quién/cuándo solo -- no lo mandes tú (mismo estampado por variante si mandás channels[]). Para patchear un solo canal de una pieza multi-canal, mandá el array `channels`
|
|
2825
|
+
registerUpdate('update_content', '/v1/content', 'Edita una pieza de contenido existente. Al pasar status a "approved" o "published" el server estampa quién/cuándo solo -- no lo mandes tú (mismo estampado por variante si mandás channels[]). Para patchear un solo canal de una pieza multi-canal, mandá el array `channels` con TODAS las variantes (la que falte se quita de la pieza); dentro de cada variante basta con channelId + lo que cambia -- el server mezcla con la variante guardada y conserva lo que no mandes (mediaFiles, externalGuid, approvedAt...). Para vaciar un campo mandalo explícito (null o []).', {
|
|
2753
2826
|
channelId: z.string().optional(),
|
|
2754
|
-
channels: z.array(CONTENT_VARIANT_SCHEMA).optional().describe('
|
|
2827
|
+
channels: z.array(CONTENT_VARIANT_SCHEMA).optional().describe('Array de variantes completo -- mandá TODAS (la que falte se quita). Cada una se mezcla con la guardada por channelId: lo que no mandes (mediaFiles, externalGuid...) se conserva.'),
|
|
2755
2828
|
title: z.string().optional(),
|
|
2756
2829
|
format: z.string().optional(),
|
|
2757
2830
|
status: z.enum(CONTENT_STATUSES).optional(),
|
|
@@ -2916,7 +2989,7 @@ export function createServer() {
|
|
|
2916
2989
|
server.registerTool(
|
|
2917
2990
|
'update_project',
|
|
2918
2991
|
{
|
|
2919
|
-
description: 'Edita el proyecto configurado (name, description, phase, projectType, scale, url). Útil para sincronizar la fase real de un proyecto que ya estaba avanzado antes de conectarlo a Orquestra -- get_project_context puede verse "vacío" (sin módulos/tareas) sin que eso signifique que el proyecto recién empieza.',
|
|
2992
|
+
description: 'Edita el proyecto configurado (name, description, phase, projectType, scale, url, startDate -- ancla del plan de fases). Útil para sincronizar la fase real de un proyecto que ya estaba avanzado antes de conectarlo a Orquestra -- get_project_context puede verse "vacío" (sin módulos/tareas) sin que eso signifique que el proyecto recién empieza.',
|
|
2920
2993
|
inputSchema: {
|
|
2921
2994
|
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
2922
2995
|
name: z.string().optional(),
|
|
@@ -2925,6 +2998,7 @@ export function createServer() {
|
|
|
2925
2998
|
projectType: z.enum(['code', 'content', 'hybrid']).optional(),
|
|
2926
2999
|
scale: z.enum(['landing', 'app', 'platform']).optional().describe('Eje ORTOGONAL a projectType, no lo reemplaza -- "landing" es una página estática sin backend/auth/db propios (ej. un portafolio o sitio de marketing): marcalo así si el proyecto es claramente eso, para que las sugerencias de setup dejen de proponer backend/auth/database. Default implícito "app" si no se manda -- sin este campo, el comportamiento es igual que antes.'),
|
|
2927
3000
|
url: z.string().optional(),
|
|
3001
|
+
startDate: z.string().optional().describe('YYYY-MM-DD, fecha ancla del plan: las fases (list_phases) calculan sus fechas efectivas a partir de ella con offsetDays. "" la quita.'),
|
|
2928
3002
|
},
|
|
2929
3003
|
},
|
|
2930
3004
|
async ({ projectId, ...body }) => {
|