rootkid0-initializer 0.1.2 → 0.1.3
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/.opencode/mcp/README.md
CHANGED
|
@@ -24,14 +24,15 @@ Configura todos los servidores MCP en esta carpeta para evitar definiciones dupl
|
|
|
24
24
|
El bootstrap del proyecto asume que MCP Notion ya esta preinstalado/configurado por el usuario.
|
|
25
25
|
El initializer NO instala ni modifica MCP global.
|
|
26
26
|
|
|
27
|
+
El bootstrap interactua con Notion solo via MCP (flujo OpenCode/agent), sin llamadas REST directas ni manejo de token en scripts.
|
|
28
|
+
|
|
27
29
|
Validacion minima del bootstrap:
|
|
28
30
|
|
|
29
|
-
- `~/.config/opencode/opencode.json`
|
|
30
|
-
- `~/.config/opencode/mcp-servers.json` (legacy)
|
|
31
|
+
- `~/.config/opencode/opencode.json`
|
|
31
32
|
|
|
32
33
|
Condicion minima requerida:
|
|
33
34
|
|
|
34
|
-
- Debe existir
|
|
35
|
+
- Debe existir `mcp.notion` habilitado (tambien soporta `mcp.servers.notion`).
|
|
35
36
|
|
|
36
37
|
Si falta, el init falla con mensaje de correccion porque el setup Notion es automatico en P1.
|
|
37
38
|
|
package/README.md
CHANGED
|
@@ -65,24 +65,27 @@ chmod +x rootkid0-bootstrap/init-project.sh
|
|
|
65
65
|
|
|
66
66
|
El script crea una carpeta nueva con la estructura actual del repositorio, excluye `rootkid0-bootstrap/` y `automation/`, reemplaza `{{PROJECT_NAME}}`, mantiene `AGENTS.md`, genera un README inicial y ejecuta bootstrap automatico de Notion.
|
|
67
67
|
|
|
68
|
-
## Setup automatico de Notion (MVP)
|
|
69
|
-
|
|
70
|
-
El init ejecuta bootstrap Notion automaticamente despues de crear el proyecto. Requisitos obligatorios:
|
|
68
|
+
## Setup automatico de Notion (MVP)
|
|
69
|
+
|
|
70
|
+
El init ejecuta bootstrap Notion automaticamente despues de crear el proyecto en modo MCP-only. Requisitos obligatorios:
|
|
71
71
|
|
|
72
72
|
- MCP Notion preinstalado/configurado por el usuario (el initializer no instala ni modifica MCP global).
|
|
73
|
-
- Archivo global `~/.config/opencode/opencode.json`
|
|
74
|
-
-
|
|
73
|
+
- Archivo global `~/.config/opencode/opencode.json` con `mcp.notion` habilitado (tambien soporta `mcp.servers.notion`).
|
|
74
|
+
- OpenCode CLI disponible para ejecutar el flujo de agente que crea la estructura en Notion via MCP.
|
|
75
75
|
- Variables opcionales:
|
|
76
|
-
- `NOTION_PARENT_PAGE_ID` (
|
|
76
|
+
- `NOTION_PARENT_PAGE_ID` (si existe, el root se crea debajo de ese parent)
|
|
77
77
|
- `NOTION_WORKSPACE_NAME` (opcional)
|
|
78
|
+
|
|
79
|
+
No se requiere `NOTION_TOKEN` en los scripts de bootstrap.
|
|
78
80
|
|
|
79
81
|
Resultado del bootstrap:
|
|
80
82
|
|
|
81
83
|
- Crea pagina raiz del proyecto en Notion.
|
|
82
84
|
- Si existe `NOTION_PARENT_PAGE_ID`, crea la pagina raiz como hija de ese parent.
|
|
83
85
|
- Si no existe `NOTION_PARENT_PAGE_ID`, crea la pagina raiz a nivel workspace del usuario.
|
|
86
|
+
- Toda la interaccion con Notion se ejecuta unicamente via MCP (sin llamadas REST directas desde bootstrap).
|
|
84
87
|
- Crea paginas por fase: `01-business` a `07-production` y `99-common`.
|
|
85
|
-
- Crea secciones placeholder del modelo multi-DB: `Projects`, `Phases`, `Deliverables`, `Backlog`, `Risks`, `Decisions`, `Incidents`.
|
|
86
|
-
- Guarda IDs generados en `99-common/notion-bootstrap.output.json` dentro del proyecto creado.
|
|
88
|
+
- Crea secciones placeholder del modelo multi-DB: `Projects`, `Phases`, `Deliverables`, `Backlog`, `Risks`, `Decisions`, `Incidents`.
|
|
89
|
+
- Guarda IDs generados en `99-common/notion-bootstrap.output.json` dentro del proyecto creado.
|
|
87
90
|
|
|
88
91
|
MCP recomendados para configurar: `context7`, `engram`, `notion`.
|
package/package.json
CHANGED
|
@@ -64,7 +64,7 @@ Proyecto inicializado desde rootkid0-initializer.
|
|
|
64
64
|
- Plantillas markdown con placeholders ya resueltos para tu proyecto.
|
|
65
65
|
- Configuracion inicial en \`99-common/project.config.json\`.
|
|
66
66
|
- Integracion OpenCode MVP (\`AGENTS.md\`, \`.opencode/\`, AGENTS locales, skills, MCP y agentes por rol).
|
|
67
|
-
- Setup automatico de Notion (MVP):
|
|
67
|
+
- Setup automatico de Notion (MVP): MCP-only via OpenCode/agent, sin token en scripts.
|
|
68
68
|
- Salida de IDs Notion en \`99-common/notion-bootstrap.output.json\`.
|
|
69
69
|
|
|
70
70
|
## Siguientes pasos
|
|
@@ -73,7 +73,7 @@ Proyecto inicializado desde rootkid0-initializer.
|
|
|
73
73
|
2. Ajusta \`99-common/project.config.json\` segun tu stack y contexto.
|
|
74
74
|
3. Revisa \`AGENTS.md\` como entrypoint de roles.
|
|
75
75
|
4. Revisa \`.opencode/README.md\` para el flujo global + subproyectos.
|
|
76
|
-
5. Confirma prerequisito MCP Notion (
|
|
76
|
+
5. Confirma prerequisito MCP Notion (mcp.notion habilitado en opencode.json) en \`.opencode/mcp/README.md\`.
|
|
77
77
|
6. Verifica \`99-common/notion-bootstrap.output.json\`.
|
|
78
78
|
7. Versiona cambios con Git y define tu backlog inicial.
|
|
79
79
|
EOF
|
|
@@ -58,7 +58,7 @@ Proyecto inicializado desde rootkid0-initializer.
|
|
|
58
58
|
- Plantillas markdown con placeholders ya resueltos para tu proyecto.
|
|
59
59
|
- Configuracion inicial en `99-common/project.config.json`.
|
|
60
60
|
- Integracion OpenCode MVP (`AGENTS.md`, `.opencode/`, AGENTS locales, skills, MCP y agentes por rol).
|
|
61
|
-
- Setup automatico de Notion (MVP):
|
|
61
|
+
- Setup automatico de Notion (MVP): MCP-only via OpenCode/agent, sin token en scripts.
|
|
62
62
|
- Salida de IDs Notion en `99-common/notion-bootstrap.output.json`.
|
|
63
63
|
|
|
64
64
|
## Siguientes pasos
|
|
@@ -67,7 +67,7 @@ Proyecto inicializado desde rootkid0-initializer.
|
|
|
67
67
|
2. Ajusta `99-common/project.config.json` segun tu stack y contexto.
|
|
68
68
|
3. Revisa `AGENTS.md` como entrypoint de roles.
|
|
69
69
|
4. Revisa `.opencode/README.md` para el flujo global + subproyectos.
|
|
70
|
-
5. Confirma prerequisito MCP Notion (
|
|
70
|
+
5. Confirma prerequisito MCP Notion (mcp.notion habilitado en opencode.json) en `.opencode/mcp/README.md`.
|
|
71
71
|
6. Verifica `99-common/notion-bootstrap.output.json`.
|
|
72
72
|
7. Versiona cambios con Git y define tu backlog inicial.
|
|
73
73
|
"@
|
|
@@ -13,161 +13,226 @@ function Fail([string]$Message) {
|
|
|
13
13
|
throw $Message
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
function Require-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Fail "Variable requerida no definida: $Name. Definila y vuelve a ejecutar init-project."
|
|
16
|
+
function Require-Command([string]$Name) {
|
|
17
|
+
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
|
|
18
|
+
Fail "Dependencia requerida no encontrada: $Name. Instalala y vuelve a ejecutar init-project."
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
function Resolve-
|
|
24
|
-
$
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
function Resolve-McpConfigFile() {
|
|
23
|
+
$opencodeFile = Join-Path $HOME ".config/opencode/opencode.json"
|
|
24
|
+
|
|
25
|
+
if (Test-Path -LiteralPath $opencodeFile) {
|
|
26
|
+
return $opencodeFile
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
Fail "Prerequisito faltante: no existe $opencodeFile. Configura OpenCode con MCP Notion habilitado y vuelve a ejecutar init-project."
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function Test-NotionMcpEnabled([string]$McpFile) {
|
|
29
33
|
try {
|
|
30
34
|
$raw = Get-Content -Path $McpFile -Raw | ConvertFrom-Json
|
|
31
|
-
if ($null -ne $raw.servers) {
|
|
32
|
-
$fromConfig = $raw.servers.notion.env.NOTION_TOKEN
|
|
33
|
-
}
|
|
34
|
-
elseif ($null -ne $raw.mcp -and $null -ne $raw.mcp.servers) {
|
|
35
|
-
$fromConfig = $raw.mcp.servers.notion.env.NOTION_TOKEN
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
$fromConfig = ""
|
|
39
|
-
}
|
|
40
35
|
}
|
|
41
36
|
catch {
|
|
42
|
-
$
|
|
37
|
+
return $false
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
if ($
|
|
46
|
-
if ($
|
|
47
|
-
$
|
|
48
|
-
|
|
49
|
-
if (-not [string]::IsNullOrWhiteSpace($resolved)) {
|
|
50
|
-
return $resolved
|
|
40
|
+
if ($null -ne $raw.mcp) {
|
|
41
|
+
if ($null -ne $raw.mcp.notion) {
|
|
42
|
+
if (($raw.mcp.notion -is [bool]) -and ($raw.mcp.notion -eq $false)) {
|
|
43
|
+
return $false
|
|
51
44
|
}
|
|
45
|
+
if (($raw.mcp.notion -isnot [bool]) -and ($null -ne $raw.mcp.notion.enabled) -and ($raw.mcp.notion.enabled -eq $false)) {
|
|
46
|
+
return $false
|
|
47
|
+
}
|
|
48
|
+
return $true
|
|
52
49
|
}
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
|
|
51
|
+
if ($null -ne $raw.mcp.servers -and $null -ne $raw.mcp.servers.notion) {
|
|
52
|
+
if (($null -ne $raw.mcp.servers.notion.enabled) -and ($raw.mcp.servers.notion.enabled -eq $false)) {
|
|
53
|
+
return $false
|
|
54
|
+
}
|
|
55
|
+
return $true
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
$legacyFile = Join-Path $HOME ".config/opencode/mcp-servers.json"
|
|
64
|
-
|
|
65
|
-
if (Test-Path -LiteralPath $opencodeFile) {
|
|
66
|
-
return $opencodeFile
|
|
59
|
+
if ($null -ne $raw.servers -and $null -ne $raw.servers.notion) {
|
|
60
|
+
if (($null -ne $raw.servers.notion.enabled) -and ($raw.servers.notion.enabled -eq $false)) {
|
|
61
|
+
return $false
|
|
62
|
+
}
|
|
63
|
+
return $true
|
|
67
64
|
}
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
return $false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function New-InstructionFile([string]$Path, [string]$RootTitle, [string]$ParentPageId) {
|
|
70
|
+
$content = @"
|
|
71
|
+
Objetivo: crear la estructura base de Notion para un proyecto rootkid0-initializer usando UNICAMENTE herramientas MCP de Notion.
|
|
72
|
+
|
|
73
|
+
Reglas obligatorias:
|
|
74
|
+
- No usar API HTTP directa de Notion.
|
|
75
|
+
- No usar tokens o variables de entorno de Notion.
|
|
76
|
+
- Usar solo las herramientas MCP de Notion disponibles en esta sesion.
|
|
77
|
+
- Responder al final SOLO con un JSON valido, sin markdown y sin texto adicional.
|
|
78
|
+
|
|
79
|
+
Pasos a ejecutar:
|
|
80
|
+
1) Crear una pagina raiz con titulo exacto: "$RootTitle".
|
|
81
|
+
2) Si se provee parent_page_id y no esta vacio, crear la pagina raiz como hija de ese page_id.
|
|
82
|
+
3) Crear bajo la pagina raiz estas paginas de fase:
|
|
83
|
+
- 01-business
|
|
84
|
+
- 02-proposal
|
|
85
|
+
- 03-design
|
|
86
|
+
- 04-management
|
|
87
|
+
- 05-development
|
|
88
|
+
- 06-deployment
|
|
89
|
+
- 07-production
|
|
90
|
+
- 99-common
|
|
91
|
+
4) Dentro de 99-common crear estas secciones:
|
|
92
|
+
- Projects
|
|
93
|
+
- Phases
|
|
94
|
+
- Deliverables
|
|
95
|
+
- Backlog
|
|
96
|
+
- Risks
|
|
97
|
+
- Decisions
|
|
98
|
+
- Incidents
|
|
99
|
+
|
|
100
|
+
Datos de entrada:
|
|
101
|
+
- root_title: "$RootTitle"
|
|
102
|
+
- parent_page_id: "$ParentPageId"
|
|
103
|
+
|
|
104
|
+
Formato de salida requerido (JSON exacto en estructura):
|
|
105
|
+
{
|
|
106
|
+
"notion_parent_mode": "workspace o page",
|
|
107
|
+
"notion_parent_page_id": "id o vacio",
|
|
108
|
+
"project_root_page_id": "id",
|
|
109
|
+
"phase_pages": {
|
|
110
|
+
"01-business": "id",
|
|
111
|
+
"02-proposal": "id",
|
|
112
|
+
"03-design": "id",
|
|
113
|
+
"04-management": "id",
|
|
114
|
+
"05-development": "id",
|
|
115
|
+
"06-deployment": "id",
|
|
116
|
+
"07-production": "id",
|
|
117
|
+
"99-common": "id"
|
|
118
|
+
},
|
|
119
|
+
"model_sections": {
|
|
120
|
+
"Projects": "id",
|
|
121
|
+
"Phases": "id",
|
|
122
|
+
"Deliverables": "id",
|
|
123
|
+
"Backlog": "id",
|
|
124
|
+
"Risks": "id",
|
|
125
|
+
"Decisions": "id",
|
|
126
|
+
"Incidents": "id"
|
|
71
127
|
}
|
|
128
|
+
}
|
|
129
|
+
"@
|
|
72
130
|
|
|
73
|
-
|
|
131
|
+
Set-Content -Path $Path -Value $content
|
|
74
132
|
}
|
|
75
133
|
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
134
|
+
function Parse-OpenCodeEvents([string]$EventsPath) {
|
|
135
|
+
$textParts = New-Object System.Collections.Generic.List[string]
|
|
136
|
+
$lines = Get-Content -Path $EventsPath
|
|
137
|
+
|
|
138
|
+
foreach ($line in $lines) {
|
|
139
|
+
if ([string]::IsNullOrWhiteSpace($line)) {
|
|
140
|
+
continue
|
|
81
141
|
}
|
|
82
|
-
|
|
83
|
-
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
$evt = $line | ConvertFrom-Json
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if ($evt.type -eq "text" -and $null -ne $evt.part -and -not [string]::IsNullOrWhiteSpace($evt.part.text)) {
|
|
151
|
+
[void]$textParts.Add([string]$evt.part.text)
|
|
84
152
|
}
|
|
85
|
-
return $false
|
|
86
153
|
}
|
|
87
|
-
|
|
88
|
-
|
|
154
|
+
|
|
155
|
+
if ($textParts.Count -eq 0) {
|
|
156
|
+
Fail "No se recibio respuesta de texto desde opencode run."
|
|
89
157
|
}
|
|
90
|
-
}
|
|
91
158
|
|
|
92
|
-
|
|
93
|
-
if ($
|
|
94
|
-
|
|
95
|
-
parent = @{ page_id = $ParentValue }
|
|
96
|
-
properties = @{
|
|
97
|
-
title = @{
|
|
98
|
-
title = @(
|
|
99
|
-
@{
|
|
100
|
-
type = "text"
|
|
101
|
-
text = @{ content = $Title }
|
|
102
|
-
}
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
} | ConvertTo-Json -Depth 10
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if ($ParentMode -eq "workspace") {
|
|
110
|
-
return @{
|
|
111
|
-
parent = @{ workspace = $true }
|
|
112
|
-
properties = @{
|
|
113
|
-
title = @{
|
|
114
|
-
title = @(
|
|
115
|
-
@{
|
|
116
|
-
type = "text"
|
|
117
|
-
text = @{ content = $Title }
|
|
118
|
-
}
|
|
119
|
-
)
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
} | ConvertTo-Json -Depth 10
|
|
159
|
+
$combined = ($textParts -join "`n").Trim()
|
|
160
|
+
if ($combined -match '^```(?:json)?\s*([\s\S]*?)\s*```$') {
|
|
161
|
+
$combined = $Matches[1].Trim()
|
|
123
162
|
}
|
|
124
163
|
|
|
125
|
-
|
|
126
|
-
|
|
164
|
+
try {
|
|
165
|
+
$result = $combined | ConvertFrom-Json
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
Fail "OpenCode no devolvio JSON valido para bootstrap Notion."
|
|
169
|
+
}
|
|
127
170
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"
|
|
132
|
-
"
|
|
171
|
+
$requiredPhases = @(
|
|
172
|
+
"01-business",
|
|
173
|
+
"02-proposal",
|
|
174
|
+
"03-design",
|
|
175
|
+
"04-management",
|
|
176
|
+
"05-development",
|
|
177
|
+
"06-deployment",
|
|
178
|
+
"07-production",
|
|
179
|
+
"99-common"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
$requiredSections = @(
|
|
183
|
+
"Projects",
|
|
184
|
+
"Phases",
|
|
185
|
+
"Deliverables",
|
|
186
|
+
"Backlog",
|
|
187
|
+
"Risks",
|
|
188
|
+
"Decisions",
|
|
189
|
+
"Incidents"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if ([string]::IsNullOrWhiteSpace($result.project_root_page_id)) {
|
|
193
|
+
Fail "Falta campo obligatorio en respuesta MCP: project_root_page_id"
|
|
133
194
|
}
|
|
134
195
|
|
|
135
|
-
|
|
196
|
+
if ($null -eq $result.phase_pages) {
|
|
197
|
+
Fail "Falta campo obligatorio en respuesta MCP: phase_pages"
|
|
198
|
+
}
|
|
136
199
|
|
|
137
|
-
|
|
138
|
-
|
|
200
|
+
if ($null -eq $result.model_sections) {
|
|
201
|
+
Fail "Falta campo obligatorio en respuesta MCP: model_sections"
|
|
139
202
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
203
|
+
|
|
204
|
+
foreach ($phase in $requiredPhases) {
|
|
205
|
+
$phaseId = $result.phase_pages.$phase
|
|
206
|
+
if ([string]::IsNullOrWhiteSpace($phaseId)) {
|
|
207
|
+
Fail "Falta fase obligatoria en respuesta MCP: $phase"
|
|
144
208
|
}
|
|
145
|
-
Fail "No se pudo crear pagina en Notion ($Title). Detalle: $apiMessage"
|
|
146
209
|
}
|
|
147
210
|
|
|
148
|
-
|
|
149
|
-
|
|
211
|
+
foreach ($section in $requiredSections) {
|
|
212
|
+
$sectionId = $result.model_sections.$section
|
|
213
|
+
if ([string]::IsNullOrWhiteSpace($sectionId)) {
|
|
214
|
+
Fail "Falta seccion obligatoria en respuesta MCP: $section"
|
|
215
|
+
}
|
|
150
216
|
}
|
|
151
217
|
|
|
152
|
-
return $
|
|
218
|
+
return $result
|
|
153
219
|
}
|
|
154
220
|
|
|
155
221
|
if (-not (Test-Path -LiteralPath $ProjectDir)) {
|
|
156
222
|
Fail "No existe el directorio de proyecto: $ProjectDir"
|
|
157
223
|
}
|
|
158
224
|
|
|
159
|
-
|
|
225
|
+
Require-Command "opencode"
|
|
160
226
|
|
|
161
|
-
|
|
162
|
-
|
|
227
|
+
$mcpFile = Resolve-McpConfigFile
|
|
228
|
+
if (-not (Test-NotionMcpEnabled -McpFile $mcpFile)) {
|
|
229
|
+
Fail "Prerequisito faltante: MCP Notion no habilitado en $mcpFile. Agrega la entrada mcp.notion (o mcp.servers.notion) y vuelve a ejecutar init-project."
|
|
163
230
|
}
|
|
164
231
|
|
|
165
|
-
$script:NotionToken = Resolve-NotionToken -McpFile $mcpFile
|
|
166
|
-
|
|
167
232
|
$workspaceName = [Environment]::GetEnvironmentVariable("NOTION_WORKSPACE_NAME")
|
|
168
233
|
$parentPageId = [Environment]::GetEnvironmentVariable("NOTION_PARENT_PAGE_ID")
|
|
169
234
|
|
|
170
|
-
Write-Host "Iniciando bootstrap automatico de Notion..."
|
|
235
|
+
Write-Host "Iniciando bootstrap automatico de Notion via MCP..."
|
|
171
236
|
|
|
172
237
|
$rootTitle = $ProjectName
|
|
173
238
|
if (-not [string]::IsNullOrWhiteSpace($workspaceName)) {
|
|
@@ -175,79 +240,50 @@ if (-not [string]::IsNullOrWhiteSpace($workspaceName)) {
|
|
|
175
240
|
}
|
|
176
241
|
|
|
177
242
|
$parentMode = "workspace"
|
|
178
|
-
$parentValue = ""
|
|
179
|
-
|
|
180
243
|
if (-not [string]::IsNullOrWhiteSpace($parentPageId)) {
|
|
181
244
|
$parentMode = "page"
|
|
182
|
-
|
|
183
|
-
Write-Host "Modo parent Notion: page ($parentPageId)"
|
|
245
|
+
Write-Host "Modo parent Notion (opcional): page ($parentPageId)"
|
|
184
246
|
}
|
|
185
247
|
else {
|
|
186
|
-
Write-Host "Modo parent Notion: workspace
|
|
248
|
+
Write-Host "Modo parent Notion (opcional): workspace"
|
|
187
249
|
}
|
|
188
250
|
|
|
189
|
-
$
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
$phaseNames = @(
|
|
193
|
-
"01-business",
|
|
194
|
-
"02-proposal",
|
|
195
|
-
"03-design",
|
|
196
|
-
"04-management",
|
|
197
|
-
"05-development",
|
|
198
|
-
"06-deployment",
|
|
199
|
-
"07-production",
|
|
200
|
-
"99-common"
|
|
201
|
-
)
|
|
251
|
+
$instructionFile = [System.IO.Path]::GetTempFileName()
|
|
252
|
+
$eventsFile = [System.IO.Path]::GetTempFileName()
|
|
202
253
|
|
|
203
|
-
|
|
204
|
-
$
|
|
254
|
+
try {
|
|
255
|
+
New-InstructionFile -Path $instructionFile -RootTitle $rootTitle -ParentPageId $parentPageId
|
|
205
256
|
|
|
206
|
-
|
|
207
|
-
$
|
|
208
|
-
|
|
209
|
-
Write-Host "Pagina creada ($phase): $phaseId"
|
|
210
|
-
if ($phase -eq "99-common") {
|
|
211
|
-
$commonPageId = $phaseId
|
|
257
|
+
& opencode run --format json --dir $ProjectDir --file $instructionFile "Sigue las instrucciones del archivo adjunto y devuelve SOLO el JSON final." 2>&1 | Set-Content -Path $eventsFile
|
|
258
|
+
if ($LASTEXITCODE -ne 0) {
|
|
259
|
+
Fail "Fallo la ejecucion de OpenCode para bootstrap de Notion. Verifica que MCP Notion este disponible y operativo."
|
|
212
260
|
}
|
|
213
|
-
}
|
|
214
261
|
|
|
215
|
-
|
|
216
|
-
Fail "No se pudo crear la pagina 99-common en Notion."
|
|
217
|
-
}
|
|
262
|
+
$mcpResult = Parse-OpenCodeEvents -EventsPath $eventsFile
|
|
218
263
|
|
|
219
|
-
$
|
|
220
|
-
"Projects",
|
|
221
|
-
"Phases",
|
|
222
|
-
"Deliverables",
|
|
223
|
-
"Backlog",
|
|
224
|
-
"Risks",
|
|
225
|
-
"Decisions",
|
|
226
|
-
"Incidents"
|
|
227
|
-
)
|
|
264
|
+
$outputPath = Join-Path $ProjectDir "99-common/notion-bootstrap.output.json"
|
|
228
265
|
|
|
229
|
-
$
|
|
266
|
+
$result = [ordered]@{
|
|
267
|
+
project_name = $ProjectName
|
|
268
|
+
workspace_name = $workspaceName
|
|
269
|
+
created_at_utc = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
|
270
|
+
notion_parent_mode = if (-not [string]::IsNullOrWhiteSpace($mcpResult.notion_parent_mode)) { $mcpResult.notion_parent_mode } else { $parentMode }
|
|
271
|
+
notion_parent_page_id = if (-not [string]::IsNullOrWhiteSpace($mcpResult.notion_parent_page_id)) { $mcpResult.notion_parent_page_id } else { $parentPageId }
|
|
272
|
+
project_root_page_id = $mcpResult.project_root_page_id
|
|
273
|
+
phase_pages = $mcpResult.phase_pages
|
|
274
|
+
model_sections = $mcpResult.model_sections
|
|
275
|
+
}
|
|
230
276
|
|
|
231
|
-
|
|
232
|
-
$sectionId = New-NotionPage -ParentMode "page" -ParentValue $commonPageId -Title $section
|
|
233
|
-
$sectionMap[$section] = $sectionId
|
|
234
|
-
Write-Host "Seccion modelo MVP creada ($section): $sectionId"
|
|
235
|
-
}
|
|
277
|
+
$result | ConvertTo-Json -Depth 10 | Set-Content -Path $outputPath
|
|
236
278
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
model_sections = $sectionMap
|
|
279
|
+
Write-Host "Bootstrap Notion completado via MCP."
|
|
280
|
+
Write-Host "Salida guardada en: $outputPath"
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
if (Test-Path -LiteralPath $instructionFile) {
|
|
284
|
+
Remove-Item -LiteralPath $instructionFile -Force
|
|
285
|
+
}
|
|
286
|
+
if (Test-Path -LiteralPath $eventsFile) {
|
|
287
|
+
Remove-Item -LiteralPath $eventsFile -Force
|
|
288
|
+
}
|
|
248
289
|
}
|
|
249
|
-
|
|
250
|
-
$result | ConvertTo-Json -Depth 10 | Set-Content -Path $outputPath
|
|
251
|
-
|
|
252
|
-
Write-Host "Bootstrap Notion completado."
|
|
253
|
-
Write-Host "Salida guardada en: $outputPath"
|
|
@@ -34,52 +34,6 @@ pick_python() {
|
|
|
34
34
|
return 1
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
validate_mcp_config() {
|
|
38
|
-
local mcp_file="$1"
|
|
39
|
-
local py_bin
|
|
40
|
-
local has_notion=""
|
|
41
|
-
|
|
42
|
-
if [[ ! -f "$mcp_file" ]]; then
|
|
43
|
-
fail "Prerequisito faltante: MCP Notion no disponible. Debe existir $mcp_file con entrada notion antes de ejecutar init-project."
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
py_bin="$(pick_python || true)"
|
|
47
|
-
if [[ -n "$py_bin" ]]; then
|
|
48
|
-
has_notion="$($py_bin - "$mcp_file" <<'PY'
|
|
49
|
-
import json
|
|
50
|
-
import sys
|
|
51
|
-
|
|
52
|
-
with open(sys.argv[1], "r", encoding="utf-8") as f:
|
|
53
|
-
data = json.load(f)
|
|
54
|
-
|
|
55
|
-
servers = {}
|
|
56
|
-
if isinstance(data, dict):
|
|
57
|
-
if isinstance(data.get("servers"), dict):
|
|
58
|
-
servers = data.get("servers", {})
|
|
59
|
-
elif isinstance(data.get("mcp"), dict) and isinstance(data["mcp"].get("servers"), dict):
|
|
60
|
-
servers = data["mcp"]["servers"]
|
|
61
|
-
|
|
62
|
-
print("yes" if "notion" in servers else "no")
|
|
63
|
-
PY
|
|
64
|
-
)"
|
|
65
|
-
else
|
|
66
|
-
has_notion="$(grep -q '"notion"' "$mcp_file" && echo yes || echo no)"
|
|
67
|
-
fi
|
|
68
|
-
|
|
69
|
-
if [[ "$has_notion" != "yes" ]]; then
|
|
70
|
-
fail "Prerequisito faltante: MCP Notion no disponible. Agrega la entrada notion en $mcp_file y vuelve a ejecutar init-project."
|
|
71
|
-
fi
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
require_env() {
|
|
75
|
-
local var_name="$1"
|
|
76
|
-
local var_value="${!var_name:-}"
|
|
77
|
-
|
|
78
|
-
if [[ -z "$var_value" ]]; then
|
|
79
|
-
fail "Variable requerida no definida: $var_name. Exporta $var_name y vuelve a ejecutar init-project."
|
|
80
|
-
fi
|
|
81
|
-
}
|
|
82
|
-
|
|
83
37
|
require_command() {
|
|
84
38
|
local command_name="$1"
|
|
85
39
|
if ! command -v "$command_name" >/dev/null 2>&1; then
|
|
@@ -87,147 +41,212 @@ require_command() {
|
|
|
87
41
|
fi
|
|
88
42
|
}
|
|
89
43
|
|
|
90
|
-
|
|
91
|
-
local
|
|
92
|
-
local token="${NOTION_TOKEN:-}"
|
|
93
|
-
local py_bin
|
|
44
|
+
resolve_mcp_config_file() {
|
|
45
|
+
local opencode_file="$HOME/.config/opencode/opencode.json"
|
|
94
46
|
|
|
95
|
-
if [[ -
|
|
96
|
-
printf '%s' "$
|
|
47
|
+
if [[ -f "$opencode_file" ]]; then
|
|
48
|
+
printf '%s' "$opencode_file"
|
|
97
49
|
return 0
|
|
98
50
|
fi
|
|
99
51
|
|
|
52
|
+
fail "Prerequisito faltante: no existe $opencode_file. Configura OpenCode con MCP Notion habilitado y vuelve a ejecutar init-project."
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
validate_mcp_config() {
|
|
56
|
+
local mcp_file="$1"
|
|
57
|
+
local py_bin
|
|
58
|
+
local has_notion=""
|
|
59
|
+
|
|
100
60
|
py_bin="$(pick_python || true)"
|
|
101
61
|
if [[ -n "$py_bin" ]]; then
|
|
102
|
-
|
|
62
|
+
has_notion="$($py_bin - "$mcp_file" <<'PY'
|
|
103
63
|
import json
|
|
104
|
-
import os
|
|
105
|
-
import re
|
|
106
64
|
import sys
|
|
107
65
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
value = ""
|
|
112
|
-
servers = {}
|
|
113
|
-
if isinstance(data, dict):
|
|
114
|
-
if isinstance(data.get("servers"), dict):
|
|
115
|
-
servers = data.get("servers", {})
|
|
116
|
-
elif isinstance(data.get("mcp"), dict) and isinstance(data["mcp"].get("servers"), dict):
|
|
117
|
-
servers = data["mcp"]["servers"]
|
|
118
|
-
|
|
119
|
-
notion = servers.get("notion", {}) if isinstance(servers, dict) else {}
|
|
120
|
-
env = notion.get("env", {}) if isinstance(notion, dict) else {}
|
|
121
|
-
|
|
122
|
-
if isinstance(env, dict):
|
|
123
|
-
raw = env.get("NOTION_TOKEN", "")
|
|
124
|
-
if isinstance(raw, str):
|
|
125
|
-
match = re.fullmatch(r"\$\{([^}]+)\}", raw.strip())
|
|
126
|
-
if match:
|
|
127
|
-
value = os.environ.get(match.group(1), "")
|
|
128
|
-
else:
|
|
129
|
-
value = raw
|
|
130
|
-
|
|
131
|
-
print(value)
|
|
132
|
-
PY
|
|
133
|
-
)"
|
|
134
|
-
fi
|
|
135
|
-
|
|
136
|
-
if [[ -z "$token" ]]; then
|
|
137
|
-
fail "No se pudo resolver credencial de Notion. Define NOTION_TOKEN o configura servers.notion.env.NOTION_TOKEN en $mcp_file."
|
|
138
|
-
fi
|
|
139
|
-
|
|
140
|
-
printf '%s' "$token"
|
|
141
|
-
}
|
|
66
|
+
def has_enabled_notion(data):
|
|
67
|
+
if not isinstance(data, dict):
|
|
68
|
+
return False
|
|
142
69
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
70
|
+
mcp = data.get("mcp")
|
|
71
|
+
if isinstance(mcp, dict):
|
|
72
|
+
notion = mcp.get("notion")
|
|
73
|
+
if notion not in (None, False):
|
|
74
|
+
if not (isinstance(notion, dict) and notion.get("enabled") is False):
|
|
75
|
+
return True
|
|
146
76
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
77
|
+
servers = mcp.get("servers")
|
|
78
|
+
if isinstance(servers, dict) and "notion" in servers:
|
|
79
|
+
notion_server = servers.get("notion")
|
|
80
|
+
if not (isinstance(notion_server, dict) and notion_server.get("enabled") is False):
|
|
81
|
+
return True
|
|
151
82
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
83
|
+
servers = data.get("servers")
|
|
84
|
+
if isinstance(servers, dict) and "notion" in servers:
|
|
85
|
+
notion_server = servers.get("notion")
|
|
86
|
+
if not (isinstance(notion_server, dict) and notion_server.get("enabled") is False):
|
|
87
|
+
return True
|
|
156
88
|
|
|
157
|
-
|
|
158
|
-
}
|
|
89
|
+
return False
|
|
159
90
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
local parent_value="$2"
|
|
163
|
-
local title="$3"
|
|
91
|
+
with open(sys.argv[1], "r", encoding="utf-8") as f:
|
|
92
|
+
raw = json.load(f)
|
|
164
93
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
94
|
+
print("yes" if has_enabled_notion(raw) else "no")
|
|
95
|
+
PY
|
|
96
|
+
)"
|
|
97
|
+
else
|
|
98
|
+
has_notion="$(grep -q '"notion"' "$mcp_file" && echo yes || echo no)"
|
|
170
99
|
fi
|
|
171
100
|
|
|
172
|
-
if [[ "$
|
|
173
|
-
|
|
174
|
-
"$(json_escape "$title")"
|
|
175
|
-
return 0
|
|
101
|
+
if [[ "$has_notion" != "yes" ]]; then
|
|
102
|
+
fail "Prerequisito faltante: MCP Notion no habilitado en $mcp_file. Agrega la entrada mcp.notion (o mcp.servers.notion) y vuelve a ejecutar init-project."
|
|
176
103
|
fi
|
|
104
|
+
}
|
|
177
105
|
|
|
178
|
-
|
|
106
|
+
build_instruction_file() {
|
|
107
|
+
local instruction_file="$1"
|
|
108
|
+
local root_title="$2"
|
|
109
|
+
local parent_page_id="$3"
|
|
110
|
+
|
|
111
|
+
cat > "$instruction_file" <<EOF
|
|
112
|
+
Objetivo: crear la estructura base de Notion para un proyecto rootkid0-initializer usando UNICAMENTE herramientas MCP de Notion.
|
|
113
|
+
|
|
114
|
+
Reglas obligatorias:
|
|
115
|
+
- No usar API HTTP directa de Notion.
|
|
116
|
+
- No usar tokens o variables de entorno de Notion.
|
|
117
|
+
- Usar solo las herramientas MCP de Notion disponibles en esta sesion.
|
|
118
|
+
- Responder al final SOLO con un JSON valido, sin markdown y sin texto adicional.
|
|
119
|
+
|
|
120
|
+
Pasos a ejecutar:
|
|
121
|
+
1) Crear una pagina raiz con titulo exacto: "$root_title".
|
|
122
|
+
2) Si se provee parent_page_id y no esta vacio, crear la pagina raiz como hija de ese page_id.
|
|
123
|
+
3) Crear bajo la pagina raiz estas paginas de fase:
|
|
124
|
+
- 01-business
|
|
125
|
+
- 02-proposal
|
|
126
|
+
- 03-design
|
|
127
|
+
- 04-management
|
|
128
|
+
- 05-development
|
|
129
|
+
- 06-deployment
|
|
130
|
+
- 07-production
|
|
131
|
+
- 99-common
|
|
132
|
+
4) Dentro de 99-common crear estas secciones:
|
|
133
|
+
- Projects
|
|
134
|
+
- Phases
|
|
135
|
+
- Deliverables
|
|
136
|
+
- Backlog
|
|
137
|
+
- Risks
|
|
138
|
+
- Decisions
|
|
139
|
+
- Incidents
|
|
140
|
+
|
|
141
|
+
Datos de entrada:
|
|
142
|
+
- root_title: "$root_title"
|
|
143
|
+
- parent_page_id: "$parent_page_id"
|
|
144
|
+
|
|
145
|
+
Formato de salida requerido (JSON exacto en estructura):
|
|
146
|
+
{
|
|
147
|
+
"notion_parent_mode": "workspace o page",
|
|
148
|
+
"notion_parent_page_id": "id o vacio",
|
|
149
|
+
"project_root_page_id": "id",
|
|
150
|
+
"phase_pages": {
|
|
151
|
+
"01-business": "id",
|
|
152
|
+
"02-proposal": "id",
|
|
153
|
+
"03-design": "id",
|
|
154
|
+
"04-management": "id",
|
|
155
|
+
"05-development": "id",
|
|
156
|
+
"06-deployment": "id",
|
|
157
|
+
"07-production": "id",
|
|
158
|
+
"99-common": "id"
|
|
159
|
+
},
|
|
160
|
+
"model_sections": {
|
|
161
|
+
"Projects": "id",
|
|
162
|
+
"Phases": "id",
|
|
163
|
+
"Deliverables": "id",
|
|
164
|
+
"Backlog": "id",
|
|
165
|
+
"Risks": "id",
|
|
166
|
+
"Decisions": "id",
|
|
167
|
+
"Incidents": "id"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
EOF
|
|
179
171
|
}
|
|
180
172
|
|
|
181
|
-
|
|
182
|
-
local
|
|
183
|
-
local parent_value="$2"
|
|
184
|
-
local title="$3"
|
|
185
|
-
local payload
|
|
186
|
-
local response_file
|
|
187
|
-
local status
|
|
173
|
+
extract_bootstrap_result() {
|
|
174
|
+
local events_file="$1"
|
|
188
175
|
local py_bin
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
status="$(curl -sS -o "$response_file" -w "%{http_code}" \
|
|
195
|
-
-X POST "https://api.notion.com/v1/pages" \
|
|
196
|
-
-H "Authorization: Bearer $NOTION_AUTH_TOKEN" \
|
|
197
|
-
-H "Notion-Version: 2022-06-28" \
|
|
198
|
-
-H "Content-Type: application/json" \
|
|
199
|
-
--data "$payload")"
|
|
200
|
-
|
|
201
|
-
if [[ "$status" -lt 200 || "$status" -ge 300 ]]; then
|
|
202
|
-
local error_body
|
|
203
|
-
error_body="$(tr -d '\n' < "$response_file")"
|
|
204
|
-
rm -f "$response_file"
|
|
205
|
-
fail "Notion API devolvio HTTP $status al crear '$title'. Respuesta: $error_body"
|
|
176
|
+
py_bin="$(pick_python || true)"
|
|
177
|
+
|
|
178
|
+
if [[ -z "$py_bin" ]]; then
|
|
179
|
+
fail "No se encontro python/python3 para validar salida JSON de OpenCode."
|
|
206
180
|
fi
|
|
207
181
|
|
|
208
|
-
py_bin
|
|
209
|
-
if [[ -n "$py_bin" ]]; then
|
|
210
|
-
page_id="$("$py_bin" - "$response_file" <<'PY'
|
|
182
|
+
"$py_bin" - "$events_file" <<'PY'
|
|
211
183
|
import json
|
|
184
|
+
import re
|
|
212
185
|
import sys
|
|
213
186
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
187
|
+
events_file = sys.argv[1]
|
|
188
|
+
parts = []
|
|
189
|
+
|
|
190
|
+
with open(events_file, "r", encoding="utf-8") as f:
|
|
191
|
+
for line in f:
|
|
192
|
+
line = line.strip()
|
|
193
|
+
if not line:
|
|
194
|
+
continue
|
|
195
|
+
try:
|
|
196
|
+
evt = json.loads(line)
|
|
197
|
+
except json.JSONDecodeError:
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
if evt.get("type") == "text":
|
|
201
|
+
part = evt.get("part", {})
|
|
202
|
+
text = part.get("text")
|
|
203
|
+
if isinstance(text, str) and text.strip():
|
|
204
|
+
parts.append(text)
|
|
205
|
+
|
|
206
|
+
combined = "\n".join(parts).strip()
|
|
207
|
+
if not combined:
|
|
208
|
+
raise SystemExit("No se recibio respuesta de texto desde opencode run.")
|
|
209
|
+
|
|
210
|
+
fence_match = re.fullmatch(r"```(?:json)?\s*(.*?)\s*```", combined, flags=re.DOTALL)
|
|
211
|
+
if fence_match:
|
|
212
|
+
combined = fence_match.group(1).strip()
|
|
213
|
+
|
|
214
|
+
data = json.loads(combined)
|
|
215
|
+
|
|
216
|
+
required_phases = [
|
|
217
|
+
"01-business",
|
|
218
|
+
"02-proposal",
|
|
219
|
+
"03-design",
|
|
220
|
+
"04-management",
|
|
221
|
+
"05-development",
|
|
222
|
+
"06-deployment",
|
|
223
|
+
"07-production",
|
|
224
|
+
"99-common",
|
|
225
|
+
]
|
|
226
|
+
required_sections = [
|
|
227
|
+
"Projects",
|
|
228
|
+
"Phases",
|
|
229
|
+
"Deliverables",
|
|
230
|
+
"Backlog",
|
|
231
|
+
"Risks",
|
|
232
|
+
"Decisions",
|
|
233
|
+
"Incidents",
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
for top in ["project_root_page_id", "phase_pages", "model_sections"]:
|
|
237
|
+
if top not in data:
|
|
238
|
+
raise SystemExit(f"Falta campo obligatorio en respuesta MCP: {top}")
|
|
239
|
+
|
|
240
|
+
for key in required_phases:
|
|
241
|
+
if key not in data["phase_pages"]:
|
|
242
|
+
raise SystemExit(f"Falta fase obligatoria en respuesta MCP: {key}")
|
|
243
|
+
|
|
244
|
+
for key in required_sections:
|
|
245
|
+
if key not in data["model_sections"]:
|
|
246
|
+
raise SystemExit(f"Falta seccion obligatoria en respuesta MCP: {key}")
|
|
247
|
+
|
|
248
|
+
print(json.dumps(data, ensure_ascii=False))
|
|
218
249
|
PY
|
|
219
|
-
)"
|
|
220
|
-
else
|
|
221
|
-
page_id="$(grep -o '"id":"[^"]*"' "$response_file" | head -n 1 | cut -d '"' -f 4)"
|
|
222
|
-
fi
|
|
223
|
-
|
|
224
|
-
rm -f "$response_file"
|
|
225
|
-
|
|
226
|
-
if [[ -z "$page_id" ]]; then
|
|
227
|
-
fail "No se pudo extraer el id de la pagina creada para '$title'."
|
|
228
|
-
fi
|
|
229
|
-
|
|
230
|
-
printf '%s' "$page_id"
|
|
231
250
|
}
|
|
232
251
|
|
|
233
252
|
PROJECT_NAME=""
|
|
@@ -262,15 +281,14 @@ if [[ ! -d "$PROJECT_DIR" ]]; then
|
|
|
262
281
|
fail "No existe el directorio de proyecto: $PROJECT_DIR"
|
|
263
282
|
fi
|
|
264
283
|
|
|
284
|
+
require_command "opencode"
|
|
265
285
|
mcp_config_file="$(resolve_mcp_config_file)"
|
|
266
286
|
validate_mcp_config "$mcp_config_file"
|
|
267
|
-
require_command "curl"
|
|
268
|
-
NOTION_AUTH_TOKEN="$(resolve_notion_token "$mcp_config_file")"
|
|
269
287
|
|
|
270
288
|
NOTION_WORKSPACE_NAME="${NOTION_WORKSPACE_NAME:-}"
|
|
271
289
|
NOTION_PARENT_PAGE_ID="${NOTION_PARENT_PAGE_ID:-}"
|
|
272
290
|
|
|
273
|
-
echo "Iniciando bootstrap automatico de Notion..."
|
|
291
|
+
echo "Iniciando bootstrap automatico de Notion via MCP..."
|
|
274
292
|
|
|
275
293
|
root_title="$PROJECT_NAME"
|
|
276
294
|
if [[ -n "$NOTION_WORKSPACE_NAME" ]]; then
|
|
@@ -278,102 +296,61 @@ if [[ -n "$NOTION_WORKSPACE_NAME" ]]; then
|
|
|
278
296
|
fi
|
|
279
297
|
|
|
280
298
|
parent_mode="workspace"
|
|
281
|
-
parent_value=""
|
|
282
|
-
|
|
283
299
|
if [[ -n "$NOTION_PARENT_PAGE_ID" ]]; then
|
|
284
300
|
parent_mode="page"
|
|
285
|
-
|
|
286
|
-
echo "Modo parent Notion: page ($NOTION_PARENT_PAGE_ID)"
|
|
301
|
+
echo "Modo parent Notion (opcional): page ($NOTION_PARENT_PAGE_ID)"
|
|
287
302
|
else
|
|
288
|
-
echo "Modo parent Notion: workspace
|
|
303
|
+
echo "Modo parent Notion (opcional): workspace"
|
|
289
304
|
fi
|
|
290
305
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
phase_names=(
|
|
295
|
-
"01-business"
|
|
296
|
-
"02-proposal"
|
|
297
|
-
"03-design"
|
|
298
|
-
"04-management"
|
|
299
|
-
"05-development"
|
|
300
|
-
"06-deployment"
|
|
301
|
-
"07-production"
|
|
302
|
-
"99-common"
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
phase_ids=()
|
|
306
|
-
common_page_id=""
|
|
307
|
-
|
|
308
|
-
for phase in "${phase_names[@]}"; do
|
|
309
|
-
phase_id="$(create_notion_page "page" "$project_root_page_id" "$phase")"
|
|
310
|
-
phase_ids+=("$phase_id")
|
|
311
|
-
echo "Pagina creada ($phase): $phase_id"
|
|
312
|
-
if [[ "$phase" == "99-common" ]]; then
|
|
313
|
-
common_page_id="$phase_id"
|
|
314
|
-
fi
|
|
315
|
-
done
|
|
306
|
+
instruction_file="$(mktemp)"
|
|
307
|
+
events_file="$(mktemp)"
|
|
308
|
+
trap 'rm -f "$instruction_file" "$events_file"' EXIT
|
|
316
309
|
|
|
317
|
-
|
|
318
|
-
|
|
310
|
+
build_instruction_file "$instruction_file" "$root_title" "$NOTION_PARENT_PAGE_ID"
|
|
311
|
+
|
|
312
|
+
if ! opencode run --format json --dir "$PROJECT_DIR" --file "$instruction_file" \
|
|
313
|
+
"Sigue las instrucciones del archivo adjunto y devuelve SOLO el JSON final." > "$events_file"; then
|
|
314
|
+
fail "Fallo la ejecucion de OpenCode para bootstrap de Notion. Verifica que MCP Notion este disponible y operativo."
|
|
319
315
|
fi
|
|
320
316
|
|
|
321
|
-
|
|
322
|
-
"Projects"
|
|
323
|
-
"Phases"
|
|
324
|
-
"Deliverables"
|
|
325
|
-
"Backlog"
|
|
326
|
-
"Risks"
|
|
327
|
-
"Decisions"
|
|
328
|
-
"Incidents"
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
section_ids=()
|
|
332
|
-
for section in "${section_names[@]}"; do
|
|
333
|
-
section_id="$(create_notion_page "page" "$common_page_id" "$section")"
|
|
334
|
-
section_ids+=("$section_id")
|
|
335
|
-
echo "Seccion modelo MVP creada ($section): $section_id"
|
|
336
|
-
done
|
|
317
|
+
mcp_result_json="$(extract_bootstrap_result "$events_file")"
|
|
337
318
|
|
|
338
319
|
output_file="$PROJECT_DIR/99-common/notion-bootstrap.output.json"
|
|
339
320
|
timestamp_utc="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
340
321
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
echo "}"
|
|
376
|
-
} > "$output_file"
|
|
377
|
-
|
|
378
|
-
echo "Bootstrap Notion completado."
|
|
322
|
+
py_bin="$(pick_python || true)"
|
|
323
|
+
if [[ -z "$py_bin" ]]; then
|
|
324
|
+
fail "No se encontro python/python3 para consolidar salida JSON del bootstrap Notion."
|
|
325
|
+
fi
|
|
326
|
+
|
|
327
|
+
"$py_bin" - "$mcp_result_json" "$PROJECT_NAME" "$NOTION_WORKSPACE_NAME" "$timestamp_utc" "$parent_mode" "$NOTION_PARENT_PAGE_ID" "$output_file" <<'PY'
|
|
328
|
+
import json
|
|
329
|
+
import sys
|
|
330
|
+
|
|
331
|
+
mcp_result = json.loads(sys.argv[1])
|
|
332
|
+
project_name = sys.argv[2]
|
|
333
|
+
workspace_name = sys.argv[3]
|
|
334
|
+
timestamp_utc = sys.argv[4]
|
|
335
|
+
parent_mode = sys.argv[5]
|
|
336
|
+
parent_page_id = sys.argv[6]
|
|
337
|
+
output_file = sys.argv[7]
|
|
338
|
+
|
|
339
|
+
result = {
|
|
340
|
+
"project_name": project_name,
|
|
341
|
+
"workspace_name": workspace_name,
|
|
342
|
+
"created_at_utc": timestamp_utc,
|
|
343
|
+
"notion_parent_mode": mcp_result.get("notion_parent_mode", parent_mode),
|
|
344
|
+
"notion_parent_page_id": mcp_result.get("notion_parent_page_id", parent_page_id),
|
|
345
|
+
"project_root_page_id": mcp_result["project_root_page_id"],
|
|
346
|
+
"phase_pages": mcp_result["phase_pages"],
|
|
347
|
+
"model_sections": mcp_result["model_sections"],
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
351
|
+
json.dump(result, f, indent=2, ensure_ascii=False)
|
|
352
|
+
f.write("\n")
|
|
353
|
+
PY
|
|
354
|
+
|
|
355
|
+
echo "Bootstrap Notion completado via MCP."
|
|
379
356
|
echo "Salida guardada en: $output_file"
|