orquestra-mcp 1.61.0 → 1.72.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # orquestra-mcp
2
2
 
3
- Servidor MCP (Model Context Protocol) para Orquestra. Expone **158 tools**
3
+ Servidor MCP (Model Context Protocol) para Orquestra. Expone **161 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/
@@ -141,6 +141,17 @@ claude mcp add orquestra \
141
141
  -- npx -y orquestra-mcp@latest
142
142
  ```
143
143
 
144
+ ## Novedades 1.66.0 (ORQ-532..535)
145
+
146
+ - `close_task({id, comment, completeChecklist?, waiveVerifications?})`: cierra una tarea desde cualquier estatus
147
+ recorriendo el flujo (camino más corto por `nextIds` hasta el final, pasando por "En revisión"); un solo
148
+ `comment` (en el último paso). Respeta las guardas de checklist/verificaciones.
149
+ - `get_tool_guide({topic})`: reglas completas de `update_task`, `create_task`, `update_tasks`, `close_task` y
150
+ `related_impact`; las descripciones de esas tools son cortas a propósito (test de tope en `test/mejorasMcp.test.mjs`).
151
+ - `update_tasks` acepta `compact:true` (recomendado): cada resultado trae solo `{ index, id, ok, status, shortCode, statusId, relatedImpact?, unresolvedLinks?, error? }`.
152
+ - `create_task` acepta `checklist: [{text, done?, assignedTo?}]` y responde `checklistCount` (y `checklistFailed` si algún ítem falló).
153
+ - `search` documenta `statusId`/`tags` en los resultados (requiere la API con ORQ-536).
154
+
144
155
  ## Tools
145
156
 
146
157
  Lectura de contexto:
@@ -239,7 +250,7 @@ create/update sigue siendo criterio del agente y del humano.
239
250
 
240
251
  ## Estado
241
252
 
242
- 158 tools (ver arriba): lectura (contexto, dashboard, workspace, infra, paridad, standup,
253
+ 161 tools (ver arriba): lectura (contexto, dashboard, workspace, infra, paridad, standup,
243
254
  sync report, setup guide, playbooks de etapa y de plataforma, catálogo de
244
255
  skills, search) + `list_*` de cada colección + creación/edición de tareas
245
256
  (con checklist, verificaciones por plataforma, tiempo, adjuntos, merge), ideas (con comentarios), notas,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "orquestra-mcp",
3
- "version": "1.61.0",
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",
3
+ "version": "1.72.0",
4
+ "description": "Servidor MCP de Orquestra -- 161 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/api.mjs CHANGED
@@ -80,6 +80,10 @@ export async function apiPost(config, path, { projectId, ...body }, query = {})
80
80
  return request(buildUrl(config, path, { projectId, ...query }), { method: 'POST', headers: jsonHeaders(config), body: JSON.stringify(body) })
81
81
  }
82
82
 
83
+ export async function apiPut(config, path, { projectId, ...body }, query = {}) {
84
+ return request(buildUrl(config, path, { projectId, ...query }), { method: 'PUT', headers: jsonHeaders(config), body: JSON.stringify(body) })
85
+ }
86
+
83
87
  export async function apiPatch(config, path, { projectId, id, ...body }, query = {}) {
84
88
  return request(buildUrl(config, `${path}/${id}`, { projectId, ...query }), { method: 'PATCH', headers: jsonHeaders(config), body: JSON.stringify(body) })
85
89
  }
@@ -101,8 +105,8 @@ export async function apiDelete(config, path, { projectId, id }, query = {}) {
101
105
 
102
106
  // DELETE sobre un recurso singleton SIN id en la ruta (ej. /v1/client-portal,
103
107
  // ORQ-332) -- mismo criterio que apiPatchPath.
104
- export async function apiDeletePath(config, path, { projectId }) {
105
- return request(buildUrl(config, path, { projectId }), { method: 'DELETE', headers: auth(config) })
108
+ export async function apiDeletePath(config, path, { projectId, ...query }) {
109
+ return request(buildUrl(config, path, { projectId, ...query }), { method: 'DELETE', headers: auth(config) })
106
110
  }
107
111
 
108
112
  // PUT crudo a una presigned URL de S3 (createAttachmentUploadUrl en
@@ -0,0 +1,65 @@
1
+ // ORQ-532: lógica pura de close_task. La API no recorre estatus sola (cada estatus solo permite pasar a su
2
+ // `nextIds`), así que cerrar una tarea que está en Backlog/Bloqueado/etc. exige varios PATCH. Aquí se calcula
3
+ // el camino más corto hacia el estatus de cierre normal (final, no cancelado ni archivo), sin tocar la red.
4
+
5
+ const isClosed = s => !!(s && (s.isFinal || s.isCancel || s.isArchive))
6
+ const isReview = s => /revisi[oó]n|review/i.test(s?.name || '')
7
+
8
+ /** Normaliza la respuesta de GET /v1/statuses (arreglo directo o { items }). */
9
+ export function normalizeStatuses(data) {
10
+ const list = Array.isArray(data) ? data : Array.isArray(data?.items) ? data.items : Array.isArray(data?.statuses) ? data.statuses : []
11
+ return list.filter(s => s && s.id)
12
+ }
13
+
14
+ /** Estatus de cierre normal: final y ni cancelado ni archivo. */
15
+ export function closeTargets(statuses) {
16
+ return statuses.filter(s => s.isFinal && !s.isCancel && !s.isArchive)
17
+ }
18
+
19
+ /** BFS desde `fromId` por nextIds; no transita por estatus cerrados (final/cancelado/archivo) salvo como destino.
20
+ * Devuelve el camino más corto como arreglo de estatus (sin incluir el origen) hacia `targetId`, o null. */
21
+ function shortestPath(byId, fromId, targetId) {
22
+ const prev = new Map([[fromId, null]])
23
+ const queue = [fromId]
24
+ while (queue.length) {
25
+ const cur = queue.shift()
26
+ if (cur === targetId) break
27
+ for (const nid of byId.get(cur)?.nextIds || []) {
28
+ const next = byId.get(nid)
29
+ if (!next || prev.has(nid)) continue
30
+ if (isClosed(next) && nid !== targetId) continue
31
+ prev.set(nid, cur)
32
+ queue.push(nid)
33
+ }
34
+ }
35
+ if (!prev.has(targetId)) return null
36
+ const out = []
37
+ for (let id = targetId; id !== fromId; id = prev.get(id)) out.unshift(byId.get(id))
38
+ return out
39
+ }
40
+
41
+ /**
42
+ * @param {object[]} statuses estatuses del proyecto ({ id, name, order, nextIds, isFinal, isCancel, isArchive })
43
+ * @param {string} fromId estatus actual de la tarea
44
+ * @returns {{ alreadyClosed: true, current: object } | { path: object[], target: object } | { error: string }}
45
+ */
46
+ export function findClosePath(statuses, fromId) {
47
+ const byId = new Map(statuses.map(s => [s.id, s]))
48
+ const current = byId.get(fromId)
49
+ if (!current) return { error: `El estatus actual de la tarea (${fromId}) no existe en el proyecto` }
50
+ if (isClosed(current)) return { alreadyClosed: true, current }
51
+ const targets = closeTargets(statuses)
52
+ if (!targets.length) return { error: 'El proyecto no tiene un estatus final (isFinal) al que cerrar' }
53
+ const candidates = targets
54
+ .map(t => ({ target: t, path: shortestPath(byId, fromId, t.id) }))
55
+ .filter(c => c.path)
56
+ if (!candidates.length) {
57
+ return { error: `No hay camino por nextIds desde "${current.name}" hasta un estatus final (${targets.map(t => `"${t.name}"`).join(', ')}). Revisa el flujo con list_statuses.` }
58
+ }
59
+ // Más corto; a igual largo, el que pasa por "En revisión" (flujo normal de cierre); luego el de menor order.
60
+ candidates.sort((a, b) =>
61
+ a.path.length - b.path.length
62
+ || Number(b.path.some(isReview)) - Number(a.path.some(isReview))
63
+ || (a.target.order ?? 0) - (b.target.order ?? 0))
64
+ return { path: candidates[0].path, target: candidates[0].target }
65
+ }