xonecode 0.6.0 → 0.8.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.
Files changed (128) hide show
  1. package/apps/web/dist/assets/{index-rqecK6s2.css → index-Boak51UP.css} +1 -1
  2. package/apps/web/dist/assets/index-Cg28x6nG.js +477 -0
  3. package/apps/web/dist/assets/{php-BBij3Lhc.js → php-Cu6F_QCJ.js} +1 -1
  4. package/apps/web/dist/assets/{ruby-Dq2YEaLo.js → ruby-BiqQPoC6.js} +1 -1
  5. package/apps/web/dist/index.html +2 -2
  6. package/dist/agent/cloudstudio/cloudstudioClient.js +10 -0
  7. package/dist/agent/cloudstudio/cloudstudioClient.js.map +1 -1
  8. package/dist/agent/cloudstudio/descarga.js +14 -4
  9. package/dist/agent/cloudstudio/descarga.js.map +1 -1
  10. package/dist/agent/cloudstudio/ramaActiva.js +11 -0
  11. package/dist/agent/cloudstudio/ramaActiva.js.map +1 -0
  12. package/dist/agent/cloudstudio/subida.js +13 -4
  13. package/dist/agent/cloudstudio/subida.js.map +1 -1
  14. package/dist/agent/config/catalogoModelos.js +45 -0
  15. package/dist/agent/config/catalogoModelos.js.map +1 -1
  16. package/dist/agent/config/modelos.js +103 -12
  17. package/dist/agent/config/modelos.js.map +1 -1
  18. package/dist/agent/config/mudanzaEnDisco.js +121 -0
  19. package/dist/agent/config/mudanzaEnDisco.js.map +1 -0
  20. package/dist/agent/config/selectorEnMaquina.js +45 -0
  21. package/dist/agent/config/selectorEnMaquina.js.map +1 -0
  22. package/dist/agent/config/settingsEnDisco.js +35 -0
  23. package/dist/agent/config/settingsEnDisco.js.map +1 -1
  24. package/dist/agent/dispositivos/juezVisual.js +152 -0
  25. package/dist/agent/dispositivos/juezVisual.js.map +1 -0
  26. package/dist/agent/dispositivos/sensorDeArranque.js +154 -0
  27. package/dist/agent/dispositivos/sensorDeArranque.js.map +1 -0
  28. package/dist/agent/exportarPdf.js +140 -0
  29. package/dist/agent/exportarPdf.js.map +1 -0
  30. package/dist/agent/grafo/criticaVisual.js +115 -0
  31. package/dist/agent/grafo/criticaVisual.js.map +1 -0
  32. package/dist/agent/grafo/navegacionXone.js +37 -4
  33. package/dist/agent/grafo/navegacionXone.js.map +1 -1
  34. package/dist/agent/grafo/perfiles.js +52 -9
  35. package/dist/agent/grafo/perfiles.js.map +1 -1
  36. package/dist/agent/grafo/proyecto.js +74 -7
  37. package/dist/agent/grafo/proyecto.js.map +1 -1
  38. package/dist/agent/grafo/skills.js +32 -0
  39. package/dist/agent/grafo/skills.js.map +1 -1
  40. package/dist/agent/grafo/xoneAgent.js +103 -13
  41. package/dist/agent/grafo/xoneAgent.js.map +1 -1
  42. package/dist/agent/navegacion/estilosEnDisco.js +110 -0
  43. package/dist/agent/navegacion/estilosEnDisco.js.map +1 -0
  44. package/dist/agent/navegacion/hechosEnDisco.js +26 -0
  45. package/dist/agent/navegacion/hechosEnDisco.js.map +1 -0
  46. package/dist/agent/navegacion/indiceEnDisco.js +15 -0
  47. package/dist/agent/navegacion/indiceEnDisco.js.map +1 -1
  48. package/dist/agent/subagentes/agentesEnDisco.js +85 -24
  49. package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
  50. package/dist/agent/subagentes/escrituraExterna.js +53 -0
  51. package/dist/agent/subagentes/escrituraExterna.js.map +1 -1
  52. package/dist/agent/subagentes/pluginDeSkills.js +99 -0
  53. package/dist/agent/subagentes/pluginDeSkills.js.map +1 -0
  54. package/dist/agent/subagentes/subagenteExterno.js +48 -0
  55. package/dist/agent/subagentes/subagenteExterno.js.map +1 -1
  56. package/dist/agent/subagentes/subagenteOpencode.js +53 -1
  57. package/dist/agent/subagentes/subagenteOpencode.js.map +1 -1
  58. package/dist/agent/tareas/juezDeTarea.js +1 -1
  59. package/dist/agent/tareas/juezDeTarea.js.map +1 -1
  60. package/dist/agent/tareas/tareasEnDisco.js +49 -0
  61. package/dist/agent/tareas/tareasEnDisco.js.map +1 -1
  62. package/dist/agent/turno/diagnosticoDeTools.js +5 -1
  63. package/dist/agent/turno/diagnosticoDeTools.js.map +1 -1
  64. package/dist/agent/turno/informeDeTraza.js +27 -4
  65. package/dist/agent/turno/informeDeTraza.js.map +1 -1
  66. package/dist/agent/turno/puente.js +24 -1
  67. package/dist/agent/turno/puente.js.map +1 -1
  68. package/dist/agent/turno/resumenDeContexto.js +88 -4
  69. package/dist/agent/turno/resumenDeContexto.js.map +1 -1
  70. package/dist/agent/turno/resumenDeTool.js +18 -0
  71. package/dist/agent/turno/resumenDeTool.js.map +1 -1
  72. package/dist/agent/turno/rubrica.js +91 -0
  73. package/dist/agent/turno/rubrica.js.map +1 -0
  74. package/dist/agent/turno/turnoReal.js +172 -11
  75. package/dist/agent/turno/turnoReal.js.map +1 -1
  76. package/dist/cli/consola.js +63 -0
  77. package/dist/cli/consola.js.map +1 -1
  78. package/dist/cli/main.js +51 -4
  79. package/dist/cli/main.js.map +1 -1
  80. package/dist/cli/run.js +8 -0
  81. package/dist/cli/run.js.map +1 -1
  82. package/dist/core/agentes.js +111 -9
  83. package/dist/core/agentes.js.map +1 -1
  84. package/dist/core/esfuerzo.js +141 -0
  85. package/dist/core/esfuerzo.js.map +1 -0
  86. package/dist/core/estilos.js +78 -0
  87. package/dist/core/estilos.js.map +1 -0
  88. package/dist/core/exportacion.js +140 -0
  89. package/dist/core/exportacion.js.map +1 -0
  90. package/dist/core/hechosDelProyecto.js +87 -0
  91. package/dist/core/hechosDelProyecto.js.map +1 -0
  92. package/dist/core/logDeDispositivo.js +143 -0
  93. package/dist/core/logDeDispositivo.js.map +1 -0
  94. package/dist/core/modelos.js +32 -0
  95. package/dist/core/modelos.js.map +1 -1
  96. package/dist/core/mudanzaDeWorkspace.js +93 -0
  97. package/dist/core/mudanzaDeWorkspace.js.map +1 -0
  98. package/dist/core/planes.js +70 -0
  99. package/dist/core/planes.js.map +1 -0
  100. package/dist/core/ports.js +4 -0
  101. package/dist/core/ports.js.map +1 -1
  102. package/dist/core/rubrica.js +90 -0
  103. package/dist/core/rubrica.js.map +1 -0
  104. package/dist/core/selectorDeCarpeta.js +87 -0
  105. package/dist/core/selectorDeCarpeta.js.map +1 -0
  106. package/dist/core/settings.js +54 -5
  107. package/dist/core/settings.js.map +1 -1
  108. package/dist/core/shellDeAgente.js +45 -0
  109. package/dist/core/shellDeAgente.js.map +1 -1
  110. package/dist/core/topeAgotado.js +69 -0
  111. package/dist/core/topeAgotado.js.map +1 -0
  112. package/dist/web/servidor/arranque.js +324 -9
  113. package/dist/web/servidor/arranque.js.map +1 -1
  114. package/dist/web/servidor/sesiones.js +30 -2
  115. package/dist/web/servidor/sesiones.js.map +1 -1
  116. package/dist/web/servidor/transporte.js.map +1 -1
  117. package/dist/web/servidor/vestibulo.js +55 -6
  118. package/dist/web/servidor/vestibulo.js.map +1 -1
  119. package/package.json +2 -1
  120. package/skills/xone-hotswap/SKILL.md +31 -9
  121. package/skills/xone-hotswap/scripts/xone-arrancar-android +202 -0
  122. package/skills/xone-hotswap/scripts/xone-captura-android +93 -0
  123. package/skills/xone-hotswap/scripts/xone-desplegar-android +28 -0
  124. package/skills/xone-hotswap/scripts/xone-hotswap +39 -9
  125. package/skills/xone-hotswap/scripts/xone-reiniciar-android +28 -0
  126. package/skills/xone-plan-builder/SKILL.md +22 -4
  127. package/skills/xone-spec-builder/SKILL.md +37 -7
  128. package/apps/web/dist/assets/index-DBZTodio.js +0 -477
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Una captura de la pantalla del aparato, POR LA VÍA NATIVA, a la carpeta de artefactos.
4
+ *
5
+ * POR QUÉ EXISTE, si el canal ya sabe capturar. `xone-hotswap '{"command":"getScreenshot"}'`
6
+ * es la vía buena —la pide la app host, así que sale lo que la app pinta— pero necesita el
7
+ * túnel y la app viva. Cuando eso no está, lo único que queda es `adb exec-out screencap`, y
8
+ * medido: el modelo la compone él y redirige la salida a un fichero, que en el `cwd` de la
9
+ * shell es LA RAÍZ DEL PROYECTO. Así apareció un `screenshot_login.png` de 773 KB entre los
10
+ * `.xne` del cliente, camino de la aprobación, de git y de CloudStudio.
11
+ *
12
+ * Un script no es una barrera —una shell alcanza el disco entero y eso no se arregla con
13
+ * prompts—, pero le quita el motivo: pedir la captura por su nombre cuesta menos que
14
+ * componer el `adb` y elegir dónde dejarla, y ya cae donde tiene que caer.
15
+ *
16
+ * LO QUE NO HACE: no imprime la imagen. Un PNG de pantalla completa son cientos de miles de
17
+ * caracteres en base64, y meterlos en el contexto es pagarlos sin que nadie pueda mirarlos.
18
+ * Sale el NOMBRE, que es con lo que la interfaz la enseña.
19
+ *
20
+ * USO:
21
+ * xone-captura-android captura a $XONECODE_ARTEFACTOS
22
+ * xone-captura-android --nombre login.png con nombre propio
23
+ * xone-captura-android --serie emulator-5554 con varios aparatos conectados
24
+ */
25
+ import { execFileSync } from "node:child_process";
26
+ import { writeFileSync } from "node:fs";
27
+ import { join, basename } from "node:path";
28
+
29
+ const args = process.argv.slice(2);
30
+ if (args.includes("--help") || args.includes("-h")) {
31
+ console.error(`xone-captura-android — captura la pantalla del aparato con \`adb\` y la deja en artefactos.
32
+
33
+ xone-captura-android captura a $XONECODE_ARTEFACTOS
34
+ xone-captura-android --nombre login.png con nombre propio
35
+ xone-captura-android --serie <id> con varios aparatos conectados
36
+
37
+ Es la vía de RESPALDO: si el túnel y la app host están vivos, la buena es
38
+ \`xone-hotswap '{"command":"getScreenshot"}'\`, que captura lo que la app pinta.
39
+ Nunca redirijas \`adb exec-out screencap\` a un fichero: el \`cwd\` es la raíz del
40
+ proyecto del usuario y ahí no se escribe.`);
41
+ process.exit(0);
42
+ }
43
+
44
+ const ARTEFACTOS = process.env.XONECODE_ARTEFACTOS;
45
+ if (ARTEFACTOS === undefined) {
46
+ // Sin sitio donde dejarla no se inventa uno: escribir en el `cwd` es exactamente el fallo
47
+ // que este script existe para no cometer.
48
+ console.error("no hay XONECODE_ARTEFACTOS: esta sesión no tiene carpeta de artefactos y la captura no se puede guardar.");
49
+ process.exit(1);
50
+ }
51
+
52
+ const opcion = (nombre) => {
53
+ const i = args.indexOf(`--${nombre}`);
54
+ return i >= 0 ? args[i + 1] : undefined;
55
+ };
56
+ const serie = opcion("serie");
57
+
58
+ // `basename` y no la cadena tal cual: el nombre lo elige el modelo, y un `../` aquí escribiría
59
+ // fuera de la carpeta de la sesión. Misma regla que `esRutaDeArtefacto` en el host.
60
+ const pedido = opcion("nombre");
61
+ const nombre = pedido === undefined ? `captura-${Date.now()}.png` : basename(pedido);
62
+ if (nombre === "" || nombre === "." || nombre === "..") {
63
+ console.error("ese nombre no vale para un fichero.");
64
+ process.exit(1);
65
+ }
66
+
67
+ let bytes;
68
+ try {
69
+ // `exec-out` y no `shell`: `shell` traduce los finales de línea y corrompe el PNG.
70
+ bytes = execFileSync("adb", [...(serie ? ["-s", serie] : []), "exec-out", "screencap", "-p"], {
71
+ maxBuffer: 256 * 1024 * 1024,
72
+ });
73
+ } catch (error) {
74
+ console.error(`no se pudo capturar con adb: ${error.message}`);
75
+ process.exit(1);
76
+ }
77
+
78
+ // Una captura vacía no es una captura: `screencap` puede salir con 0 y no devolver nada si el
79
+ // aparato está bloqueado. Decirlo es mejor que dejar un fichero de 0 bytes con buena pinta.
80
+ if (bytes.length === 0) {
81
+ console.error("adb devolvió una captura VACÍA: ¿está el aparato despierto y desbloqueado?");
82
+ process.exit(1);
83
+ }
84
+
85
+ const destino = join(ARTEFACTOS, nombre);
86
+ try {
87
+ writeFileSync(destino, bytes);
88
+ } catch (error) {
89
+ console.error(`no se pudo guardar la captura: ${error.message}`);
90
+ process.exit(1);
91
+ }
92
+
93
+ console.log(`captura guardada como ${nombre} (${bytes.length} bytes). Es de la vía nativa: enseña lo que el SISTEMA pinta, no lo que la app host dice.`);
@@ -278,6 +278,34 @@ function excepcionDeXOne() {
278
278
  const app = nombreDeLaApp();
279
279
 
280
280
  {
281
+ /**
282
+ * Sin aparato NO se empieza, y se dice el siguiente paso HECHO.
283
+ *
284
+ * Antes el primer `adb forward` moría con «no devices/emulators found», que es cierto y no
285
+ * dice qué hacer: medido, el modelo se ponía a componer el arranque a mano —y `emulator` no
286
+ * está en el PATH—. Es la regla de `porQueNo`: cuando sabemos la salida, se escribe, no se
287
+ * aconseja.
288
+ */
289
+ function exigirAparato() {
290
+ let lista = "";
291
+ try {
292
+ lista = execFileSync("adb", ["devices"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
293
+ } catch (error) {
294
+ console.error(`no pude hablar con adb: ${error.message.split("\n")[0]}`);
295
+ process.exit(1);
296
+ }
297
+ const hay = lista
298
+ .split(/\r?\n/)
299
+ .slice(1)
300
+ .some((l) => /\sdevice$/.test(l.trim()));
301
+ if (!hay) {
302
+ console.error("no hay ningún aparato conectado. Arranca el emulador primero:");
303
+ console.error(" xone-arrancar-android");
304
+ process.exit(1);
305
+ }
306
+ }
307
+
308
+ exigirAparato();
281
309
  console.log(`1/5 túnel: ${PUERTO}`);
282
310
  adb("forward", `tcp:${PUERTO}`, `tcp:${PUERTO}`);
283
311
 
@@ -56,8 +56,14 @@ import { join } from "node:path";
56
56
  const URL_POR_OMISION = "wss://127.0.0.1:8443/hotswap";
57
57
  const TOPE_MS = Number(process.env.HOTSWAP_TOPE_MS ?? 40_000);
58
58
  const ARTEFACTOS = process.env.XONECODE_ARTEFACTOS;
59
- /** A partir de aquí se da por binario y no se imprime. */
60
- const LARGO_DE_BINARIO = 2_000;
59
+ /**
60
+ * A partir de aquí una respuesta de TEXTO se guarda en vez de imprimirse.
61
+ *
62
+ * No decide si algo es binario —eso lo dice `extensionDe` mirando los bytes—, solo si cabe en
63
+ * la salida de un comando. El árbol de controles de una pantalla llena son varios miles de
64
+ * caracteres y volcarlos enteros en el contexto del agente cuesta más que la pregunta.
65
+ */
66
+ const LARGO_PARA_GUARDAR = 2_000;
61
67
 
62
68
  const pedidos = process.argv.slice(2);
63
69
  if (pedidos.length === 0 || pedidos.includes("--help") || pedidos.includes("-h")) {
@@ -90,12 +96,24 @@ function salir(motivo) {
90
96
  process.exit(1);
91
97
  }
92
98
 
93
- /** Extensión por los primeros bytes en base64, sin adivinar por el nombre del comando. */
94
- function extensionDe(base64) {
99
+ /**
100
+ * Qué es esto, por sus primeros bytes en base64 — nunca por el nombre del comando.
101
+ *
102
+ * **`undefined` significa «no es una imagen», y eso NO es «es binario desconocido».** Era el
103
+ * bug: cualquier campo de más de 2.000 caracteres se daba por base64 y se pasaba por
104
+ * `Buffer.from(valor, "base64")`. El árbol de `getAllElements` es JSON de TEXTO, así que en
105
+ * cuanto una pantalla tiene bastantes controles pasaba de ese largo, se «decodificaba»
106
+ * —descartando `{`, `"`, `:` y los espacios, que no son del alfabeto base64— y lo que quedaba
107
+ * en el disco eran unos 3/4 de bytes de basura, con nombre `captura-….bin`. Comprobado contra
108
+ * el aparato: el fichero real empezaba por `9da9 9e30 03ff` y un árbol JSON pasado por ese
109
+ * mismo `Buffer.from` empieza exactamente igual. O sea que el agente perdía la pantalla justo
110
+ * cuando la pantalla tenía algo que contar, y encima creía haber guardado una captura.
111
+ */
112
+ function imagenDe(base64) {
95
113
  if (base64.startsWith("/9j/")) return "jpg";
96
114
  if (base64.startsWith("iVBORw0KGgo")) return "png";
97
115
  if (base64.startsWith("UklGR")) return "webp";
98
- return "bin";
116
+ return undefined;
99
117
  }
100
118
 
101
119
  /**
@@ -108,13 +126,25 @@ function extensionDe(base64) {
108
126
  function sinBinarios(respuesta) {
109
127
  const copia = { ...respuesta };
110
128
  for (const [clave, valor] of Object.entries(copia)) {
111
- if (typeof valor !== "string" || valor.length < LARGO_DE_BINARIO) continue;
129
+ if (typeof valor !== "string" || valor.length < LARGO_PARA_GUARDAR) continue;
130
+ const imagen = imagenDe(valor);
112
131
  if (ARTEFACTOS === undefined) {
113
- copia[clave] = `<${valor.length} car. de binario; sin XONECODE_ARTEFACTOS no hay dónde guardarlo>`;
132
+ copia[clave] =
133
+ imagen === undefined
134
+ ? `<${valor.length} caracteres; sin XONECODE_ARTEFACTOS no hay dónde guardarlos>`
135
+ : `<imagen ${imagen} de ${valor.length} car.; sin XONECODE_ARTEFACTOS no hay dónde guardarla>`;
114
136
  continue;
115
137
  }
116
- const nombre = `captura-${Date.now()}.${extensionDe(valor)}`;
117
- const bytes = Buffer.from(valor, "base64");
138
+ /**
139
+ * Una imagen se DECODIFICA y se llama captura; lo demás es TEXTO y se guarda tal cual,
140
+ * con su extensión y con un nombre que dice lo que es. Llamar `captura-` a un árbol de
141
+ * controles no era solo feo: mandaba a quien lo leyera a buscar una foto.
142
+ */
143
+ const nombre =
144
+ imagen === undefined
145
+ ? `respuesta-${clave}-${Date.now()}.${valor.trimStart().startsWith("[") || valor.trimStart().startsWith("{") ? "json" : "txt"}`
146
+ : `captura-${Date.now()}.${imagen}`;
147
+ const bytes = imagen === undefined ? Buffer.from(valor, "utf8") : Buffer.from(valor, "base64");
118
148
  writeFileSync(join(ARTEFACTOS, nombre), bytes);
119
149
  copia[clave] = `<guardado como ${nombre}, ${bytes.length} bytes>`;
120
150
  }
@@ -51,6 +51,34 @@ const cliente = join(import.meta.dirname, "xone-hotswap");
51
51
  const mandar = (comando) =>
52
52
  execFileSync("node", [cliente, JSON.stringify(comando)], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
53
53
 
54
+ /**
55
+ * Sin aparato NO se empieza, y se dice el siguiente paso HECHO.
56
+ *
57
+ * Antes el primer `adb forward` moría con «no devices/emulators found», que es cierto y no
58
+ * dice qué hacer: medido, el modelo se ponía a componer el arranque a mano —y `emulator` no
59
+ * está en el PATH—. Es la regla de `porQueNo`: cuando sabemos la salida, se escribe, no se
60
+ * aconseja.
61
+ */
62
+ function exigirAparato() {
63
+ let lista = "";
64
+ try {
65
+ lista = execFileSync("adb", ["devices"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
66
+ } catch (error) {
67
+ console.error(`no pude hablar con adb: ${error.message.split("\n")[0]}`);
68
+ process.exit(1);
69
+ }
70
+ const hay = lista
71
+ .split(/\r?\n/)
72
+ .slice(1)
73
+ .some((l) => /\sdevice$/.test(l.trim()));
74
+ if (!hay) {
75
+ console.error("no hay ningún aparato conectado. Arranca el emulador primero:");
76
+ console.error(" xone-arrancar-android");
77
+ process.exit(1);
78
+ }
79
+ }
80
+
81
+ exigirAparato();
54
82
  console.log(`1/3 túnel y parada de ${PAQUETE}`);
55
83
  // El túnel PRIMERO y sin quitarlo después: hay que reaplicarlo tras cada reconexión del cable,
56
84
  // así que quitarlo dejaría al siguiente hablando con nadie.
@@ -22,17 +22,33 @@ xone-review → valida con xone-simulator
22
22
 
23
23
  ## Estructura de archivos
24
24
 
25
+ Todo vive en **la carpeta del plan**, `/planes/<nombre>/`, NUNCA en la raíz del proyecto: un plan
26
+ no es parte de la app del cliente, y lo que se escribe en la raíz entra en el commit del turno y
27
+ sube a CloudStudio. Escribe tu `TASKS.md` **en la misma carpeta de la que leíste el `PLAN.md`**,
28
+ no en otra: quien desarrolle va a buscarlo ahí. Y el nombre es **EXACTO** — nada de
29
+ `tareas.md` ni variantes: dos ficheros con las mismas tareas son dos fuentes de verdad, y la
30
+ que nadie marca se queda vieja.
31
+
25
32
  ```
26
- <raíz del proyecto>/
33
+ /planes/favoritos/
27
34
  ├── PLAN.md ← entrada (producido por xone-spec-builder)
28
- ├── TASKS.md ← salida (esta skill)
35
+ ├── TASKS.md ← salida (esta skill)
29
36
  ├── CONTEXT.md ← glosario (ya existe del spec-builder)
30
- ├── docs/
31
- │ └── adr/
37
+ └── adr/
32
38
  ```
33
39
 
34
40
  Si no existe `PLAN.md`, **detente y pide al usuario que ejecute `xone-spec-builder` primero**. No se puede descomponer lo que no está especificado.
35
41
 
42
+ ## El `TASKS.md` se ACTUALIZA, no se archiva
43
+
44
+ `**Estado:**` es `pendiente`, `en curso` o `hecha`, y lo mantiene **quien ejecuta la tarea**, en
45
+ este mismo fichero y en esta misma carpeta. Las casillas de los criterios se marcan `- [x]`
46
+ cuando ese criterio está comprobado, no cuando se escribió el código que debería cumplirlo.
47
+
48
+ Ésa es la razón de que el plan viva en `/planes/` y no entre los artefactos: no es un documento
49
+ que se entrega una vez, es el estado compartido entre quien planifica y quien desarrolla, a lo
50
+ largo de varios turnos y varias sesiones.
51
+
36
52
  ## Precondiciones
37
53
 
38
54
  Antes de descomponer, valida el `PLAN.md`:
@@ -133,6 +149,7 @@ Ver [TASKS-FORMAT.md](references/TASKS-FORMAT.md) para la plantilla completa. Re
133
149
  ## Tareas
134
150
 
135
151
  ### 01 — {Título}
152
+ **Estado:** pendiente
136
153
  **Qué entrega:** {comportamiento end-to-end, perspectiva del usuario — no lista de capas}
137
154
  **Bloqueada por:** Ninguna — puede empezar ya
138
155
  **Notas:** {si hay algo que el spec dejó pendiente que afecta a esta tarea}
@@ -141,6 +158,7 @@ Ver [TASKS-FORMAT.md](references/TASKS-FORMAT.md) para la plantilla completa. Re
141
158
  - [ ] Criterio de aceptación 2
142
159
 
143
160
  ### 02 — {Título}
161
+ **Estado:** pendiente
144
162
  **Qué entrega:** …
145
163
  **Bloqueada por:** 01
146
164
  …
@@ -24,22 +24,52 @@ El spec builder es el primer paso de cualquier trabajo XOne. Lo que sigue:
24
24
 
25
25
  ## Estructura de archivos
26
26
 
27
- La entrevista escribe en el **directorio de trabajo** del usuario — la raíz del proyecto XOne (existente o por crear). Crea los archivos **lazy**, solo cuando hay algo que escribir:
27
+ La entrevista escribe en **`/planes/<nombre>/`**, NUNCA en la raíz del proyecto. Un plan no es
28
+ parte de la app: la raíz es el proyecto XOne del cliente, y lo que se escribe ahí pasa por
29
+ aprobación, entra en el commit de cada turno y **sube a CloudStudio**. `/planes/` no: no entra en
30
+ git, no se sincroniza, y **sobrevive a la sesión** — se planifica un día y se implementa otro.
31
+
32
+ **Elige tú el `<nombre>`**, y es una carpeta por plan porque puede haber varios a la vez:
33
+ minúsculas, dígitos y guiones sencillos (`favoritos`, `sync-offline`), corto y por lo que el
34
+ desarrollo ES. Mira antes `/planes/` por si ya existe uno del mismo tema: si lo hay, AMPLÍALO en
35
+ vez de abrir otro. Crea los archivos **lazy**, solo cuando hay algo que escribir.
36
+
37
+ **Los nombres son EXACTOS**: `PLAN.md`, `CONTEXT.md` y la carpeta `adr/`. No crees
38
+ traducciones ni variantes (`spec.md`, `glosario.md`, `plan.md`): quien lea el plan después
39
+ busca esos nombres, y dos ficheros con lo mismo dentro son dos fuentes de verdad — la que
40
+ nadie actualiza se queda vieja sin que nadie lo note.
28
41
 
29
42
  ```
30
- <raíz del proyecto>/
43
+ /planes/favoritos/
31
44
  ├── PLAN.md ← el plan del desarrollo (entregable)
32
45
  ├── CONTEXT.md ← glosario del dominio (términos canónicos, evitar)
33
- ├── docs/
34
- │ └── adr/
35
- │ ├── 0001-sqlite-local-vs-replica.md
36
- │ └── 0002-login-con-oauth2-o-contra-db.md
46
+ └── adr/
47
+ ├── 0001-sqlite-local-vs-replica.md
48
+ └── 0002-login-con-oauth2-o-contra-db.md
37
49
  ```
38
50
 
39
- Si no existe `PLAN.md`, créalo cuando la entrevista empiece a cristalizar decisiones. Si no existe `CONTEXT.md`, créalo cuando se resuelva el primer término de dominio. Si no existe `docs/adr/`, créalo cuando se tome la primera decisión digna de ADR.
51
+ Si no existe `PLAN.md`, créalo cuando la entrevista empiece a cristalizar decisiones. Si no existe `CONTEXT.md`, créalo cuando se resuelva el primer término de dominio. Si no existe `adr/` dentro de la carpeta del plan, créalo cuando se tome la primera decisión digna de ADR.
40
52
 
41
53
  > **`PLAN.md` es el entregable.** `CONTEXT.md` y los ADRs lo acompañan y alimentan, pero lo que el usuario se lleva es el plan.
42
54
 
55
+ ## Si no hay nadie a quien entrevistar
56
+
57
+ Puede que corras **dentro de un subagente**, y entonces no tienes interlocutor: una delegación es
58
+ de un solo disparo y no hay forma de devolverle una pregunta a nadie a mitad de trabajo. Se nota
59
+ en que nadie contesta a la primera ronda.
60
+
61
+ Cuando eso pase, **no te inventes las respuestas**, que es justo lo que esta skill se prohíbe.
62
+ Haz esto:
63
+
64
+ 1. Escribe el `PLAN.md` con lo que SÍ puedes resolver leyendo el proyecto.
65
+ 2. Pon las decisiones que no puedes tomar en **§Pendientes**, cada una con las opciones y con
66
+ qué cambia según la respuesta. Una pregunta sin consecuencia no merece bloquear nada.
67
+ 3. **Dilo en tu respuesta final**, nombrando las pendientes que bloquean. Quien lea eso sí puede
68
+ preguntárselo a la persona y volver a encargártelo con las respuestas dentro.
69
+
70
+ Un plan con tres huecos declarados vale; uno con tres huecos rellenados a ojo es peor que no
71
+ tenerlo, porque nadie sabe cuáles eran.
72
+
43
73
  ## Antes de entrevistar: triage de complejidad
44
74
 
45
75
  No toda petición necesita una entrevista de 8 rounds. Antes de empezar, clasifica la **complejidad** del desarrollo y ajusta el nivel de especificación: