rootkid0-initializer 0.1.1 → 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,13 +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/
|
|
31
|
+
- `~/.config/opencode/opencode.json`
|
|
30
32
|
|
|
31
33
|
Condicion minima requerida:
|
|
32
34
|
|
|
33
|
-
- Debe existir
|
|
35
|
+
- Debe existir `mcp.notion` habilitado (tambien soporta `mcp.servers.notion`).
|
|
34
36
|
|
|
35
37
|
Si falta, el init falla con mensaje de correccion porque el setup Notion es automatico en P1.
|
|
36
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/
|
|
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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rootkid0-initializer",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "CLI para inicializar proyectos rootkid0.",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "CLI para inicializar el flujo de proyectos al estilo rootkid0.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"bin": {
|
|
@@ -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,126 +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
|
-
$fromConfig = $raw.servers.notion.env.NOTION_TOKEN
|
|
32
35
|
}
|
|
33
36
|
catch {
|
|
34
|
-
$
|
|
37
|
+
return $false
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
if ($
|
|
38
|
-
if ($
|
|
39
|
-
$
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
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
|
|
49
|
+
}
|
|
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
|
|
43
54
|
}
|
|
55
|
+
return $true
|
|
44
56
|
}
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
}
|
|
58
|
+
|
|
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
|
|
47
62
|
}
|
|
63
|
+
return $true
|
|
48
64
|
}
|
|
49
65
|
|
|
50
|
-
|
|
66
|
+
return $false
|
|
51
67
|
}
|
|
52
68
|
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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"
|
|
84
127
|
}
|
|
128
|
+
}
|
|
129
|
+
"@
|
|
85
130
|
|
|
86
|
-
|
|
131
|
+
Set-Content -Path $Path -Value $content
|
|
87
132
|
}
|
|
88
133
|
|
|
89
|
-
function
|
|
90
|
-
$
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
141
|
+
}
|
|
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)
|
|
152
|
+
}
|
|
94
153
|
}
|
|
95
154
|
|
|
96
|
-
|
|
155
|
+
if ($textParts.Count -eq 0) {
|
|
156
|
+
Fail "No se recibio respuesta de texto desde opencode run."
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
$combined = ($textParts -join "`n").Trim()
|
|
160
|
+
if ($combined -match '^```(?:json)?\s*([\s\S]*?)\s*```$') {
|
|
161
|
+
$combined = $Matches[1].Trim()
|
|
162
|
+
}
|
|
97
163
|
|
|
98
164
|
try {
|
|
99
|
-
$
|
|
165
|
+
$result = $combined | ConvertFrom-Json
|
|
100
166
|
}
|
|
101
167
|
catch {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
168
|
+
Fail "OpenCode no devolvio JSON valido para bootstrap Notion."
|
|
169
|
+
}
|
|
170
|
+
|
|
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"
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if ($null -eq $result.phase_pages) {
|
|
197
|
+
Fail "Falta campo obligatorio en respuesta MCP: phase_pages"
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if ($null -eq $result.model_sections) {
|
|
201
|
+
Fail "Falta campo obligatorio en respuesta MCP: model_sections"
|
|
202
|
+
}
|
|
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"
|
|
105
208
|
}
|
|
106
|
-
Fail "No se pudo crear pagina en Notion ($Title). Detalle: $apiMessage"
|
|
107
209
|
}
|
|
108
210
|
|
|
109
|
-
|
|
110
|
-
|
|
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
|
+
}
|
|
111
216
|
}
|
|
112
217
|
|
|
113
|
-
return $
|
|
218
|
+
return $result
|
|
114
219
|
}
|
|
115
220
|
|
|
116
221
|
if (-not (Test-Path -LiteralPath $ProjectDir)) {
|
|
117
222
|
Fail "No existe el directorio de proyecto: $ProjectDir"
|
|
118
223
|
}
|
|
119
224
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (-not (Test-Path -LiteralPath $mcpFile)) {
|
|
123
|
-
Fail "Prerequisito faltante: MCP Notion no disponible. Debe existir $mcpFile con entrada notion antes de ejecutar init-project."
|
|
124
|
-
}
|
|
225
|
+
Require-Command "opencode"
|
|
125
226
|
|
|
126
|
-
|
|
127
|
-
|
|
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."
|
|
128
230
|
}
|
|
129
231
|
|
|
130
|
-
$script:NotionToken = Resolve-NotionToken -McpFile $mcpFile
|
|
131
|
-
|
|
132
232
|
$workspaceName = [Environment]::GetEnvironmentVariable("NOTION_WORKSPACE_NAME")
|
|
133
233
|
$parentPageId = [Environment]::GetEnvironmentVariable("NOTION_PARENT_PAGE_ID")
|
|
134
234
|
|
|
135
|
-
Write-Host "Iniciando bootstrap automatico de Notion..."
|
|
235
|
+
Write-Host "Iniciando bootstrap automatico de Notion via MCP..."
|
|
136
236
|
|
|
137
237
|
$rootTitle = $ProjectName
|
|
138
238
|
if (-not [string]::IsNullOrWhiteSpace($workspaceName)) {
|
|
@@ -140,79 +240,50 @@ if (-not [string]::IsNullOrWhiteSpace($workspaceName)) {
|
|
|
140
240
|
}
|
|
141
241
|
|
|
142
242
|
$parentMode = "workspace"
|
|
143
|
-
$parentValue = ""
|
|
144
|
-
|
|
145
243
|
if (-not [string]::IsNullOrWhiteSpace($parentPageId)) {
|
|
146
244
|
$parentMode = "page"
|
|
147
|
-
|
|
148
|
-
Write-Host "Modo parent Notion: page ($parentPageId)"
|
|
245
|
+
Write-Host "Modo parent Notion (opcional): page ($parentPageId)"
|
|
149
246
|
}
|
|
150
247
|
else {
|
|
151
|
-
Write-Host "Modo parent Notion: workspace
|
|
248
|
+
Write-Host "Modo parent Notion (opcional): workspace"
|
|
152
249
|
}
|
|
153
250
|
|
|
154
|
-
$
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
$phaseNames = @(
|
|
158
|
-
"01-business",
|
|
159
|
-
"02-proposal",
|
|
160
|
-
"03-design",
|
|
161
|
-
"04-management",
|
|
162
|
-
"05-development",
|
|
163
|
-
"06-deployment",
|
|
164
|
-
"07-production",
|
|
165
|
-
"99-common"
|
|
166
|
-
)
|
|
251
|
+
$instructionFile = [System.IO.Path]::GetTempFileName()
|
|
252
|
+
$eventsFile = [System.IO.Path]::GetTempFileName()
|
|
167
253
|
|
|
168
|
-
|
|
169
|
-
$
|
|
254
|
+
try {
|
|
255
|
+
New-InstructionFile -Path $instructionFile -RootTitle $rootTitle -ParentPageId $parentPageId
|
|
170
256
|
|
|
171
|
-
|
|
172
|
-
$
|
|
173
|
-
|
|
174
|
-
Write-Host "Pagina creada ($phase): $phaseId"
|
|
175
|
-
if ($phase -eq "99-common") {
|
|
176
|
-
$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."
|
|
177
260
|
}
|
|
178
|
-
}
|
|
179
261
|
|
|
180
|
-
|
|
181
|
-
Fail "No se pudo crear la pagina 99-common en Notion."
|
|
182
|
-
}
|
|
262
|
+
$mcpResult = Parse-OpenCodeEvents -EventsPath $eventsFile
|
|
183
263
|
|
|
184
|
-
$
|
|
185
|
-
"Projects",
|
|
186
|
-
"Phases",
|
|
187
|
-
"Deliverables",
|
|
188
|
-
"Backlog",
|
|
189
|
-
"Risks",
|
|
190
|
-
"Decisions",
|
|
191
|
-
"Incidents"
|
|
192
|
-
)
|
|
264
|
+
$outputPath = Join-Path $ProjectDir "99-common/notion-bootstrap.output.json"
|
|
193
265
|
|
|
194
|
-
$
|
|
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
|
+
}
|
|
195
276
|
|
|
196
|
-
|
|
197
|
-
$sectionId = New-NotionPage -ParentMode "page" -ParentValue $commonPageId -Title $section
|
|
198
|
-
$sectionMap[$section] = $sectionId
|
|
199
|
-
Write-Host "Seccion modelo MVP creada ($section): $sectionId"
|
|
200
|
-
}
|
|
277
|
+
$result | ConvertTo-Json -Depth 10 | Set-Content -Path $outputPath
|
|
201
278
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
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
|
+
}
|
|
213
289
|
}
|
|
214
|
-
|
|
215
|
-
$result | ConvertTo-Json -Depth 10 | Set-Content -Path $outputPath
|
|
216
|
-
|
|
217
|
-
Write-Host "Bootstrap Notion completado."
|
|
218
|
-
Write-Host "Salida guardada en: $outputPath"
|
|
@@ -34,27 +34,6 @@ pick_python() {
|
|
|
34
34
|
return 1
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
validate_mcp_config() {
|
|
38
|
-
local mcp_file="$HOME/.config/opencode/mcp-servers.json"
|
|
39
|
-
|
|
40
|
-
if [[ ! -f "$mcp_file" ]]; then
|
|
41
|
-
fail "Prerequisito faltante: MCP Notion no disponible. Debe existir $mcp_file con entrada 'notion' antes de ejecutar init-project."
|
|
42
|
-
fi
|
|
43
|
-
|
|
44
|
-
if ! grep -q '"notion"' "$mcp_file"; then
|
|
45
|
-
fail "Prerequisito faltante: MCP Notion no disponible. Agrega la entrada 'notion' en $mcp_file y vuelve a ejecutar init-project."
|
|
46
|
-
fi
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
require_env() {
|
|
50
|
-
local var_name="$1"
|
|
51
|
-
local var_value="${!var_name:-}"
|
|
52
|
-
|
|
53
|
-
if [[ -z "$var_value" ]]; then
|
|
54
|
-
fail "Variable requerida no definida: $var_name. Exporta $var_name y vuelve a ejecutar init-project."
|
|
55
|
-
fi
|
|
56
|
-
}
|
|
57
|
-
|
|
58
37
|
require_command() {
|
|
59
38
|
local command_name="$1"
|
|
60
39
|
if ! command -v "$command_name" >/dev/null 2>&1; then
|
|
@@ -62,124 +41,212 @@ require_command() {
|
|
|
62
41
|
fi
|
|
63
42
|
}
|
|
64
43
|
|
|
65
|
-
|
|
66
|
-
local
|
|
67
|
-
local token="${NOTION_TOKEN:-}"
|
|
68
|
-
local py_bin
|
|
44
|
+
resolve_mcp_config_file() {
|
|
45
|
+
local opencode_file="$HOME/.config/opencode/opencode.json"
|
|
69
46
|
|
|
70
|
-
if [[ -
|
|
71
|
-
printf '%s' "$
|
|
47
|
+
if [[ -f "$opencode_file" ]]; then
|
|
48
|
+
printf '%s' "$opencode_file"
|
|
72
49
|
return 0
|
|
73
50
|
fi
|
|
74
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
|
+
|
|
75
60
|
py_bin="$(pick_python || true)"
|
|
76
61
|
if [[ -n "$py_bin" ]]; then
|
|
77
|
-
|
|
62
|
+
has_notion="$($py_bin - "$mcp_file" <<'PY'
|
|
78
63
|
import json
|
|
79
|
-
import os
|
|
80
|
-
import re
|
|
81
64
|
import sys
|
|
82
65
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
value = ""
|
|
87
|
-
servers = data.get("servers", {})
|
|
88
|
-
notion = servers.get("notion", {}) if isinstance(servers, dict) else {}
|
|
89
|
-
env = notion.get("env", {}) if isinstance(notion, dict) else {}
|
|
90
|
-
|
|
91
|
-
if isinstance(env, dict):
|
|
92
|
-
raw = env.get("NOTION_TOKEN", "")
|
|
93
|
-
if isinstance(raw, str):
|
|
94
|
-
match = re.fullmatch(r"\$\{([^}]+)\}", raw.strip())
|
|
95
|
-
if match:
|
|
96
|
-
value = os.environ.get(match.group(1), "")
|
|
97
|
-
else:
|
|
98
|
-
value = raw
|
|
99
|
-
|
|
100
|
-
print(value)
|
|
101
|
-
PY
|
|
102
|
-
)"
|
|
103
|
-
fi
|
|
66
|
+
def has_enabled_notion(data):
|
|
67
|
+
if not isinstance(data, dict):
|
|
68
|
+
return False
|
|
104
69
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
108
76
|
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
111
82
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
116
88
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
with open(sys.argv[1], "r", encoding="utf-8") as f:
|
|
92
|
+
raw = json.load(f)
|
|
93
|
+
|
|
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)"
|
|
122
99
|
fi
|
|
123
100
|
|
|
124
|
-
if [[ "$
|
|
125
|
-
|
|
126
|
-
"$(json_escape "$title")"
|
|
127
|
-
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."
|
|
128
103
|
fi
|
|
104
|
+
}
|
|
129
105
|
|
|
130
|
-
|
|
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
|
|
131
171
|
}
|
|
132
172
|
|
|
133
|
-
|
|
134
|
-
local
|
|
135
|
-
local parent_value="$2"
|
|
136
|
-
local title="$3"
|
|
137
|
-
local payload
|
|
138
|
-
local response_file
|
|
139
|
-
local status
|
|
173
|
+
extract_bootstrap_result() {
|
|
174
|
+
local events_file="$1"
|
|
140
175
|
local py_bin
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
status="$(curl -sS -o "$response_file" -w "%{http_code}" \
|
|
147
|
-
-X POST "https://api.notion.com/v1/pages" \
|
|
148
|
-
-H "Authorization: Bearer $NOTION_AUTH_TOKEN" \
|
|
149
|
-
-H "Notion-Version: 2022-06-28" \
|
|
150
|
-
-H "Content-Type: application/json" \
|
|
151
|
-
--data "$payload")"
|
|
152
|
-
|
|
153
|
-
if [[ "$status" -lt 200 || "$status" -ge 300 ]]; then
|
|
154
|
-
local error_body
|
|
155
|
-
error_body="$(tr -d '\n' < "$response_file")"
|
|
156
|
-
rm -f "$response_file"
|
|
157
|
-
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."
|
|
158
180
|
fi
|
|
159
181
|
|
|
160
|
-
py_bin
|
|
161
|
-
if [[ -n "$py_bin" ]]; then
|
|
162
|
-
page_id="$("$py_bin" - "$response_file" <<'PY'
|
|
182
|
+
"$py_bin" - "$events_file" <<'PY'
|
|
163
183
|
import json
|
|
184
|
+
import re
|
|
164
185
|
import sys
|
|
165
186
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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))
|
|
170
249
|
PY
|
|
171
|
-
)"
|
|
172
|
-
else
|
|
173
|
-
page_id="$(grep -o '"id":"[^"]*"' "$response_file" | head -n 1 | cut -d '"' -f 4)"
|
|
174
|
-
fi
|
|
175
|
-
|
|
176
|
-
rm -f "$response_file"
|
|
177
|
-
|
|
178
|
-
if [[ -z "$page_id" ]]; then
|
|
179
|
-
fail "No se pudo extraer el id de la pagina creada para '$title'."
|
|
180
|
-
fi
|
|
181
|
-
|
|
182
|
-
printf '%s' "$page_id"
|
|
183
250
|
}
|
|
184
251
|
|
|
185
252
|
PROJECT_NAME=""
|
|
@@ -214,14 +281,14 @@ if [[ ! -d "$PROJECT_DIR" ]]; then
|
|
|
214
281
|
fail "No existe el directorio de proyecto: $PROJECT_DIR"
|
|
215
282
|
fi
|
|
216
283
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
284
|
+
require_command "opencode"
|
|
285
|
+
mcp_config_file="$(resolve_mcp_config_file)"
|
|
286
|
+
validate_mcp_config "$mcp_config_file"
|
|
220
287
|
|
|
221
288
|
NOTION_WORKSPACE_NAME="${NOTION_WORKSPACE_NAME:-}"
|
|
222
289
|
NOTION_PARENT_PAGE_ID="${NOTION_PARENT_PAGE_ID:-}"
|
|
223
290
|
|
|
224
|
-
echo "Iniciando bootstrap automatico de Notion..."
|
|
291
|
+
echo "Iniciando bootstrap automatico de Notion via MCP..."
|
|
225
292
|
|
|
226
293
|
root_title="$PROJECT_NAME"
|
|
227
294
|
if [[ -n "$NOTION_WORKSPACE_NAME" ]]; then
|
|
@@ -229,102 +296,61 @@ if [[ -n "$NOTION_WORKSPACE_NAME" ]]; then
|
|
|
229
296
|
fi
|
|
230
297
|
|
|
231
298
|
parent_mode="workspace"
|
|
232
|
-
parent_value=""
|
|
233
|
-
|
|
234
299
|
if [[ -n "$NOTION_PARENT_PAGE_ID" ]]; then
|
|
235
300
|
parent_mode="page"
|
|
236
|
-
|
|
237
|
-
echo "Modo parent Notion: page ($NOTION_PARENT_PAGE_ID)"
|
|
301
|
+
echo "Modo parent Notion (opcional): page ($NOTION_PARENT_PAGE_ID)"
|
|
238
302
|
else
|
|
239
|
-
echo "Modo parent Notion: workspace
|
|
303
|
+
echo "Modo parent Notion (opcional): workspace"
|
|
240
304
|
fi
|
|
241
305
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
phase_names=(
|
|
246
|
-
"01-business"
|
|
247
|
-
"02-proposal"
|
|
248
|
-
"03-design"
|
|
249
|
-
"04-management"
|
|
250
|
-
"05-development"
|
|
251
|
-
"06-deployment"
|
|
252
|
-
"07-production"
|
|
253
|
-
"99-common"
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
phase_ids=()
|
|
257
|
-
common_page_id=""
|
|
258
|
-
|
|
259
|
-
for phase in "${phase_names[@]}"; do
|
|
260
|
-
phase_id="$(create_notion_page "page" "$project_root_page_id" "$phase")"
|
|
261
|
-
phase_ids+=("$phase_id")
|
|
262
|
-
echo "Pagina creada ($phase): $phase_id"
|
|
263
|
-
if [[ "$phase" == "99-common" ]]; then
|
|
264
|
-
common_page_id="$phase_id"
|
|
265
|
-
fi
|
|
266
|
-
done
|
|
306
|
+
instruction_file="$(mktemp)"
|
|
307
|
+
events_file="$(mktemp)"
|
|
308
|
+
trap 'rm -f "$instruction_file" "$events_file"' EXIT
|
|
267
309
|
|
|
268
|
-
|
|
269
|
-
|
|
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."
|
|
270
315
|
fi
|
|
271
316
|
|
|
272
|
-
|
|
273
|
-
"Projects"
|
|
274
|
-
"Phases"
|
|
275
|
-
"Deliverables"
|
|
276
|
-
"Backlog"
|
|
277
|
-
"Risks"
|
|
278
|
-
"Decisions"
|
|
279
|
-
"Incidents"
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
section_ids=()
|
|
283
|
-
for section in "${section_names[@]}"; do
|
|
284
|
-
section_id="$(create_notion_page "page" "$common_page_id" "$section")"
|
|
285
|
-
section_ids+=("$section_id")
|
|
286
|
-
echo "Seccion modelo MVP creada ($section): $section_id"
|
|
287
|
-
done
|
|
317
|
+
mcp_result_json="$(extract_bootstrap_result "$events_file")"
|
|
288
318
|
|
|
289
319
|
output_file="$PROJECT_DIR/99-common/notion-bootstrap.output.json"
|
|
290
320
|
timestamp_utc="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
291
321
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
echo "}"
|
|
327
|
-
} > "$output_file"
|
|
328
|
-
|
|
329
|
-
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."
|
|
330
356
|
echo "Salida guardada en: $output_file"
|