orquestra-mcp 1.46.0 → 1.47.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 +55 -24
- package/bin/orquestra-mcp.mjs +3 -3
- package/package.json +1 -1
- package/src/api.mjs +8 -5
- package/src/configFile.mjs +20 -6
- package/src/foundations.mjs +18 -2
- package/src/platformPlaybooks.mjs +39 -50
- package/src/server.mjs +104 -16
- package/src/setup.mjs +9 -3
- package/src/stages.mjs +2 -12
package/README.md
CHANGED
|
@@ -12,25 +12,34 @@ configurar/sincronizar un proyecto completo sin crear duplicados.
|
|
|
12
12
|
Ver `CLAUDE.md` para contexto de desarrollo. Ver la memoria del proyecto
|
|
13
13
|
`orquestra-mcp-api-plan` para las 8 fases del plan MCP/API completo.
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Empezar (macOS, Linux y Windows)
|
|
16
|
+
|
|
17
|
+
Necesitas una cuenta en [app.orquestra.me](https://app.orquestra.me) y Node 18+.
|
|
18
|
+
|
|
19
|
+
**1. Conecta tu cuenta y tu proyecto** — desde la carpeta de tu proyecto:
|
|
16
20
|
|
|
17
21
|
```bash
|
|
18
|
-
|
|
19
|
-
node bin/orquestra-mcp.mjs setup
|
|
22
|
+
npx orquestra-mcp@latest setup
|
|
20
23
|
```
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
Abre el navegador para confirmar tu sesión (Google o email/password) y luego:
|
|
26
|
+
|
|
27
|
+
- Si **no tienes workspace**, te pide su nombre y después el de tu primer proyecto.
|
|
28
|
+
- Si **perteneces a varios workspaces**, te deja elegir en cuál trabajar (elegir aquí
|
|
29
|
+
no cambia el que tienes abierto en la web).
|
|
30
|
+
- Eliges un proyecto de ese workspace o creas uno nuevo.
|
|
31
|
+
|
|
32
|
+
La conexión se guarda **por carpeta** en `~/.orquestra/mcp-config.json`: puedes tener
|
|
33
|
+
distintos proyectos (o cuentas) en distintas carpetas. Para conectar otra carpeta o
|
|
34
|
+
cambiar de proyecto, vuelve a correr el setup ahí.
|
|
24
35
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- Si **ya tienes** uno o varios: te deja elegir cuál usar por default
|
|
29
|
-
desde la misma terminal.
|
|
36
|
+
**2. Regístralo en tu agente** (ver abajo: Claude Code, Claude Desktop, Cursor, Codex,
|
|
37
|
+
Gemini CLI). Usa `orquestra-mcp@latest` para que `npx` no se quede con una versión vieja
|
|
38
|
+
de su caché.
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
(
|
|
40
|
+
**Ver la versión / actualizar:** `npm view orquestra-mcp version` muestra la última
|
|
41
|
+
publicada. Con `@latest` cada arranque la usa; el servidor que ya está corriendo conserva
|
|
42
|
+
la suya hasta que reconectas (en Claude Code: `/mcp` → reconectar).
|
|
34
43
|
|
|
35
44
|
Variable opcional `ORQUESTRA_WEB_URL` para apuntar el login a otro lugar
|
|
36
45
|
(ej. `http://localhost:5173` mientras desarrollas `orquestra-web` local en
|
|
@@ -71,35 +80,57 @@ Variables de entorno (todas opcionales si ya corriste `setup` — se leen de
|
|
|
71
80
|
|
|
72
81
|
### Claude Code
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
macOS / Linux:
|
|
75
84
|
|
|
76
85
|
```bash
|
|
77
|
-
claude mcp add orquestra --
|
|
86
|
+
claude mcp add orquestra -- npx -y orquestra-mcp@latest
|
|
78
87
|
```
|
|
79
88
|
|
|
80
|
-
|
|
89
|
+
Windows (PowerShell) — `npx` necesita `cmd /c` delante:
|
|
81
90
|
|
|
82
|
-
```
|
|
83
|
-
claude mcp add orquestra
|
|
84
|
-
-e ORQUESTRA_API_TOKEN=orq_... \
|
|
85
|
-
-e ORQUESTRA_WORKSPACE_ID=... \
|
|
86
|
-
-- node /ruta/absoluta/a/orquestra-mcp/bin/orquestra-mcp.mjs
|
|
91
|
+
```powershell
|
|
92
|
+
claude mcp add orquestra -- cmd /c npx -y orquestra-mcp@latest
|
|
87
93
|
```
|
|
88
94
|
|
|
95
|
+
Agrega `--scope user` después de `add` para tenerlo en todos tus proyectos. Si PowerShell
|
|
96
|
+
dice que `claude` no se reconoce, falta instalar Claude Code:
|
|
97
|
+
`npm install -g @anthropic-ai/claude-code` (y vuelve a abrir la terminal). Si ya lo habías
|
|
98
|
+
agregado sin `@latest`, primero `claude mcp remove orquestra`.
|
|
99
|
+
|
|
89
100
|
### Claude Desktop (`claude_desktop_config.json`)
|
|
90
101
|
|
|
102
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
103
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
104
|
+
|
|
91
105
|
```json
|
|
92
106
|
{
|
|
93
107
|
"mcpServers": {
|
|
94
108
|
"orquestra": {
|
|
95
|
-
"command": "
|
|
96
|
-
"args": ["
|
|
109
|
+
"command": "npx",
|
|
110
|
+
"args": ["-y", "orquestra-mcp@latest"]
|
|
97
111
|
}
|
|
98
112
|
}
|
|
99
113
|
}
|
|
100
114
|
```
|
|
101
115
|
|
|
102
|
-
|
|
116
|
+
Cierra la app por completo (en Windows también desde el ícono junto al reloj) y vuelve a
|
|
117
|
+
abrirla.
|
|
118
|
+
|
|
119
|
+
### Cursor, Codex CLI, Gemini CLI
|
|
120
|
+
|
|
121
|
+
Mismo comando (`npx -y orquestra-mcp@latest`), cambia el archivo — ver
|
|
122
|
+
[orquestra.me/mcp#instalacion](https://www.orquestra.me/mcp#instalacion). En Windows, si
|
|
123
|
+
el agente no encuentra `npx`, usa `"command": "cmd"` con
|
|
124
|
+
`"args": ["/c", "npx", "-y", "orquestra-mcp@latest"]`.
|
|
125
|
+
|
|
126
|
+
### Sin `setup` (variables de entorno)
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
claude mcp add orquestra \
|
|
130
|
+
-e ORQUESTRA_API_TOKEN=orq_... \
|
|
131
|
+
-e ORQUESTRA_WORKSPACE_ID=... \
|
|
132
|
+
-- npx -y orquestra-mcp@latest
|
|
133
|
+
```
|
|
103
134
|
|
|
104
135
|
## Tools
|
|
105
136
|
|
package/bin/orquestra-mcp.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
3
|
-
import { createServer } from '../src/server.mjs'
|
|
3
|
+
import { createServer, createSetupNeededServer } from '../src/server.mjs'
|
|
4
4
|
import { runSetup } from '../src/setup.mjs'
|
|
5
5
|
import { runVaultSync } from '../src/vaultSync.mjs'
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ const HELP_TEXT = `orquestra-mcp -- Servidor MCP de Orquestra (132 tools)
|
|
|
9
9
|
Uso:
|
|
10
10
|
npx orquestra-mcp Inicia el servidor MCP por stdio -- lo usa tu cliente
|
|
11
11
|
MCP (Claude Code, Claude Desktop, etc.), no se corre a mano
|
|
12
|
-
npx orquestra-mcp setup Login por navegador +
|
|
12
|
+
npx orquestra-mcp setup Login por navegador + conecta esta carpeta a un proyecto
|
|
13
13
|
npx orquestra-mcp vault sync <carpeta> [--apply]
|
|
14
14
|
Sincroniza la base de conocimiento del workspace con una
|
|
15
15
|
carpeta de tu vault de Obsidian (la 1ra vez solo muestra el plan)
|
|
@@ -50,7 +50,7 @@ async function main() {
|
|
|
50
50
|
server = createServer()
|
|
51
51
|
} catch (err) {
|
|
52
52
|
console.error(`[orquestra-mcp] ${err.message}`)
|
|
53
|
-
|
|
53
|
+
server = createSetupNeededServer(err.message)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const transport = new StdioServerTransport()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orquestra-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.47.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/api.mjs
CHANGED
|
@@ -27,12 +27,15 @@ export function loadConfig() {
|
|
|
27
27
|
if (!apiToken) missing.push('ORQUESTRA_API_TOKEN')
|
|
28
28
|
if (!workspaceId) missing.push('ORQUESTRA_WORKSPACE_ID')
|
|
29
29
|
if (missing.length) {
|
|
30
|
+
const folders = Object.keys(fileConfig?.projects || {})
|
|
30
31
|
throw new Error(
|
|
31
|
-
`
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
`
|
|
32
|
+
`Esta carpeta (${process.cwd()}) no está conectada a Orquestra. ` +
|
|
33
|
+
(folders.length
|
|
34
|
+
? `Carpetas conectadas en ${CONFIG_PATH}: ${folders.join(' | ')}. `
|
|
35
|
+
: `No hay ninguna carpeta conectada (${CONFIG_PATH} no existe o está vacío). `) +
|
|
36
|
+
`Solución: abre una terminal en esta carpeta y corre "npx orquestra-mcp@latest setup" ` +
|
|
37
|
+
`(o ábrela desde una carpeta que ya esté conectada), y después reconecta el MCP. ` +
|
|
38
|
+
`Alternativa sin setup: variables de entorno ${missing.join(' y ')}.`
|
|
36
39
|
)
|
|
37
40
|
}
|
|
38
41
|
|
package/src/configFile.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
2
2
|
import { homedir } from 'os'
|
|
3
|
-
import { join, dirname,
|
|
3
|
+
import { join, dirname, win32, posix } from 'path'
|
|
4
4
|
|
|
5
5
|
export const CONFIG_PATH = join(homedir(), '.orquestra', 'mcp-config.json')
|
|
6
6
|
|
|
@@ -63,13 +63,27 @@ export function upsertProjectMapping({ apiBase, apiToken, workspaceId, folderPat
|
|
|
63
63
|
// gana, así que un repo individual con su propio mapeo (y su propia cuenta)
|
|
64
64
|
// puede sobreescribir el de la carpeta contenedora que lo engloba. Devuelve
|
|
65
65
|
// el objeto {apiBase, apiToken, workspaceId, projectId} de esa carpeta.
|
|
66
|
-
|
|
66
|
+
// En Windows las rutas no distinguen mayúsculas, y VS Code arranca el
|
|
67
|
+
// servidor con la unidad en minúscula ("c:\Users\...") aunque setup haya
|
|
68
|
+
// guardado "C:\Users\..." (o con "/" en vez de "\"). Comparar exacto hacía
|
|
69
|
+
// que no encontrara la carpeta y el servidor se cerrara al arrancar
|
|
70
|
+
// ("Connection closed" en Claude Code, 21 oct 2026). `platform` es para tests.
|
|
71
|
+
export function folderKey(folderPath, platform = process.platform) {
|
|
72
|
+
const isWin = platform === 'win32'
|
|
73
|
+
const resolved = (isWin ? win32 : posix).resolve(folderPath)
|
|
74
|
+
const trimmed = resolved.length > 1 && !/^[a-z]:\\$/i.test(resolved) ? resolved.replace(/[\\/]+$/, '') : resolved
|
|
75
|
+
return isWin ? trimmed.toLowerCase() : trimmed
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function resolveProjectForCwd(projects, cwd, platform = process.platform) {
|
|
67
79
|
if (!projects) return undefined
|
|
80
|
+
const sep = platform === 'win32' ? '\\' : '/'
|
|
81
|
+
const target = folderKey(cwd, platform)
|
|
68
82
|
let best = null
|
|
69
83
|
for (const folderPath of Object.keys(projects)) {
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
if (!best ||
|
|
84
|
+
const key = folderKey(folderPath, platform)
|
|
85
|
+
if (target !== key && !target.startsWith(key.endsWith(sep) ? key : key + sep)) continue
|
|
86
|
+
if (!best || key.length > best.key.length) best = { key, folderPath }
|
|
73
87
|
}
|
|
74
|
-
return best ? projects[best] : undefined
|
|
88
|
+
return best ? projects[best.folderPath] : undefined
|
|
75
89
|
}
|
package/src/foundations.mjs
CHANGED
|
@@ -4,9 +4,20 @@
|
|
|
4
4
|
// analytics/crash reporting ausentes en las 4 apps). Constantes estáticas,
|
|
5
5
|
// sin acceso a red ni a la base de datos -- mismo criterio que stages.mjs.
|
|
6
6
|
// get_foundations_checklist las expone tal cual; el agente conectado hace
|
|
7
|
-
// la detección real (grep del repo local) y
|
|
8
|
-
//
|
|
7
|
+
// la detección real (grep del repo local) y reporta con report_foundations
|
|
8
|
+
// (ORQ-400: queda en el checklist de fundamentos del proyecto). Las keys son
|
|
9
|
+
// las mismas que CODE_FOUNDATIONS de orquestra-infra (lib/foundations.mjs);
|
|
10
|
+
// `detectedByServer` = el servidor lo ve solo en el árbol de los repos conectados.
|
|
9
11
|
export const FOUNDATIONS = [
|
|
12
|
+
{
|
|
13
|
+
key: 'readme',
|
|
14
|
+
label: 'README para levantar el proyecto',
|
|
15
|
+
category: 'calidad',
|
|
16
|
+
why: 'Sin instrucciones de setup (dependencias, variables de entorno, comandos), cada persona nueva -- o tu yo del futuro -- pierde horas reconstruyéndolo.',
|
|
17
|
+
detectionHint: 'README en la raíz del repo que de verdad explique cómo levantarlo, no solo el título.',
|
|
18
|
+
scales: ['landing', 'app', 'platform'],
|
|
19
|
+
detectedByServer: true,
|
|
20
|
+
},
|
|
10
21
|
{
|
|
11
22
|
key: 'analytics',
|
|
12
23
|
label: 'Analytics de producto',
|
|
@@ -14,6 +25,7 @@ export const FOUNDATIONS = [
|
|
|
14
25
|
why: 'Sin eventos básicos (pantallas, acciones clave) no hay forma de saber qué usa la gente ni de tomar decisiones con datos.',
|
|
15
26
|
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
27
|
scales: ['app', 'platform'],
|
|
28
|
+
detectedByServer: false,
|
|
17
29
|
},
|
|
18
30
|
{
|
|
19
31
|
key: 'crash_reporting',
|
|
@@ -22,6 +34,7 @@ export const FOUNDATIONS = [
|
|
|
22
34
|
why: 'Sin esto, los crashes de usuarios reales son invisibles -- solo se enteran si alguien reporta a mano.',
|
|
23
35
|
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
36
|
scales: ['app', 'platform'],
|
|
37
|
+
detectedByServer: false,
|
|
25
38
|
},
|
|
26
39
|
{
|
|
27
40
|
key: 'error_alerting',
|
|
@@ -30,6 +43,7 @@ export const FOUNDATIONS = [
|
|
|
30
43
|
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
44
|
detectionHint: 'Buscar alarmas de CloudWatch con AlarmActions/SNS, integraciones de Sentry/PagerDuty, o un webhook de errores a Slack/email en la infraestructura.',
|
|
32
45
|
scales: ['platform'],
|
|
46
|
+
detectedByServer: false,
|
|
33
47
|
},
|
|
34
48
|
{
|
|
35
49
|
key: 'dependency_scanning',
|
|
@@ -38,6 +52,7 @@ export const FOUNDATIONS = [
|
|
|
38
52
|
why: 'Dependencias con vulnerabilidades conocidas quedan sin parchear indefinidamente si nada avisa cuando aparecen.',
|
|
39
53
|
detectionHint: 'Buscar .github/dependabot.yml, renovate.json, o un paso de audit (npm audit / pip-audit / similar) en el CI.',
|
|
40
54
|
scales: ['app', 'platform'],
|
|
55
|
+
detectedByServer: true,
|
|
41
56
|
},
|
|
42
57
|
{
|
|
43
58
|
key: 'ci_pipeline',
|
|
@@ -46,5 +61,6 @@ export const FOUNDATIONS = [
|
|
|
46
61
|
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
62
|
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
63
|
scales: ['app', 'platform'],
|
|
64
|
+
detectedByServer: true,
|
|
49
65
|
},
|
|
50
66
|
]
|
|
@@ -16,7 +16,7 @@ export const PLATFORM_PLAYBOOKS = {
|
|
|
16
16
|
label: 'iOS — App Store',
|
|
17
17
|
color: '#007AFF',
|
|
18
18
|
moduleName: 'Publicación en App Store',
|
|
19
|
-
moduleDescription: '
|
|
19
|
+
moduleDescription: 'Lo que hay que decidir y preparar para enviar la app a revisión en App Store Connect, sin sorpresas de último momento. Que la ficha esté publicada y que las URLs de privacidad y soporte respondan lo revisa solo el checklist de Buenas prácticas.',
|
|
20
20
|
moduleTags: ['playbook', 'publicacion'],
|
|
21
21
|
|
|
22
22
|
// Revisado contra App Store Connect real el 19 oct 2026 (ORQ-383). `section`
|
|
@@ -190,17 +190,9 @@ export const PLATFORM_PLAYBOOKS = {
|
|
|
190
190
|
|
|
191
191
|
// `optional: true` = no viene seleccionada por default al aplicar (pasos
|
|
192
192
|
// de crecimiento que solo tienen sentido con la app ya aprobada).
|
|
193
|
+
// Sin tareas de "verificar URL de privacidad/soporte" (ORQ-398): las
|
|
194
|
+
// prueba solo el checklist de Buenas prácticas y, si falla, ahí se crea la tarea.
|
|
193
195
|
tasks: [
|
|
194
|
-
{
|
|
195
|
-
title: 'Verificar/crear política de privacidad pública (URL)',
|
|
196
|
-
description: 'App Store Connect pide una URL pública de política de privacidad antes de poder enviar a revisión. Si el proyecto no tiene una, hay que publicarla (puede vivir en la landing).',
|
|
197
|
-
type: 'business', priority: 'high', tags: ['publicacion', 'ios'], offsetDays: 2, checkKey: 'privacy',
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
title: 'Verificar/crear página o contacto de soporte',
|
|
201
|
-
description: 'Apple pide una URL de soporte visible para el usuario (puede ser un mailto, un formulario o una página de FAQ). Probarla en el navegador: tiene que responder 200.',
|
|
202
|
-
type: 'business', priority: 'high', tags: ['publicacion', 'ios'], offsetDays: 2, checkKey: 'support',
|
|
203
|
-
},
|
|
204
196
|
{
|
|
205
197
|
title: 'Confirmar que la app permite eliminar la cuenta desde adentro',
|
|
206
198
|
description: 'Guideline 5.1.1(v), obligatorio -- "escríbenos por correo" NO cumple el requisito. Tiene que haber un botón real de "Eliminar cuenta" (con confirmación) en Perfil/Ajustes que borre la cuenta y los datos sin salir de la app. Si no existe, constrúyelo ANTES de mandar a revisión, no después de un rechazo.',
|
|
@@ -288,7 +280,7 @@ export const PLATFORM_PLAYBOOKS = {
|
|
|
288
280
|
label: 'Android — Google Play',
|
|
289
281
|
color: '#3DDC84',
|
|
290
282
|
moduleName: 'Publicación en Google Play',
|
|
291
|
-
moduleDescription: '
|
|
283
|
+
moduleDescription: 'Lo que hay que decidir y preparar para publicar la app en Google Play Console, sin sorpresas de último momento. Que la ficha esté publicada y que la URL de privacidad responda lo revisa solo el checklist de Buenas prácticas.',
|
|
292
284
|
moduleTags: ['playbook', 'publicacion'],
|
|
293
285
|
|
|
294
286
|
referenceGuide: [
|
|
@@ -369,22 +361,9 @@ export const PLATFORM_PLAYBOOKS = {
|
|
|
369
361
|
},
|
|
370
362
|
],
|
|
371
363
|
|
|
364
|
+
// Sin tareas de "verificar URL de privacidad / email de soporte / que la
|
|
365
|
+
// URL responda 200" (ORQ-398): lo prueba solo el checklist de Buenas prácticas.
|
|
372
366
|
tasks: [
|
|
373
|
-
{
|
|
374
|
-
title: 'Verificar/crear política de privacidad pública (URL)',
|
|
375
|
-
description: 'Play Console (Contenido de la app) pide una URL pública de política de privacidad antes de poder publicar. Si el proyecto no tiene una, hay que publicarla (puede vivir en la landing).',
|
|
376
|
-
type: 'business', priority: 'high', tags: ['publicacion', 'android'], offsetDays: 2, checkKey: 'privacy',
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
title: 'Verificar/crear página o contacto de soporte',
|
|
380
|
-
description: 'Play Console pide un email de contacto de soporte visible en la ficha de Play Store.',
|
|
381
|
-
type: 'business', priority: 'high', tags: ['publicacion', 'android'], offsetDays: 2, checkKey: 'support',
|
|
382
|
-
},
|
|
383
|
-
{
|
|
384
|
-
title: 'Probar en el navegador que la URL de privacidad/soporte responde 200 (no solo que "ya se desplegó")',
|
|
385
|
-
description: 'Gotcha real: Play Console valida que la URL responda, no solo el formato. Un deploy exitoso no garantiza que rutas como /privacidad carguen -- si el hosting es una SPA (React Router y similares) sin la regla de rewrite configurada (servir index.html para cualquier ruta), da 404 real aunque el build haya salido bien. Probar la URL directo en el navegador, en una pestaña nueva, ANTES de pegarla en el formulario.',
|
|
386
|
-
type: 'tech', priority: 'high', tags: ['publicacion', 'android'], offsetDays: 2,
|
|
387
|
-
},
|
|
388
367
|
{
|
|
389
368
|
title: 'Confirmar pricing y disponibilidad por país',
|
|
390
369
|
description: 'Definir si la app es gratis o de pago y en qué países se distribuye -- "Disponible en todos los países" es el default razonable salvo restricción regulatoria específica.',
|
|
@@ -431,10 +410,15 @@ export const PLATFORM_PLAYBOOKS = {
|
|
|
431
410
|
hasForm: false,
|
|
432
411
|
consoleName: 'SEO y analítica',
|
|
433
412
|
moduleName: 'SEO y analítica web',
|
|
434
|
-
moduleDescription: '
|
|
413
|
+
moduleDescription: 'Lo que hay que hacer para que la web/landing nazca medible e indexable: Search Console, analítica y rendimiento. Lo técnico (redirecciones, sitemap, contenido, canonical...) lo revisa solo el checklist de Buenas prácticas.',
|
|
435
414
|
moduleTags: ['playbook', 'seo', 'analitica'],
|
|
436
415
|
|
|
437
416
|
referenceGuide: [
|
|
417
|
+
{
|
|
418
|
+
key: 'practices_checklist',
|
|
419
|
+
title: 'Lo técnico lo revisa Orquestra solo',
|
|
420
|
+
body: 'Redirecciones 301 (http→https y con/sin www), robots.txt, sitemap, noindex, contenido en el HTML, título y descripción, h1, canonical, og:image y JSON-LD se revisan solos cada lunes en "Buenas prácticas" (arriba en esta página), y te avisa si algo que estaba bien se rompe. No hace falta crear tareas para eso: si un punto falla, ahí mismo puedes crear la tarea.',
|
|
421
|
+
},
|
|
438
422
|
{
|
|
439
423
|
key: 'search_console',
|
|
440
424
|
title: 'Google Search Console',
|
|
@@ -485,6 +469,26 @@ export const PLATFORM_PLAYBOOKS = {
|
|
|
485
469
|
title: 'HTTPS, dominio canónico y redirecciones',
|
|
486
470
|
body: 'Elige una sola versión (con o sin www) y redirige 301 las demás, incluido http → https. Evita cadenas de redirecciones y páginas duplicadas bajo dos URLs.',
|
|
487
471
|
},
|
|
472
|
+
{
|
|
473
|
+
key: 'html_content',
|
|
474
|
+
title: 'El contenido tiene que venir en el HTML',
|
|
475
|
+
body: 'Una SPA (React/Vite) que sirve solo <div id="root"></div> deja a los buscadores casi sin texto: Google lo renderiza tarde y otros buscadores no lo ejecutan. Usa prerender o SSR para que cada ruta pública llegue con su contenido real.',
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
key: 'noindex_app',
|
|
479
|
+
title: 'La app y el login no deben competir con la web',
|
|
480
|
+
body: 'Si la app vive en un subdominio (app.tu-dominio.com), ponle <meta name="robots" content="noindex">. Si no, Google puede mostrar la pantalla de login en vez de tu web pública cuando alguien busca tu marca.',
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
key: 'own_links',
|
|
484
|
+
title: 'Tus enlaces, siempre a la versión canónica',
|
|
485
|
+
body: 'Bio de redes, fichas de App Store/Google Play, npm, firmas de correo y enlaces dentro de la app: todos con la misma URL (con o sin www, la que elegiste). Son las primeras señales que Google usa para entender que ese dominio eres tú.',
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
key: 'generic_name',
|
|
489
|
+
title: 'Si tu marca es una palabra común',
|
|
490
|
+
body: 'Con un nombre genérico (una palabra del diccionario) primero vas a aparecer por "marca + producto" o por tu dominio, no por la palabra sola: compites con diccionarios y sitios con años de antigüedad. Lo que acelera: enlaces de sitios reconocidos (tiendas, npm, directorios, artículos) y contenido propio.',
|
|
491
|
+
},
|
|
488
492
|
{
|
|
489
493
|
key: 'privacy_legal',
|
|
490
494
|
title: 'Privacidad y consentimiento',
|
|
@@ -495,7 +499,7 @@ export const PLATFORM_PLAYBOOKS = {
|
|
|
495
499
|
tasks: [
|
|
496
500
|
{
|
|
497
501
|
title: 'Agregar el sitio a Google Search Console y enviar el sitemap',
|
|
498
|
-
description: 'Crear la propiedad (tipo Dominio, verificación por DNS TXT), enviar sitemap.xml y confirmar que no marque errores de indexación. Sin esto no hay forma de saber si Google ve el sitio.',
|
|
502
|
+
description: 'Crear la propiedad (tipo Dominio, verificación por DNS TXT), enviar sitemap.xml y confirmar que no marque errores de indexación. Sin esto no hay forma de saber si Google ve el sitio. Al terminar, márcalo como hecho en Buenas prácticas.',
|
|
499
503
|
type: 'tech', priority: 'high', tags: ['seo', 'web', 'search-console'], offsetDays: 1,
|
|
500
504
|
},
|
|
501
505
|
{
|
|
@@ -508,35 +512,20 @@ export const PLATFORM_PLAYBOOKS = {
|
|
|
508
512
|
description: 'Solo si habrá anuncios en Facebook/Instagram: crear el Pixel en Events Manager, instalarlo en el sitio y confirmar PageView con la extensión Meta Pixel Helper o la pestaña Probar eventos.',
|
|
509
513
|
type: 'tech', priority: 'low', tags: ['seo', 'web', 'analytics', 'meta'], offsetDays: 4, checkKey: 'meta_pixel',
|
|
510
514
|
},
|
|
511
|
-
{
|
|
512
|
-
title: 'Verificar que sitemap.xml y robots.txt respondan 200 y no bloqueen el sitio',
|
|
513
|
-
description: 'Abrirlos en el navegador en el dominio de producción. Revisar que robots.txt no tenga "Disallow: /" heredado de staging y que apunte al sitemap. En SPAs, comprobar que el hosting no devuelva index.html para estas rutas.',
|
|
514
|
-
type: 'tech', priority: 'high', tags: ['seo', 'web'], offsetDays: 2,
|
|
515
|
-
},
|
|
516
|
-
{
|
|
517
|
-
title: 'Revisar título, meta description, h1 y canonical de cada página indexable',
|
|
518
|
-
description: 'Un <title> único (≤60) y meta description (≤155) por página, un solo h1 y canonical. Si es SPA, confirmar que cada ruta los entrega al rastreador (prerender/SSR o manejador de head).',
|
|
519
|
-
type: 'tech', priority: 'medium', tags: ['seo', 'web'], offsetDays: 3,
|
|
520
|
-
},
|
|
521
|
-
{
|
|
522
|
-
title: 'Configurar Open Graph / Twitter Cards con imagen 1200x630',
|
|
523
|
-
description: 'og:title, og:description, og:image y twitter:card. Probar el enlace pegándolo en WhatsApp, LinkedIn y X.',
|
|
524
|
-
type: 'business', priority: 'medium', tags: ['seo', 'web', 'social'], offsetDays: 3,
|
|
525
|
-
},
|
|
526
515
|
{
|
|
527
516
|
title: 'Medir y corregir Core Web Vitals en PageSpeed Insights (móvil)',
|
|
528
517
|
description: 'Correr PageSpeed Insights sobre la home y las páginas clave. Objetivo: LCP < 2.5s, CLS < 0.1, INP < 200ms. Atacar primero imágenes sin comprimir, fuentes bloqueantes y JS sin dividir.',
|
|
529
518
|
type: 'tech', priority: 'medium', tags: ['seo', 'web', 'performance'], offsetDays: 5, checkKey: 'core_web_vitals',
|
|
530
519
|
},
|
|
531
520
|
{
|
|
532
|
-
title: '
|
|
533
|
-
description: '
|
|
534
|
-
type: 'tech', priority: '
|
|
521
|
+
title: 'Revisar en Search Console la canónica que eligió Google y las páginas sin indexar',
|
|
522
|
+
description: '1-2 semanas después de lanzar (o de cambiar el dominio): inspeccionar la home y confirmar que "Google-selected canonical" sea tu URL canónica, y revisar Páginas → "Por qué no se indexan". Al terminar, márcalo como hecho en Buenas prácticas.',
|
|
523
|
+
type: 'tech', priority: 'high', tags: ['seo', 'web', 'search-console'], offsetDays: 14,
|
|
535
524
|
},
|
|
536
525
|
{
|
|
537
|
-
title: '
|
|
538
|
-
description: '
|
|
539
|
-
type: '
|
|
526
|
+
title: 'Apuntar tus perfiles y fichas a la URL canónica',
|
|
527
|
+
description: 'Bio de redes, fichas de las tiendas, npm, firmas de correo y enlaces dentro de la app con la misma URL canónica. Al terminar, márcalo como hecho en Buenas prácticas.',
|
|
528
|
+
type: 'business', priority: 'medium', tags: ['seo', 'web'], offsetDays: 7,
|
|
540
529
|
},
|
|
541
530
|
{
|
|
542
531
|
title: 'Importar la propiedad a Bing Webmaster Tools',
|
package/src/server.mjs
CHANGED
|
@@ -146,7 +146,7 @@ No le preguntes al usuario por workspaceId ni projectId -- ya están configurado
|
|
|
146
146
|
|
|
147
147
|
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.
|
|
148
148
|
|
|
149
|
-
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): primero llamá a get_project_findings -- ya trae, calculado por el servidor y priorizado, lo que falta o está mal dentro de Orquestra (vencidas, bloqueadas, módulos en riesgo, paridad, infraestructura, servicios que faltan, duplicadas) -- y preséntaselo al usuario por severidad antes de seguir; luego, para lo que solo se ve en el código, 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).
|
|
149
|
+
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): primero llamá a get_project_findings -- ya trae, calculado por el servidor y priorizado, lo que falta o está mal dentro de Orquestra (vencidas, bloqueadas, módulos en riesgo, paridad, infraestructura, servicios que faltan, duplicadas) -- y preséntaselo al usuario por severidad antes de seguir; luego, para lo que solo se ve en el código, 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). Antes, mira get_practices sin distributionId: los ítems con detectedByServer ya los revisa el servidor en los repos conectados, así que concéntrate en los que salen "unknown". Reporta en UNA llamada a report_foundations lo que confirmaste (pass si existe y se usa, fail si falta, na con motivo si el usuario decidió no tenerlo), con evidencia específica de archivos reales -- 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.
|
|
150
150
|
|
|
151
151
|
Si el usuario pregunta explícito qué Claude Skills de terceros le faltan (o pide instalar alguna) -- no lo hagas sin que te lo pidan: llamá a get_skills_catalog y revisá con acceso real al filesystem cuáles de esos keys ya existen en .claude/skills/ (del proyecto) o ~/.claude/skills/ (global). Antes de instalar cualquiera con install_skill, llamá primero a preview_skill y mostrale al usuario el skillMdContent completo -- nunca instales sin que haya visto ese contenido y confirmado explícitamente, los SKILL.md de terceros pueden traer instrucciones que un agente ejecuta solo con cargarlas.`
|
|
152
152
|
|
|
@@ -161,6 +161,22 @@ const SCOPE_EVIDENCE = {
|
|
|
161
161
|
notApplicableReason: z.string().optional().describe('Motivo de "no aplica" (obligatorio si notApplicable es true)'),
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
// Sin conexión para la carpeta (loadConfig falló): antes el proceso se cerraba
|
|
165
|
+
// y el cliente solo mostraba "Connection closed", sin el motivo. Ahora arranca
|
|
166
|
+
// con una sola tool que explica qué pasa y cómo arreglarlo, para que el agente
|
|
167
|
+
// se lo diga al usuario.
|
|
168
|
+
export function createSetupNeededServer(reason) {
|
|
169
|
+
const server = new McpServer(
|
|
170
|
+
{ name: 'orquestra-mcp', version: PKG_VERSION },
|
|
171
|
+
{ instructions: `Orquestra no está conectado en esta carpeta, así que sus herramientas no están disponibles. Si el usuario pide algo de Orquestra, llama a diagnostico_conexion y explícale cómo arreglarlo. Motivo: ${reason}` },
|
|
172
|
+
)
|
|
173
|
+
server.registerTool('diagnostico_conexion', {
|
|
174
|
+
description: 'Explica por qué Orquestra no está conectado en esta carpeta y cómo arreglarlo.',
|
|
175
|
+
inputSchema: {},
|
|
176
|
+
}, async () => ({ content: [{ type: 'text', text: `orquestra-mcp ${PKG_VERSION}: ${reason}` }] }))
|
|
177
|
+
return server
|
|
178
|
+
}
|
|
179
|
+
|
|
164
180
|
export function createServer() {
|
|
165
181
|
const config = loadConfig()
|
|
166
182
|
const server = new McpServer({ name: 'orquestra-mcp', version: PKG_VERSION }, { instructions: SERVER_INSTRUCTIONS })
|
|
@@ -357,6 +373,79 @@ export function createServer() {
|
|
|
357
373
|
{ distributionId: z.string().describe('Distribución type="web" con domain configurado') }
|
|
358
374
|
)
|
|
359
375
|
|
|
376
|
+
// Buenas prácticas (ORQ-392/394; tiendas iOS/Android ORQ-398): checklist que
|
|
377
|
+
// evalúa el servidor (lib/practices.mjs y lib/storePractices.mjs en infra) y
|
|
378
|
+
// guarda en `distributions.practices`. No hay
|
|
379
|
+
// GET propio: se lee de la distribución. Los puntos manuales los confirma una
|
|
380
|
+
// persona en la web -- a propósito no hay tool para marcarlos.
|
|
381
|
+
server.registerTool(
|
|
382
|
+
'get_practices',
|
|
383
|
+
{
|
|
384
|
+
description: 'Checklist de buenas prácticas de una distribución, evaluado por el servidor cada lunes. Web: redirecciones 301 (http→https, con/sin www), robots.txt, sitemap, noindex, contenido en el HTML sin JS, título/descripción, h1, canonical, og:image, JSON-LD y Core Web Vitals; para la app (indexable=false) solo https + noindex. iOS/Android: ficha pública en App Store/Google Play, que la ficha enlace a la web canónica, frescura de la versión, capturas y calificación, y que las URLs de privacidad/soporte respondan 200. Cada punto trae status (pass|warn|fail|unknown|manual|na), severity, detail y evidence ya redactados -- úsalos tal cual, no re-diagnostiques. Los puntos group="manual" (Search Console, Data Collection/Data safety, cuenta demo del revisor...) los confirma el usuario en la web. Úsala después de un deploy o de llenar la guía de publicación (antes, run_practices) o cuando el usuario pregunte por SEO, indexación o la ficha de la tienda. Para corregir un fail, trabaja en el código real del repo o en la guía de publicación y vuelve a revisar. SIN distributionId devuelve los checklists del PROYECTO, revisados a diario: FUNDAMENTOS (proyectos de código en construcción o lanzados: distribuciones, servicios, entornos y repos registrados, canal de beta, agente de QA, bugs de prioridad alta abiertos, y el grupo Código -- README/CI/Dependabot los ve el servidor en los repos conectados, analytics/crash_reporting/error_alerting solo con report_foundations) y CRECIMIENTO (etapa post): canales registrados y con ritmo, ritmo cumplido por canal en 28 días, contenido programado a 7 días, changelog reciente, última versión anunciada en Contenido, IDs de medición y buenas prácticas de la web pública -- para corregir un fail de ritmo crea piezas reales con create_content en canales de list_channels, nunca inventes un canal.',
|
|
385
|
+
inputSchema: {
|
|
386
|
+
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
387
|
+
distributionId: z.string().optional().describe('Distribución type="web", "ios" o "android" (ver list_distributions). Omitido = checklist de crecimiento del proyecto.'),
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
async ({ projectId, distributionId }) => {
|
|
391
|
+
try {
|
|
392
|
+
if (!distributionId) {
|
|
393
|
+
const p = await apiGet(config, `/v1/projects/${resolveProjectId(projectId)}`, {})
|
|
394
|
+
return textResult({
|
|
395
|
+
projectId: p.id, name: p.name, phase: p.phase,
|
|
396
|
+
foundations: p.foundations || null,
|
|
397
|
+
foundationsManual: p.foundationsManual || {},
|
|
398
|
+
foundationsCode: p.foundationsCode || {},
|
|
399
|
+
growth: p.growth || null,
|
|
400
|
+
growthManual: p.growthManual || {},
|
|
401
|
+
hint: [
|
|
402
|
+
!p.foundations && 'Fundamentos: se revisa en proyectos de código en construcción (mvp/alpha/beta) o ya lanzados.',
|
|
403
|
+
!p.growth && 'Crecimiento: solo en la etapa post (fase launch/v1/v2/v3/mantenimiento) o con el playbook post aplicado.',
|
|
404
|
+
].filter(Boolean).join(' ') || undefined,
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
const { items } = await apiGet(config, '/v1/distributions', { projectId: resolveProjectId(projectId) })
|
|
408
|
+
const d = (items || []).find(x => x.id === distributionId)
|
|
409
|
+
if (!d) return errorResult(new Error(`No existe la distribución ${distributionId} en este proyecto`))
|
|
410
|
+
const store = d.type === 'ios' || d.type === 'android'
|
|
411
|
+
if (d.type !== 'web' && !store) return errorResult(new Error('Las buenas prácticas solo aplican a distribuciones type="web", "ios" o "android"'))
|
|
412
|
+
return textResult({
|
|
413
|
+
id: d.id, name: d.name || d.label, type: d.type,
|
|
414
|
+
...(store ? { bundleId: d.bundleId || d.identifier || null } : { domain: d.domain || null, indexable: d.indexable ?? null }),
|
|
415
|
+
practices: d.practices || null,
|
|
416
|
+
practicesManual: d.practicesManual || {},
|
|
417
|
+
...(!store && !d.domain ? { hint: 'Sin domain configurado no se revisa: agrégalo con update_distribution.' } : {}),
|
|
418
|
+
...(store && !d.bundleId && !d.identifier ? { hint: 'Sin bundleId no se encuentra la ficha de la tienda: agrégalo con update_distribution.' } : {}),
|
|
419
|
+
})
|
|
420
|
+
} catch (err) {
|
|
421
|
+
return errorResult(err)
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
server.registerTool(
|
|
427
|
+
'run_practices',
|
|
428
|
+
{
|
|
429
|
+
description: 'Pide una revisión nueva de buenas prácticas para una distribución web con dominio o una app iOS/Android (por ejemplo, justo después de desplegar o de publicar en la tienda). Corre en segundo plano (unos segundos): responde "running"; el resultado aparece después en get_practices. 409 si ya hay una en curso. SIN distributionId recalcula al momento los dos checklists del proyecto (fundamentos y crecimiento) y devuelve {foundations, growth}; el que no aplica a la fase del proyecto viene como {skipped}. Si un punto que pasaba ahora falla, el servidor avisa solo al equipo.',
|
|
430
|
+
inputSchema: {
|
|
431
|
+
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
432
|
+
distributionId: z.string().optional().describe('Distribución type="web" con domain configurado, o type="ios"/"android". Omitido = checklist de crecimiento del proyecto.'),
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
async ({ projectId, distributionId }) => {
|
|
436
|
+
try {
|
|
437
|
+
const pid = resolveProjectId(projectId)
|
|
438
|
+
if (distributionId) return textResult(await apiPost(config, '/v1/practices', { projectId: pid, distributionId }))
|
|
439
|
+
// Sin distribución: los dos checklists del proyecto; el que no aplica responde 400.
|
|
440
|
+
const run = path => apiPost(config, path, { projectId: pid }).catch(err => ({ skipped: err.message }))
|
|
441
|
+
const [foundations, growth] = await Promise.all([run('/v1/foundations'), run('/v1/growth')])
|
|
442
|
+
return textResult({ foundations, growth })
|
|
443
|
+
} catch (err) {
|
|
444
|
+
return errorResult(err)
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
)
|
|
448
|
+
|
|
360
449
|
registerRead(
|
|
361
450
|
'get_infra_status',
|
|
362
451
|
'/v1/infra',
|
|
@@ -625,32 +714,30 @@ export function createServer() {
|
|
|
625
714
|
server.registerTool(
|
|
626
715
|
'get_foundations_checklist',
|
|
627
716
|
{
|
|
628
|
-
description: 'Checklist de "fundamentos olvidados" -- prácticas chicas (analytics, crash reporting, alertas de error
|
|
717
|
+
description: 'Checklist de "fundamentos olvidados" -- prácticas chicas (README, CI, escaneo de dependencias, analytics, crash reporting, alertas de error) 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), "detectionHint" para que TÚ (con acceso real al filesystem del repo) confirmes si se cumple, y "detectedByServer" (true = el servidor ya lo revisa solo en los repos conectados; no hace falta reportarlo salvo para marcar na o si no hay repo conectado). Reporta lo confirmado con report_foundations; el estado actual está en get_practices sin distributionId.',
|
|
629
718
|
},
|
|
630
719
|
async () => textResult(FOUNDATIONS)
|
|
631
720
|
)
|
|
632
721
|
|
|
722
|
+
// ORQ-400: reemplaza a report_foundation_gap (creaba una sugerencia suelta). Ahora lo
|
|
723
|
+
// confirmado queda en el checklist de fundamentos del proyecto (project.foundationsCode),
|
|
724
|
+
// tanto lo que se cumple como lo que falta, y la web lo pinta en el Dashboard.
|
|
633
725
|
server.registerTool(
|
|
634
|
-
'
|
|
726
|
+
'report_foundations',
|
|
635
727
|
{
|
|
636
|
-
description: 'Reporta
|
|
728
|
+
description: 'Reporta al checklist de fundamentos del proyecto lo que confirmaste en el código real de los repos (ítems de get_foundations_checklist): pass si existe Y se usa, fail si falta, na si no aplica (con el motivo). Nunca lo reportes sin haber revisado los archivos de verdad. Queda vigente 90 días y gana sobre lo que el servidor detecta solo en el repo, así que úsalo sobre todo para analytics/crash_reporting/error_alerting (el servidor no lee código) y para marcar na lo que el usuario decidió no tener. Recalcula el checklist y devuelve el resultado completo; un fail aparece en el Dashboard con "Crear tarea".',
|
|
637
729
|
inputSchema: {
|
|
638
730
|
projectId: z.string().optional().describe(PROJECT_ID_DESC),
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
731
|
+
reports: z.array(z.object({
|
|
732
|
+
key: z.enum(FOUNDATIONS.map(f => f.key)).describe('key del ítem de get_foundations_checklist'),
|
|
733
|
+
status: z.enum(['pass', 'fail', 'na']),
|
|
734
|
+
evidence: z.string().min(1).describe('Evidencia concreta y específica del proyecto: archivo y qué encontraste (ej. "Crashlytics.crashlytics() en AppDelegate.swift, sin equivalente en la web"), o el motivo de na. Nada genérico.'),
|
|
735
|
+
})).min(1),
|
|
642
736
|
},
|
|
643
737
|
},
|
|
644
|
-
async ({ projectId,
|
|
738
|
+
async ({ projectId, reports }) => {
|
|
645
739
|
try {
|
|
646
|
-
|
|
647
|
-
const data = await apiPost(config, '/v1/suggestions', {
|
|
648
|
-
projectId: resolvedProjectId,
|
|
649
|
-
kind: 'foundation_gap',
|
|
650
|
-
foundationKey, title, reason,
|
|
651
|
-
idempotencyKey: `foundation:${resolvedProjectId}:${foundationKey}`,
|
|
652
|
-
})
|
|
653
|
-
return textResult(data)
|
|
740
|
+
return textResult(await apiPost(config, '/v1/foundations', { projectId: resolveProjectId(projectId), reports }))
|
|
654
741
|
} catch (err) {
|
|
655
742
|
return errorResult(err)
|
|
656
743
|
}
|
|
@@ -2457,6 +2544,7 @@ export function createServer() {
|
|
|
2457
2544
|
repoUrl: z.string().optional().describe('URL del repo de este código -- solo informativo, no requiere credenciales.'),
|
|
2458
2545
|
knowledgeSource: z.string().optional().describe('Doc libre tipo CLAUDE.md para el agente de QA de esta distribución.'),
|
|
2459
2546
|
publishForm: z.record(z.string()).optional().describe('Respuestas del formulario de la Guía de publicación -- clave = key de referenceGuide en get_platform_playbook, valor = texto declarado. Reemplaza el mapa completo (nunca merge parcial). Preferí apply_platform_playbook a mandar esto directo -- esa tool avisa y pide confirmación si la distribución ya tiene datos guardados, esta lo sobreescribe sin preguntar.'),
|
|
2547
|
+
indexable: z.boolean().nullable().optional().describe('Solo web: ¿debe aparecer en Google? true = web pública (landing), false = app/login (las buenas prácticas le exigen noindex), null = que el servidor lo infiera. Pregúntale al usuario si no es obvio; no lo adivines.'),
|
|
2460
2548
|
})
|
|
2461
2549
|
|
|
2462
2550
|
// ═══ Contenido (Fase 1 del rediseño, 11 ago 2026; modelo multi-canal
|
package/src/setup.mjs
CHANGED
|
@@ -175,11 +175,17 @@ export async function runSetup() {
|
|
|
175
175
|
|
|
176
176
|
upsertProjectMapping({ apiBase, apiToken: token, workspaceId, folderPath, projectId: defaultProjectId })
|
|
177
177
|
console.log(`\nListo. "${folderPath}" (y sus subcarpetas) ya apunta a este proyecto -- guardado en ${CONFIG_PATH}.`)
|
|
178
|
+
// En Windows Claude Code necesita `cmd /c` para lanzar npx, y el config de
|
|
179
|
+
// Claude Desktop vive en %APPDATA%. `@latest` evita que npx reuse una
|
|
180
|
+
// versión vieja de su caché.
|
|
181
|
+
const isWindows = process.platform === 'win32'
|
|
178
182
|
console.log('\nSiguiente paso -- conéctalo a tu agente de IA:\n')
|
|
179
183
|
console.log(' Claude Code:')
|
|
180
|
-
console.log(
|
|
181
|
-
console.log('
|
|
182
|
-
console.log('
|
|
184
|
+
console.log(` claude mcp add orquestra -- ${isWindows ? 'cmd /c ' : ''}npx -y orquestra-mcp@latest`)
|
|
185
|
+
if (isWindows) console.log(' (si "claude" no se reconoce: npm install -g @anthropic-ai/claude-code y abre otra terminal)')
|
|
186
|
+
console.log(' Después reinicia Claude Code o reconéctalo con /mcp.\n')
|
|
187
|
+
console.log(` Claude Desktop -- agrega esto a ${isWindows ? '%APPDATA%\\Claude\\' : '~/Library/Application Support/Claude/'}claude_desktop_config.json:`)
|
|
188
|
+
console.log(' { "mcpServers": { "orquestra": { "command": "npx", "args": ["-y", "orquestra-mcp@latest"] } } }\n')
|
|
183
189
|
console.log(' ¿Usas Cursor, ChatGPT/Codex o Gemini CLI? La config de cada uno está en:')
|
|
184
190
|
console.log(' https://www.orquestra.me/mcp#instalacion\n')
|
|
185
191
|
console.log('Docs completas y todas las tools disponibles: https://www.orquestra.me/mcp')
|
package/src/stages.mjs
CHANGED
|
@@ -47,39 +47,29 @@ export const PLAYBOOKS = {
|
|
|
47
47
|
},
|
|
48
48
|
build: {
|
|
49
49
|
moduleName: 'Camino a beta',
|
|
50
|
-
moduleDescription: '
|
|
50
|
+
moduleDescription: 'El trabajo humano para llegar a una beta con usuarios reales dando feedback: entrada al producto, flujo de feedback y testers. Lo técnico que se puede revisar (infraestructura registrada, canal de beta, QA, bugs urgentes, README, CI, dependencias, analítica y crashes) lo revisa solo el checklist de Fundamentos del Dashboard.',
|
|
51
51
|
moduleTags: ['playbook', 'proyecto'],
|
|
52
52
|
intro: 'Construye con dirección: infraestructura medible, feedback desde el día uno y una beta que puedas poner en manos de usuarios reales.',
|
|
53
53
|
tasks: [
|
|
54
|
-
{ title: 'Configurar entornos e infraestructura base', description: 'Backend, base de datos, auth y storage funcionando. Regístralos en la sección Infra con su proveedor y estado.', type: 'tech', priority: 'high', tags: ['infra'], offsetDays: 3 },
|
|
55
|
-
{ title: 'Instrumentar analytics y crash reporting', description: 'Desde el primer build: eventos clave (registro, acción principal, retorno) y reporte de crashes. Sin datos no hay decisiones.', type: 'tech', priority: 'high', tags: ['analytics'], offsetDays: 7 },
|
|
56
54
|
{ title: 'Implementar autenticación y onboarding', description: 'El flujo de entrada define la primera impresión. Registro simple + un onboarding que lleve al usuario a su primer momento de valor.', type: 'feature', priority: 'high', tags: ['onboarding'], offsetDays: 14 },
|
|
57
55
|
{ title: 'Definir flujo de feedback de beta testers', description: '¿Cómo te van a reportar bugs y sugerencias? (formulario, grupo de WhatsApp, botón in-app). Decide dónde aterriza: aquí en Ideas.', type: 'improvement', priority: 'medium', tags: ['beta', 'feedback'], offsetDays: 18 },
|
|
58
|
-
{ title: 'Preparar build de beta (TestFlight / track interno / staging)', description: 'Un canal de distribución de beta por plataforma. Registra la versión en review en el panel de Infra.', type: 'tech', priority: 'high', tags: ['beta'], offsetDays: 21 },
|
|
59
56
|
{ title: 'Reclutar 10-20 beta testers', description: 'Idealmente del público objetivo que validaste en pre-proyecto. Amigos que no son el público te dirán que "está padre" y nada más.', type: 'business', priority: 'medium', tags: ['beta'], offsetDays: 25 },
|
|
60
|
-
{ title: 'Documentar setup del proyecto (README)', description: 'Cómo levantar el proyecto desde cero: dependencias, variables de entorno, comandos. Guárdalo en Docs. Tu yo del futuro lo agradece.', type: 'docs', priority: 'low', tags: ['docs'], offsetDays: 28 },
|
|
61
|
-
{ title: 'Checklist de estabilidad pre-lanzamiento', description: 'Crashes < 1%, flujos principales sin bugs conocidos, tiempos de carga aceptables, datos de prueba limpios. Si pasa, estás listo para launch.', type: 'tech', priority: 'high', tags: ['calidad'], offsetDays: 35 },
|
|
62
57
|
],
|
|
63
58
|
},
|
|
64
59
|
post: {
|
|
65
60
|
moduleName: 'Lanzamiento y crecimiento',
|
|
66
|
-
moduleDescription: 'El plan de
|
|
61
|
+
moduleDescription: 'El plan de lanzamiento después de la beta: posicionamiento, landing, canales y contenido de lanzamiento. Lo que se sostiene en el tiempo (ritmo de publicación, changelog anunciado, analítica, feedback y revisión semanal de métricas) lo revisa solo el checklist de Crecimiento del Dashboard.',
|
|
67
62
|
moduleTags: ['playbook', 'post-proyecto', 'marketing'],
|
|
68
63
|
intro: 'Terminaste la beta — ahora el trabajo es que la gente lo use. Este playbook te lleva del "¿y ahora qué?" a un lanzamiento con canales, contenido y métricas.',
|
|
69
64
|
tasks: [
|
|
70
65
|
{ title: 'Redactar el pitch: propuesta de valor en una frase', description: 'La frase que usarás en la tienda, la landing y redes. Fórmula: "[Producto] ayuda a [público] a [resultado] sin [dolor]". Itérala hasta que alguien ajeno la entienda a la primera.', type: 'business', priority: 'high', tags: ['marketing', 'posicionamiento'], offsetDays: 2 },
|
|
71
66
|
{ title: 'Preparar assets de tienda (ASO): capturas, descripción, keywords', description: '5-8 capturas que cuenten una historia (no pantallas sueltas), descripción con el pitch arriba, y keywords que tu público realmente busca.', type: 'business', priority: 'high', tags: ['marketing', 'aso'], offsetDays: 5 },
|
|
72
67
|
{ title: 'Publicar landing con captura de correos', description: 'Una página: pitch, 3 beneficios, capturas y un CTA (descargar o dejar correo). Los correos capturados son tu canal propio para siempre.', type: 'feature', priority: 'high', tags: ['marketing', 'landing'], offsetDays: 7 },
|
|
73
|
-
{ title: 'Configurar analytics de adquisición', description: 'Fuentes de tráfico, conversión de visita→registro→activación. Necesitas saber DE DÓNDE llegan los usuarios que sí se quedan.', type: 'tech', priority: 'medium', tags: ['analytics', 'adquisición'], offsetDays: 8 },
|
|
74
68
|
{ title: 'Conectar tus canales reales de contenido', description: 'Antes de programar nada: registra en Contenido → Cuentas conectadas los canales donde de verdad vas a publicar (redes, blog, newsletter). El plan de contenido de abajo solo sirve con canales reales, no uno genérico — si personalizas este playbook con IA y ya tienes canales conectados, te genera piezas reales directo.', type: 'tech', priority: 'high', tags: ['marketing', 'contenido'], offsetDays: 9 },
|
|
75
69
|
{ title: 'Plan de contenido de lanzamiento (2 semanas)', description: 'Con tus canales ya conectados (tarea anterior), programa en Contenido: teaser, anuncio, demo del caso de uso principal, historia de por qué lo construiste, testimonios de beta testers — uno por canal real, no una lista genérica.', type: 'business', priority: 'high', tags: ['marketing', 'contenido'], offsetDays: 10 },
|
|
76
70
|
{ title: 'Lanzar en canales: Product Hunt, comunidades y redes', description: 'Elige 3 canales donde vive tu público (PH, subreddits, grupos de FB/Discord, foros del nicho). Un post honesto de creador funciona mejor que uno de venta.', type: 'business', priority: 'high', tags: ['marketing', 'lanzamiento'], offsetDays: 14 },
|
|
77
71
|
{ title: 'Contactar 10 personas con audiencia en el nicho', description: 'Creators, newsletters, podcasts o prensa pequeña del tema. Mensaje corto y personalizado + acceso gratis. Con que respondan 2, ganaste.', type: 'followup', priority: 'medium', tags: ['marketing', 'outreach'], offsetDays: 16 },
|
|
78
|
-
{ title: 'Activar loop de feedback de usuarios reales', description: 'Un canal permanente: encuesta in-app, correo de bienvenida que pregunta "¿qué te trajo aquí?", o botón de sugerencias. Todo aterriza en Ideas.', type: 'improvement', priority: 'medium', tags: ['feedback'], offsetDays: 18 },
|
|
79
72
|
{ title: 'Definir y medir activación y retención (D1 / D7 / D30)', description: 'Activación: % que llega al momento de valor. Retención: % que regresa al día 1, 7 y 30. Estos números te dicen si tienes producto o solo descargas.', type: 'business', priority: 'medium', tags: ['métricas'], offsetDays: 21 },
|
|
80
|
-
{ title: 'Ritmo de contenido post-lanzamiento (3 publicaciones/semana)', description: 'El lanzamiento es un día; el crecimiento es constancia. Deja programadas en Contenido las siguientes 2 semanas: tips de uso, casos, mejoras del changelog — repartidas entre tus canales reales conectados.', type: 'business', priority: 'medium', tags: ['marketing', 'contenido'], offsetDays: 24 },
|
|
81
|
-
{ title: 'Publicar release notes / changelog público v1', description: 'Convierte tu Changelog en comunicación: qué hay de nuevo, en lenguaje de usuario. Cada release es una excusa para volver a aparecer.', type: 'docs', priority: 'low', tags: ['changelog'], offsetDays: 26 },
|
|
82
|
-
{ title: 'Revisión semanal de métricas y próximos pasos', description: 'Reunión recurrente (aunque seas solo tú): adquisición, activación, retención, top feedback. Decide UNA mejora prioritaria por semana.', type: 'meeting', priority: 'medium', tags: ['métricas', 'ritmo'], offsetDays: 28 },
|
|
83
73
|
],
|
|
84
74
|
},
|
|
85
75
|
}
|