orquestra-mcp 1.9.0 → 1.10.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "orquestra-mcp",
3
- "version": "1.9.0",
4
- "description": "Servidor MCP de Orquestra -- 71 tools para leer y escribir tareas, ideas, changelog, infraestructura, contenido, QA y más desde un agente de IA",
3
+ "version": "1.10.0",
4
+ "description": "Servidor MCP de Orquestra -- 72 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"
@@ -0,0 +1,50 @@
1
+ // Checklist de "fundamentos olvidados" -- prácticas chicas que casi ningún
2
+ // proyecto instrumenta por su cuenta pero suman mucho cuando faltan (idea
3
+ // de José, 21 ago 2026, a raíz de auditar Orquestra mismo y encontrar
4
+ // analytics/crash reporting ausentes en las 4 apps). Constantes estáticas,
5
+ // sin acceso a red ni a la base de datos -- mismo criterio que stages.mjs.
6
+ // get_foundations_checklist las expone tal cual; el agente conectado hace
7
+ // la detección real (grep del repo local) y usa report_foundation_gap para
8
+ // cada una que confirme ausente -- ver SERVER_INSTRUCTIONS.
9
+ export const FOUNDATIONS = [
10
+ {
11
+ key: 'analytics',
12
+ label: 'Analytics de producto',
13
+ category: 'observabilidad',
14
+ why: 'Sin eventos básicos (pantallas, acciones clave) no hay forma de saber qué usa la gente ni de tomar decisiones con datos.',
15
+ detectionHint: 'Buscar SDKs de analytics en dependencias (Firebase Analytics, Segment, Amplitude, PostHog, Mixpanel) o gtag/GA4 en el HTML -- y confirmar que de verdad se llame (logEvent/track), no solo que esté listada la dependencia.',
16
+ scales: ['app', 'platform'],
17
+ },
18
+ {
19
+ key: 'crash_reporting',
20
+ label: 'Reporte de errores y crashes',
21
+ category: 'observabilidad',
22
+ why: 'Sin esto, los crashes de usuarios reales son invisibles -- solo se enteran si alguien reporta a mano.',
23
+ detectionHint: 'Buscar Crashlytics/Sentry/Bugsnag en dependencias, y confirmar inicialización real en el entrypoint de la app (no solo la dependencia listada y sin usar).',
24
+ scales: ['app', 'platform'],
25
+ },
26
+ {
27
+ key: 'error_alerting',
28
+ label: 'Alertas de error del backend',
29
+ category: 'observabilidad',
30
+ why: 'Un log que nadie lee no es monitoreo -- sin una alarma real, un backend caído puede tardar horas o días en notarse.',
31
+ detectionHint: 'Buscar alarmas de CloudWatch con AlarmActions/SNS, integraciones de Sentry/PagerDuty, o un webhook de errores a Slack/email en la infraestructura.',
32
+ scales: ['platform'],
33
+ },
34
+ {
35
+ key: 'dependency_scanning',
36
+ label: 'Escaneo de vulnerabilidades de dependencias',
37
+ category: 'seguridad',
38
+ why: 'Dependencias con vulnerabilidades conocidas quedan sin parchear indefinidamente si nada avisa cuando aparecen.',
39
+ detectionHint: 'Buscar .github/dependabot.yml, renovate.json, o un paso de audit (npm audit / pip-audit / similar) en el CI.',
40
+ scales: ['app', 'platform'],
41
+ },
42
+ {
43
+ key: 'ci_pipeline',
44
+ label: 'CI antes de mergear',
45
+ category: 'calidad',
46
+ why: 'Sin lint/tests/build automáticos en cada PR, cada regresión depende de que alguien la note a mano antes de mergear.',
47
+ detectionHint: 'Buscar workflows de CI (.github/workflows/*.yml, GitLab CI, CircleCI) con jobs que corran en pull_request, no solo en push a main.',
48
+ scales: ['app', 'platform'],
49
+ },
50
+ ]
package/src/server.mjs CHANGED
@@ -2,6 +2,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
2
  import { z } from 'zod'
3
3
  import { loadConfig, apiGet, apiPost, apiPatch, apiDelete, ApiError } from './api.mjs'
4
4
  import { STAGES, PLAYBOOKS } from './stages.mjs'
5
+ import { FOUNDATIONS } from './foundations.mjs'
5
6
 
