Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions server/src/agent/loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,9 @@ créalo con bash: es tu trabajo, no preguntes por dónde empezar.
El orden importa porque hasta que no hay manifiesto no hay instalación, sin
instalación no hay dev server, y sin dev server la sala mira una pantalla vacía.
- El proyecto va EN LA RAÍZ del directorio de trabajo, no en una subcarpeta.
Pero la raíz nunca está vacía: Multi siembra ahí su .gitignore antes de que llegues.
Un generador que se planta a preguntar si continúa sobre un directorio con archivos
se cancela solo, porque aquí nadie le contesta, y no hay bandera que lo arregle.
Cuando eso pase, no insistas: dale el directorio vacío que espera y mueve después a
la raíz lo que haya dejado.
Prefiere eso a escribir la configuración del stack de memoria, porque la plantilla
oficial está al día y lo que tú recuerdas es de cuando te entrenaron.
La raíz está vacía, así que los generadores del stack corren ahí sin problema.
Prefiérelos a escribir la configuración de memoria: su plantilla
está al día y lo que tú recuerdas es de cuando te entrenaron.
Comprueba que el manifiesto quedó en la raíz antes de seguir.
- Si te dicen el stack, usa ese, sea cual sea (Next, Svelte, Django, Go, lo que pidan).
- Si no te lo dicen, elige uno moderno y sensato en vez de interrogar a alguien que
Expand Down Expand Up @@ -132,7 +128,7 @@ créalo con bash: es tu trabajo, no preguntes por dónde empezar.
Pide las credenciales de una base externa por el panel de Variables, diciendo
los nombres exactos que vas a leer, y ofrece dejar la app andando con datos de
prueba mientras llegan.
El archivo de una base local NO entra al historial (está en el .gitignore): lo de
El archivo de una base local NO entra al historial (Multi ya lo ignora): lo de
ahí son datos de prueba. Deja el esquema en el código o en una migración, para que
la app arranque sola en una base vacía.
Y dilo al cerrar, en una línea: una base local vive SOLO en esta sala y no viaja
Expand Down
10 changes: 9 additions & 1 deletion server/src/demos/variables.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { readFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { createWorkspace } from "../engine/workspace.js";
import { guardarVariables, leerVariables } from "../engine/env.js";
Expand Down Expand Up @@ -71,9 +72,16 @@ async function main() {
check("las anteriores se fueron", v3.length === 1, v3.map((v) => v.nombre).join(","));

console.log("\n6. Y no entra al historial de la sala");
const gitignore = await readFile(join(ws.dir, ".gitignore"), "utf8");
// Las reglas del motor viven en `.git/info/exclude`, no en la raíz: así la
// sala nace vacía y ningún generador de proyecto las pisa.
const gitignore = await readFile(join(ws.dir, ".git", "info", "exclude"), "utf8");
check("el .env está ignorado", gitignore.includes(".env"));

// Y la sala nace VACÍA: un .gitignore en la raíz haría que los generadores de
// proyecto se planten a preguntar, y que el primero en correr lo pise con el
// suyo. Las dos cosas pasaron antes de mover las reglas a .git/info/exclude.
check("no hay .gitignore en la raíz", !existsSync(join(ws.dir, ".gitignore")));

console.log(`\n${pass} pasaron, ${fail} fallaron\n`);
process.exit(fail > 0 ? 1 : 0);
}
Expand Down
6 changes: 3 additions & 3 deletions server/src/engine/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,9 @@ export async function hayCommits(dir: string): Promise<boolean> {
* La diferencia no es teórica: un agente que crea archivos nuevos (el caso
* normal de un turno a medias) no se detectaba con `diff-index`.
*
* Sin ningún commit todavía se responde `false`: la sala recién nacida solo
* tiene el `.gitignore` del motor sin trackear, y llamar a eso "trabajo sin
* guardar" sería avisar de algo que nadie escribió.
* Sin ningún commit todavía se responde `false`: la sala recién nacida está
* vacía, y llamar a eso "trabajo sin guardar" sería avisar de algo que nadie
* escribió.
*/
export async function tieneCambiosSinCommitear(dir: string): Promise<boolean> {
if (!(await hayCommits(dir))) return false;
Expand Down
46 changes: 34 additions & 12 deletions server/src/engine/workspace.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { mkdir, writeFile, rm } from "node:fs/promises";
import { mkdir, writeFile, readFile, rm } from "node:fs/promises";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { dirname, join } from "node:path";
import { execFile } from "node:child_process";
import { promisify } from "node:util";

Expand All @@ -24,8 +24,10 @@ export interface Workspace {
* proyecto de arranque sería meter una plantilla por la puerta de atrás y dejar
* de ser agnósticos (era andamio de la Fase 0 para probar el motor; ya no).
*
* Lo único que se siembra es `.gitignore`, y eso SÍ es del motor: sin él,
* `git add -A` indexa node_modules y los turnos nunca commitean.
* Vacía de verdad, ni un archivo. Lo que el motor necesita ignorar vive dentro
* de `.git`, fuera de la vista: si estuviera en la raíz, los generadores de
* proyecto se plantarían a preguntar si continúan sobre un directorio con
* archivos, y el primero en correr lo pisaría con el suyo.
*/
export async function createWorkspace(
roomId: string,
Expand Down Expand Up @@ -57,17 +59,33 @@ export async function createWorkspace(
}

/**
* Siembra el `.gitignore` del motor si no existe.
* Las reglas de ignorar del motor, en `.git/info/exclude`.
*
* No es plantilla de proyecto: es la condición para que el historial funcione.
* Si el agente después lo edita para su stack, se respeta (por eso no se pisa).
* NO en un `.gitignore` de la raíz, y esa es la decisión que importa: git lee
* los dos igual, pero este vive dentro de `.git` y por lo tanto la carpeta de
* trabajo queda VACÍA.
*
* Con un archivo en la raíz, un generador (`npm create`, `create-next-app`…) se
* planta a preguntar si continúa sobre un directorio que no está vacío, nadie le
* contesta y se cancela; y el que sí corre escribe su propio `.gitignore`
* encima, borrando lo del motor sin que nadie se entere. Las dos cosas pasaron
* de verdad, y le costaban al agente media docena de comandos por sala.
*
* Nada de esto es plantilla de proyecto: es la condición para que el historial
* funcione. Si el proyecto trae su propio `.gitignore`, ese es suyo y se queda.
*/
const MARCA = "# Lo que nunca entra al historial de la sala. Lo pone Multi.";

async function ensureGitignore(dir: string): Promise<void> {
const path = join(dir, ".gitignore");
if (existsSync(path)) return;
const path = join(dir, ".git", "info", "exclude");
// `git init` ya deja aquí un archivo con instrucciones comentadas, así que no
// vale preguntar si EXISTE: hay que mirar si son NUESTRAS reglas las que están.
// Saltárselo dejaba el .env sin ignorar, y en silencio.
const previo = existsSync(path) ? await readFile(path, "utf8") : "";
if (previo.includes(MARCA)) return;
const lineas = [
"# Lo que nunca entra al historial de la sala.",
"# El agente puede agregar lo que su stack necesite.",
MARCA,
"# El proyecto puede tener su propio .gitignore para lo suyo.",
"node_modules/",
"dist/",
"build/",
Expand Down Expand Up @@ -100,7 +118,11 @@ async function ensureGitignore(dir: string): Promise<void> {
"*.db-wal",
"",
];
await writeFile(path, lineas.join("\n"), "utf8");
// Se AÑADE a lo que `git init` dejó, en vez de pisarlo: eso son comentarios de
// ayuda de git y borrarlos no aporta nada.
const separador = previo && !previo.endsWith("\n") ? "\n" : "";
await mkdir(dirname(path), { recursive: true });
await writeFile(path, previo + separador + lineas.join("\n"), "utf8");
}

/**
Expand Down
Loading