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.
- package/apps/web/dist/assets/{index-rqecK6s2.css → index-Boak51UP.css} +1 -1
- package/apps/web/dist/assets/index-Cg28x6nG.js +477 -0
- package/apps/web/dist/assets/{php-BBij3Lhc.js → php-Cu6F_QCJ.js} +1 -1
- package/apps/web/dist/assets/{ruby-Dq2YEaLo.js → ruby-BiqQPoC6.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/dist/agent/cloudstudio/cloudstudioClient.js +10 -0
- package/dist/agent/cloudstudio/cloudstudioClient.js.map +1 -1
- package/dist/agent/cloudstudio/descarga.js +14 -4
- package/dist/agent/cloudstudio/descarga.js.map +1 -1
- package/dist/agent/cloudstudio/ramaActiva.js +11 -0
- package/dist/agent/cloudstudio/ramaActiva.js.map +1 -0
- package/dist/agent/cloudstudio/subida.js +13 -4
- package/dist/agent/cloudstudio/subida.js.map +1 -1
- package/dist/agent/config/catalogoModelos.js +45 -0
- package/dist/agent/config/catalogoModelos.js.map +1 -1
- package/dist/agent/config/modelos.js +103 -12
- package/dist/agent/config/modelos.js.map +1 -1
- package/dist/agent/config/mudanzaEnDisco.js +121 -0
- package/dist/agent/config/mudanzaEnDisco.js.map +1 -0
- package/dist/agent/config/selectorEnMaquina.js +45 -0
- package/dist/agent/config/selectorEnMaquina.js.map +1 -0
- package/dist/agent/config/settingsEnDisco.js +35 -0
- package/dist/agent/config/settingsEnDisco.js.map +1 -1
- package/dist/agent/dispositivos/juezVisual.js +152 -0
- package/dist/agent/dispositivos/juezVisual.js.map +1 -0
- package/dist/agent/dispositivos/sensorDeArranque.js +154 -0
- package/dist/agent/dispositivos/sensorDeArranque.js.map +1 -0
- package/dist/agent/exportarPdf.js +140 -0
- package/dist/agent/exportarPdf.js.map +1 -0
- package/dist/agent/grafo/criticaVisual.js +115 -0
- package/dist/agent/grafo/criticaVisual.js.map +1 -0
- package/dist/agent/grafo/navegacionXone.js +37 -4
- package/dist/agent/grafo/navegacionXone.js.map +1 -1
- package/dist/agent/grafo/perfiles.js +52 -9
- package/dist/agent/grafo/perfiles.js.map +1 -1
- package/dist/agent/grafo/proyecto.js +74 -7
- package/dist/agent/grafo/proyecto.js.map +1 -1
- package/dist/agent/grafo/skills.js +32 -0
- package/dist/agent/grafo/skills.js.map +1 -1
- package/dist/agent/grafo/xoneAgent.js +103 -13
- package/dist/agent/grafo/xoneAgent.js.map +1 -1
- package/dist/agent/navegacion/estilosEnDisco.js +110 -0
- package/dist/agent/navegacion/estilosEnDisco.js.map +1 -0
- package/dist/agent/navegacion/hechosEnDisco.js +26 -0
- package/dist/agent/navegacion/hechosEnDisco.js.map +1 -0
- package/dist/agent/navegacion/indiceEnDisco.js +15 -0
- package/dist/agent/navegacion/indiceEnDisco.js.map +1 -1
- package/dist/agent/subagentes/agentesEnDisco.js +85 -24
- package/dist/agent/subagentes/agentesEnDisco.js.map +1 -1
- package/dist/agent/subagentes/escrituraExterna.js +53 -0
- package/dist/agent/subagentes/escrituraExterna.js.map +1 -1
- package/dist/agent/subagentes/pluginDeSkills.js +99 -0
- package/dist/agent/subagentes/pluginDeSkills.js.map +1 -0
- package/dist/agent/subagentes/subagenteExterno.js +48 -0
- package/dist/agent/subagentes/subagenteExterno.js.map +1 -1
- package/dist/agent/subagentes/subagenteOpencode.js +53 -1
- package/dist/agent/subagentes/subagenteOpencode.js.map +1 -1
- package/dist/agent/tareas/juezDeTarea.js +1 -1
- package/dist/agent/tareas/juezDeTarea.js.map +1 -1
- package/dist/agent/tareas/tareasEnDisco.js +49 -0
- package/dist/agent/tareas/tareasEnDisco.js.map +1 -1
- package/dist/agent/turno/diagnosticoDeTools.js +5 -1
- package/dist/agent/turno/diagnosticoDeTools.js.map +1 -1
- package/dist/agent/turno/informeDeTraza.js +27 -4
- package/dist/agent/turno/informeDeTraza.js.map +1 -1
- package/dist/agent/turno/puente.js +24 -1
- package/dist/agent/turno/puente.js.map +1 -1
- package/dist/agent/turno/resumenDeContexto.js +88 -4
- package/dist/agent/turno/resumenDeContexto.js.map +1 -1
- package/dist/agent/turno/resumenDeTool.js +18 -0
- package/dist/agent/turno/resumenDeTool.js.map +1 -1
- package/dist/agent/turno/rubrica.js +91 -0
- package/dist/agent/turno/rubrica.js.map +1 -0
- package/dist/agent/turno/turnoReal.js +172 -11
- package/dist/agent/turno/turnoReal.js.map +1 -1
- package/dist/cli/consola.js +63 -0
- package/dist/cli/consola.js.map +1 -1
- package/dist/cli/main.js +51 -4
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/run.js +8 -0
- package/dist/cli/run.js.map +1 -1
- package/dist/core/agentes.js +111 -9
- package/dist/core/agentes.js.map +1 -1
- package/dist/core/esfuerzo.js +141 -0
- package/dist/core/esfuerzo.js.map +1 -0
- package/dist/core/estilos.js +78 -0
- package/dist/core/estilos.js.map +1 -0
- package/dist/core/exportacion.js +140 -0
- package/dist/core/exportacion.js.map +1 -0
- package/dist/core/hechosDelProyecto.js +87 -0
- package/dist/core/hechosDelProyecto.js.map +1 -0
- package/dist/core/logDeDispositivo.js +143 -0
- package/dist/core/logDeDispositivo.js.map +1 -0
- package/dist/core/modelos.js +32 -0
- package/dist/core/modelos.js.map +1 -1
- package/dist/core/mudanzaDeWorkspace.js +93 -0
- package/dist/core/mudanzaDeWorkspace.js.map +1 -0
- package/dist/core/planes.js +70 -0
- package/dist/core/planes.js.map +1 -0
- package/dist/core/ports.js +4 -0
- package/dist/core/ports.js.map +1 -1
- package/dist/core/rubrica.js +90 -0
- package/dist/core/rubrica.js.map +1 -0
- package/dist/core/selectorDeCarpeta.js +87 -0
- package/dist/core/selectorDeCarpeta.js.map +1 -0
- package/dist/core/settings.js +54 -5
- package/dist/core/settings.js.map +1 -1
- package/dist/core/shellDeAgente.js +45 -0
- package/dist/core/shellDeAgente.js.map +1 -1
- package/dist/core/topeAgotado.js +69 -0
- package/dist/core/topeAgotado.js.map +1 -0
- package/dist/web/servidor/arranque.js +324 -9
- package/dist/web/servidor/arranque.js.map +1 -1
- package/dist/web/servidor/sesiones.js +30 -2
- package/dist/web/servidor/sesiones.js.map +1 -1
- package/dist/web/servidor/transporte.js.map +1 -1
- package/dist/web/servidor/vestibulo.js +55 -6
- package/dist/web/servidor/vestibulo.js.map +1 -1
- package/package.json +2 -1
- package/skills/xone-hotswap/SKILL.md +31 -9
- package/skills/xone-hotswap/scripts/xone-arrancar-android +202 -0
- package/skills/xone-hotswap/scripts/xone-captura-android +93 -0
- package/skills/xone-hotswap/scripts/xone-desplegar-android +28 -0
- package/skills/xone-hotswap/scripts/xone-hotswap +39 -9
- package/skills/xone-hotswap/scripts/xone-reiniciar-android +28 -0
- package/skills/xone-plan-builder/SKILL.md +22 -4
- package/skills/xone-spec-builder/SKILL.md +37 -7
- 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
|
-
/**
|
|
60
|
-
|
|
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
|
-
/**
|
|
94
|
-
|
|
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
|
|
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 <
|
|
129
|
+
if (typeof valor !== "string" || valor.length < LARGO_PARA_GUARDAR) continue;
|
|
130
|
+
const imagen = imagenDe(valor);
|
|
112
131
|
if (ARTEFACTOS === undefined) {
|
|
113
|
-
copia[clave] =
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
33
|
+
/planes/favoritos/
|
|
27
34
|
├── PLAN.md ← entrada (producido por xone-spec-builder)
|
|
28
|
-
├── TASKS.md
|
|
35
|
+
├── TASKS.md ← salida (esta skill)
|
|
29
36
|
├── CONTEXT.md ← glosario (ya existe del spec-builder)
|
|
30
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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 `
|
|
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:
|