6
7
  function errorResult(err) {
7
8
  const text = err instanceof ApiError ? `Error (${err.status}): ${err.message}` : `Error: ${err.message}`
@@ -86,7 +87,9 @@ Si el usuario pide "sincronizar"/"revisar que todo esté al día"/"verificar el
86
87
 
87
88
  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.
88
89
 
89
- 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.`
90
+ 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.
91
+
92
+ Si estás iniciando una sesión de trabajo real en un proyecto que YA tiene código (no un "vacío" recién conectado, ver arriba) y hace rato no se corrió este chequeo, o el usuario pide explícito "revisa fundamentos"/"qué le falta a este proyecto" (no lo hagas en cada mensaje ni en exploración sin cambios): llamá a get_foundations_checklist y, con acceso real al filesystem del repo (grep de dependencias, archivos de config, workflows de CI -- seguí el detectionHint de cada ítem), confirmá cuáles de verdad faltan -- filtrá por "scales" contra el scale real del proyecto (ver project.scale en get_project_context; un ítem con scales:["platform"] no aplica a una landing). Por cada uno que confirmes ausente con evidencia concreta, llamá a report_foundation_gap con esa evidencia específica -- nunca lo reportes solo porque el checklist lo menciona, sin haber revisado el código de verdad. No repitas un chequeo ya hecho recientemente en la misma sesión.`
90
93
 
91
94
  export function createServer() {
92
95
  const config = loadConfig()
@@ -318,6 +321,41 @@ export function createServer() {
318
321
  }
319
322
  )
320
323
 
324
+ server.registerTool(
325
+ 'get_foundations_checklist',
326
+ {
327
+ description: 'Checklist de "fundamentos olvidados" -- prácticas chicas (analytics, crash reporting, alertas de error, escaneo de dependencias, CI) que casi ningún proyecto instrumenta por su cuenta pero suman mucho cuando faltan. No llama a la API -- datos estáticos. Cada ítem trae "scales" (a qué project.scale aplica -- "landing" queda afuera de la mayoría a propósito) y "detectionHint" para que TÚ (con acceso real al filesystem del repo) confirmes si de verdad falta antes de reportarlo con report_foundation_gap -- ver SERVER_INSTRUCTIONS para cuándo correr este chequeo.',
328
+ },
329
+ async () => textResult(FOUNDATIONS)
330
+ )
331
+
332
+ server.registerTool(
333
+ 'report_foundation_gap',
334
+ {
335
+ description: 'Reporta un ítem de get_foundations_checklist que confirmaste ausente con evidencia real del repo (no lo llames sin haber revisado el código de verdad). Crea una sugerencia "foundation_gap", visible en el drawer "Sugerencias de IA" de la web -- aceptarla crea una tarea real (mismo mecanismo que accept_suggestion). Si ese foundationKey ya tiene una sugerencia (pending, aceptada o descartada) no crea una duplicada -- descartar una no la hace reaparecer en el siguiente chequeo.',
336
+ inputSchema: {
337
+ projectId: z.string().optional().describe(PROJECT_ID_DESC),
338
+ foundationKey: z.string().describe('key del ítem de get_foundations_checklist, ej. "analytics"'),
339
+ title: z.string().describe('Título corto y específico al proyecto real, ej. "Falta Crashlytics en la app iOS" -- no copies el label genérico del checklist tal cual.'),
340
+ reason: z.string().describe('Evidencia concreta que encontraste (qué buscaste, qué NO encontraste) y por qué importa acá -- no una explicación genérica del ítem.'),
341
+ },
342
+ },
343
+ async ({ projectId, foundationKey, title, reason }) => {
344
+ try {
345
+ const resolvedProjectId = resolveProjectId(projectId)
346
+ const data = await apiPost(config, '/v1/suggestions', {
347
+ projectId: resolvedProjectId,
348
+ kind: 'foundation_gap',
349
+ foundationKey, title, reason,
350
+ idempotencyKey: `foundation:${resolvedProjectId}:${foundationKey}`,
351
+ })
352
+ return textResult(data)
353
+ } catch (err) {
354
+ return errorResult(err)
355
+ }
356
+ }
357
+ )
358
+
321
359
  // ═══ ESCRITURA (existentes) ═════════════════════════════════════════════════
322
360
 
323
361
  registerCreate(
@@ -700,7 +738,7 @@ export function createServer() {
700
738
  registerRead(
701
739
  'list_suggestions',
702
740
  '/v1/suggestions',
703
- 'Lista las sugerencias de IA de un proyecto (reescrituras de changelog, tareas que avanzaron/cerraron por evidencia, hallazgos de QA, gaps de setup/contenido/publicidad, fusiones de tareas sugeridas, ángulos de contenido). Por default solo trae las "pending" -- usá status="all" para ver también las ya aceptadas/descartadas, o un status puntual.',
741
+ 'Lista las sugerencias de IA de un proyecto (reescrituras de changelog, tareas que avanzaron/cerraron por evidencia, hallazgos de QA, gaps de setup/contenido/publicidad/fundamentos, fusiones de tareas sugeridas, ángulos de contenido). Por default solo trae las "pending" -- usá status="all" para ver también las ya aceptadas/descartadas, o un status puntual.',
704
742
  { status: z.string().optional().describe('Default "pending". "all" trae todas, o un status puntual ("accepted"/"dismissed").') },
705
743
  (args, projectId) => ({ projectId, status: args.status })
706
744
  )
@@ -712,8 +750,8 @@ export function createServer() {
712
750
  inputSchema: {
713
751
  projectId: z.string().optional().describe(PROJECT_ID_DESC),
714
752
  suggestionId: z.string().describe('Ver list_suggestions'),
715
- moduleId: z.string().optional().describe('Solo para kind="qa_finding" -- ese branch crea una tarea nueva, que exige moduleId real (mismas reglas que create_task). Ignorado en el resto de los kinds.'),
716
- dueDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Formato esperado: YYYY-MM-DD').optional().describe('Solo para kind="qa_finding" -- mismo motivo que moduleId arriba.'),
753
+ moduleId: z.string().optional().describe('Solo para kind="qa_finding"|"foundation_gap" -- esos branches crean una tarea nueva, que exige moduleId real (mismas reglas que create_task). Ignorado en el resto de los kinds.'),
754
+ dueDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Formato esperado: YYYY-MM-DD').optional().describe('Solo para kind="qa_finding"|"foundation_gap" -- mismo motivo que moduleId arriba.'),
717
755
  overrides: z.record(z.any()).optional().describe('Solo para kind="content_angle" -- el form final de la pieza de contenido a crear (puede diferir de lo que propuso la IA). Mismos campos que create_content.'),
718
756
  },
719
757
  },