oficina-opencode 2.0.1 → 2.1.1

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 (92) hide show
  1. package/README.md +164 -14
  2. package/commands/oficina.md +22 -9
  3. package/dist/core/atomic.d.ts +85 -0
  4. package/dist/core/atomic.d.ts.map +1 -0
  5. package/dist/core/atomic.js +186 -0
  6. package/dist/core/atomic.js.map +1 -0
  7. package/dist/core/bus.d.ts +76 -56
  8. package/dist/core/bus.d.ts.map +1 -1
  9. package/dist/core/bus.js +141 -100
  10. package/dist/core/bus.js.map +1 -1
  11. package/dist/core/catalog.d.ts +27 -0
  12. package/dist/core/catalog.d.ts.map +1 -1
  13. package/dist/core/catalog.js +41 -6
  14. package/dist/core/catalog.js.map +1 -1
  15. package/dist/core/cost.d.ts +275 -0
  16. package/dist/core/cost.d.ts.map +1 -0
  17. package/dist/core/cost.js +724 -0
  18. package/dist/core/cost.js.map +1 -0
  19. package/dist/core/docsync.d.ts +124 -0
  20. package/dist/core/docsync.d.ts.map +1 -0
  21. package/dist/core/docsync.js +191 -0
  22. package/dist/core/docsync.js.map +1 -0
  23. package/dist/core/evidence.d.ts +314 -0
  24. package/dist/core/evidence.d.ts.map +1 -0
  25. package/dist/core/evidence.js +602 -0
  26. package/dist/core/evidence.js.map +1 -0
  27. package/dist/core/fsx.d.ts.map +1 -1
  28. package/dist/core/fsx.js +7 -1
  29. package/dist/core/fsx.js.map +1 -1
  30. package/dist/core/gates.d.ts +9 -2
  31. package/dist/core/gates.d.ts.map +1 -1
  32. package/dist/core/gates.js +19 -3
  33. package/dist/core/gates.js.map +1 -1
  34. package/dist/core/ids.d.ts +48 -0
  35. package/dist/core/ids.d.ts.map +1 -0
  36. package/dist/core/ids.js +87 -0
  37. package/dist/core/ids.js.map +1 -0
  38. package/dist/core/leases.d.ts +45 -0
  39. package/dist/core/leases.d.ts.map +1 -1
  40. package/dist/core/leases.js +85 -17
  41. package/dist/core/leases.js.map +1 -1
  42. package/dist/core/office.d.ts +72 -1
  43. package/dist/core/office.d.ts.map +1 -1
  44. package/dist/core/office.js +173 -6
  45. package/dist/core/office.js.map +1 -1
  46. package/dist/core/paths.d.ts +11 -1
  47. package/dist/core/paths.d.ts.map +1 -1
  48. package/dist/core/paths.js +28 -4
  49. package/dist/core/paths.js.map +1 -1
  50. package/dist/core/recovery.d.ts +87 -0
  51. package/dist/core/recovery.d.ts.map +1 -0
  52. package/dist/core/recovery.js +325 -0
  53. package/dist/core/recovery.js.map +1 -0
  54. package/dist/core/sandbox.d.ts +179 -0
  55. package/dist/core/sandbox.d.ts.map +1 -0
  56. package/dist/core/sandbox.js +546 -0
  57. package/dist/core/sandbox.js.map +1 -0
  58. package/dist/core/state.d.ts +7 -0
  59. package/dist/core/state.d.ts.map +1 -1
  60. package/dist/core/state.js +15 -1
  61. package/dist/core/state.js.map +1 -1
  62. package/dist/core/stores.d.ts +65 -0
  63. package/dist/core/stores.d.ts.map +1 -0
  64. package/dist/core/stores.js +115 -0
  65. package/dist/core/stores.js.map +1 -0
  66. package/dist/core/tripwire.d.ts +87 -0
  67. package/dist/core/tripwire.d.ts.map +1 -0
  68. package/dist/core/tripwire.js +203 -0
  69. package/dist/core/tripwire.js.map +1 -0
  70. package/dist/core/types.d.ts +33 -1
  71. package/dist/core/types.d.ts.map +1 -1
  72. package/dist/core/types.js +1 -0
  73. package/dist/core/types.js.map +1 -1
  74. package/dist/hooks/scopeGuard.d.ts +86 -13
  75. package/dist/hooks/scopeGuard.d.ts.map +1 -1
  76. package/dist/hooks/scopeGuard.js +296 -62
  77. package/dist/hooks/scopeGuard.js.map +1 -1
  78. package/dist/index.d.ts +10 -0
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +185 -2
  81. package/dist/index.js.map +1 -1
  82. package/dist/tools/index.d.ts.map +1 -1
  83. package/dist/tools/index.js +158 -0
  84. package/dist/tools/index.js.map +1 -1
  85. package/docs/COMPATIBILITY.md +98 -0
  86. package/docs/GARANTIAS.md +190 -0
  87. package/docs/PLAN-V2.1.md +144 -0
  88. package/docs/QUE-ES.md +15 -0
  89. package/docs/catalog-facts.json +27 -0
  90. package/package.json +6 -2
  91. package/skills/oficina/SKILL.md +191 -69
  92. package/skills/oficina/reference/mesh.md +11 -2
@@ -0,0 +1,546 @@
1
+ /**
2
+ * Sandbox: resolución y validación de rutas.
3
+ *
4
+ * ## Por qué existe
5
+ *
6
+ * El guard de scope comparaba strings:
7
+ *
8
+ * const relative = path.relative(projectRoot, absolute)
9
+ * if (!matchesScope(relative, mayTouch)) throw ...
10
+ *
11
+ * Eso tiene tres agujeros, y los tres son reales:
12
+ *
13
+ * 1. **Symlinks.** `src/link.ts` -> `../../etc/secrets.ts`. El path relativo
14
+ * es `src/link.ts`, que matchea `src/**`. El guard autoriza. La
15
+ * escritura ocurre afuera. Verificar el path **después** de resolver los
16
+ * symlinks es la diferencia entre "está en el scope" y "termina en el
17
+ * scope".
18
+ *
19
+ * 2. **`..` con `mayTouch: ["**"]`.** `**` matcheaba cualquier string,
20
+ * incluido `../../etc/passwd`. El `**` tiene que significar "todo el
21
+ * proyecto", nunca "todo el filesystem".
22
+ *
23
+ * 3. **Paths absolutos.** `/etc/passwd` o `C:\Windows\...` tienen un
24
+ * relativo que empieza en `..`, y con `**` pasaban.
25
+ *
26
+ * ## La regla
27
+ *
28
+ * Ninguna ruta se autoriza por comparación de strings. Se resuelve primero
29
+ * (absoluta → normalizada → symlinks resueltos → realpath) y recién ahí se
30
+ * decide. Y la decisión es **fail-closed**: ante cualquier duda, se deniega.
31
+ *
32
+ * ## Qué NO promete
33
+ *
34
+ * Un sandbox a nivel de aplicación no puede⬇️ contener un `bash` que el modelo
35
+ * redacta con un interpreter embebido. Eso sí se cubre, pero con otra
36
+ * técnica: se analiza el comando antes de ejecutarlo y se instalan
37
+ * *tripwires* después. Ver `analyzeShellCommand` y el modulo de deteccion.
38
+ */
39
+ import fs from "node:fs/promises";
40
+ import path from "node:path";
41
+ /* ────────────────────────────────────────────────────────────────────────────
42
+ * Glob a regex
43
+ * ──────────────────────────────────────────────────────────────────────────── */
44
+ /**
45
+ * Glob mínimo a regex. Soporta `*`, `**` y `?`.
46
+ *
47
+ * El orden de los reemplazos importa: el `?` se convierte ANTES de insertar
48
+ * los patrones de `**`, porque el grupo insertado contiene un `?` literal
49
+ * que, si se convierte después, se rompe y deja de matchear.
50
+ */
51
+ export function globToRegex(glob) {
52
+ const escaped = glob
53
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
54
+ // `?` primero: los patrones insertados después contienen `?` literal.
55
+ .replace(/\?/g, ".")
56
+ .replace(/\*\*\//g, "\u0000")
57
+ .replace(/\*\*/g, "\u0001")
58
+ .replace(/\*/g, "[^/]*")
59
+ .replace(/\u0000/g, "(?:.*/)?")
60
+ .replace(/\u0001/g, ".*");
61
+ return new RegExp(`^${escaped}$`);
62
+ }
63
+ /** Normaliza un glob: sin `./` inicial, sin `/` inicial, con `/` como separador. */
64
+ export function normalizeGlob(glob) {
65
+ return glob.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
66
+ }
67
+ /**
68
+ * ¿El path cae dentro de alguno de los globs?
69
+ *
70
+ * Ojo con `**`: matchea todo lo que se le pase. Por eso `checkScope` nunca
71
+ * llama a esta función antes de verificar la contención en el proyecto.
72
+ */
73
+ export function matchesScope(relativePath, globs) {
74
+ const normalized = relativePath.replace(/\\/g, "/").replace(/^\.\//, "");
75
+ return globs.some((glob) => {
76
+ if (glob === "**" || glob === "***")
77
+ return true;
78
+ const g = normalizeGlob(glob);
79
+ if (g === "**")
80
+ return true;
81
+ return globToRegex(g).test(normalized);
82
+ });
83
+ }
84
+ /* ────────────────────────────────────────────────────────────────────────────
85
+ * Contención
86
+ * ──────────────────────────────────────────────────────────────────────────── */
87
+ const IS_WINDOWS = process.platform === "win32";
88
+ /** Normaliza para comparar: en Windows las rutas son case-insensitive. */
89
+ function comparable(p) {
90
+ const resolved = path.resolve(p);
91
+ return IS_WINDOWS ? resolved.toLowerCase() : resolved;
92
+ }
93
+ /**
94
+ * ¿`target` está dentro de `root`?
95
+ *
96
+ * La comparación es sobre paths ya resueltos, y el separador final se exige
97
+ * explícitamente: `/proj-evil` NO está dentro de `/proj`.
98
+ */
99
+ export function isInsideRoot(root, target) {
100
+ const r = comparable(root);
101
+ const t = comparable(target);
102
+ if (t === r)
103
+ return true;
104
+ return t.startsWith(r.endsWith(path.sep) ? r : r + path.sep);
105
+ }
106
+ /** Relativo con `/` como separador. */
107
+ export function toPosix(p) {
108
+ return p.replace(/\\/g, "/");
109
+ }
110
+ /**
111
+ * `realpath` del ancestro existente mas profundo, con la cola re-enganchada.
112
+ *
113
+ * `fs.realpath` falla si el archivo no existe todavia. Pero un archivo nuevo
114
+ * en un directorio symlinkeado tiene un padre real distinto: hay que
115
+ * resolver el padre y re-enganchar el nombre.
116
+ */
117
+ async function realpathBestEffort(target) {
118
+ let current = path.resolve(target);
119
+ const tail = [];
120
+ for (;;) {
121
+ try {
122
+ const real = await fs.realpath(current);
123
+ const resolved = tail.length > 0 ? path.join(real, ...tail) : real;
124
+ return { resolved, exists: tail.length === 0 };
125
+ }
126
+ catch {
127
+ const parent = path.dirname(current);
128
+ if (parent === current) {
129
+ // Llegamos a la raiz del sistema y no existe: devolvemos el target.
130
+ return { resolved: path.resolve(target), exists: false };
131
+ }
132
+ tail.unshift(path.basename(current));
133
+ current = parent;
134
+ }
135
+ }
136
+ }
137
+ /**
138
+ * Recorre los componentes del camino buscando symlinks.
139
+ *
140
+ * Un symlink que apunta fuera del root es un escape, se detecte o no el
141
+ * archivo final.
142
+ */
143
+ async function findSymlinks(root, absolute) {
144
+ const symlinks = [];
145
+ let escapes = false;
146
+ if (!isInsideRoot(root, absolute)) {
147
+ return { symlinks, escapes: true };
148
+ }
149
+ const rel = path.relative(path.resolve(root), absolute);
150
+ const parts = rel.split(path.sep).filter((p) => p.length > 0);
151
+ let current = path.resolve(root);
152
+ for (const part of parts) {
153
+ current = path.join(current, part);
154
+ let stat;
155
+ try {
156
+ stat = await fs.lstat(current);
157
+ }
158
+ catch {
159
+ // El componente no existe todavia: no hay symlink que seguir.
160
+ continue;
161
+ }
162
+ if (!stat.isSymbolicLink())
163
+ continue;
164
+ symlinks.push(current);
165
+ let target;
166
+ try {
167
+ target = await fs.realpath(current);
168
+ }
169
+ catch {
170
+ // Symlink roto: no se puede resolver. Se trata como escapatorio
171
+ // conservador, porque un link roto puede apuntar a cualquier lado
172
+ // en cuanto exista el destino.
173
+ escapes = true;
174
+ continue;
175
+ }
176
+ if (!isInsideRoot(root, target))
177
+ escapes = true;
178
+ }
179
+ return { symlinks, escapes };
180
+ }
181
+ /**
182
+ * Resuelve un path contra el root, deshaciendo symlinks.
183
+ *
184
+ * Devuelve toda la información que `checkScope` necesita para decidir, y
185
+ * no decide: la decision vive en un solo lugar.
186
+ */
187
+ export async function resolveTarget(root, rawPath) {
188
+ const projectRoot = path.resolve(root);
189
+ const absolute = path.resolve(projectRoot, rawPath);
190
+ const { resolved, exists } = await realpathBestEffort(absolute);
191
+ const { symlinks, escapes } = await findSymlinks(projectRoot, absolute);
192
+ const insideLexically = isInsideRoot(projectRoot, absolute);
193
+ const insidePhysically = insideLexically && !escapes && isInsideRoot(projectRoot, resolved);
194
+ return {
195
+ absolute,
196
+ resolved,
197
+ relative: toPosix(path.relative(projectRoot, resolved)),
198
+ lexicalRelative: toPosix(path.relative(projectRoot, absolute)),
199
+ symlinks,
200
+ exists,
201
+ insideLexically,
202
+ insidePhysically,
203
+ };
204
+ }
205
+ /* ────────────────────────────────────────────────────────────────────────────
206
+ * Archivos sensibles
207
+ * ──────────────────────────────────────────────────────────────────────────── */
208
+ /**
209
+ * Archivos que NUNCA se pueden escribir, pase lo que pase.
210
+ *
211
+ * No son negociables por el scope de la card: un `mayTouch: ["**"]` no
212
+ * habilita a tocar el `.npmrc` de alguien ni una clave privada.
213
+ */
214
+ export const SENSITIVE_PATTERNS = [
215
+ "**/.env",
216
+ "**/.env.*",
217
+ "**/.npmrc",
218
+ "**/.netrc",
219
+ "**/.git/**",
220
+ "**/.git-credentials",
221
+ "**/.htpasswd",
222
+ "**/*.pem",
223
+ "**/*.key",
224
+ "**/*.p12",
225
+ "**/*.pfx",
226
+ "**/*.crt",
227
+ "**/id_rsa*",
228
+ "**/id_ed25519*",
229
+ "**/id_ecdsa*",
230
+ "**/.ssh/**",
231
+ "**/.aws/**",
232
+ "**/.gnupg/**",
233
+ "**/.config/gcloud/**",
234
+ "**/.kube/**",
235
+ "**/credentials.json",
236
+ "**/service-account*.json",
237
+ "**/.docker/config.json",
238
+ "**/.opencode/opencode.json",
239
+ ];
240
+ /**
241
+ * Excepciones: archivos que matchean un patrón sensible pero son inocuos.
242
+ *
243
+ * `.env.example` es un template sin secretos; bloquearlo obliga a los
244
+ * agentes a hacer un rodeo por el `.env` real.
245
+ */
246
+ export const SENSITIVE_EXCEPTIONS = [
247
+ "**/.env.example",
248
+ "**/.env.sample",
249
+ "**/.env.template",
250
+ "**/*.key.example",
251
+ "**/credentials.example.json",
252
+ ];
253
+ /** ¿El path relativo cae en un patrón sensible y no es una excepción? */
254
+ export function isSensitive(relativePath) {
255
+ const normalized = normalizeGlob(relativePath);
256
+ if (SENSITIVE_EXCEPTIONS.some((ex) => matchesScope(normalized, [ex])))
257
+ return false;
258
+ return SENSITIVE_PATTERNS.some((pattern) => matchesScope(normalized, [pattern]));
259
+ }
260
+ /**
261
+ * Rutas que la propia oficina usa como canal de entrega.
262
+ *
263
+ * El agente tiene que poder escribir `handoff.md`, `evidence.md`,
264
+ * `review.md` y `gates.md`: son la evidencia de su trabajo. El scope de la
265
+ * card es `src/**`, asi que sin esta excepcion el agente no podria
266
+ * entregar nunca.
267
+ *
268
+ * OJO: solo los directorios de entrega. `state.json`, `roster.json`, el bus
269
+ * y los leases son del motor — que un agente pueda escribir ahi seria
270
+ * falsear la verdad de la corrida.
271
+ */
272
+ export const OFFICE_WRITABLE = [
273
+ ".oficina/artifacts/**",
274
+ ".oficina/fichas/**",
275
+ ];
276
+ /**
277
+ * Decide si un path puede escribirse.
278
+ *
279
+ * El orden de las comprobaciones es intencionado: primero lo que invalida
280
+ * la ruta, después lo que la vuelve peligrosa, y recién al final si entra
281
+ * en el scope permitido. Así el error que recibe el modelo dice la verdad
282
+ * mas específica posible.
283
+ */
284
+ export async function checkScope(root, rawPath, policy, options = {}) {
285
+ const projectRoot = path.resolve(root);
286
+ if (typeof rawPath !== "string" || rawPath.length === 0) {
287
+ return deny("invalid", "Ruta vacía.", projectRoot, "");
288
+ }
289
+ if (rawPath.includes("\0")) {
290
+ return deny("invalid", "La ruta contiene un byte NUL.", projectRoot, "");
291
+ }
292
+ const target = await resolveTarget(projectRoot, rawPath);
293
+ // 1. Fuera del proyecto (por `..` o por path absoluto).
294
+ if (!target.insideLexically) {
295
+ return {
296
+ ...deny("outside-project", `\`${rawPath}\` resuelve fuera del proyecto (\`${target.absolute}\`). ` +
297
+ `La oficina solo escribe dentro de \`${projectRoot}\`.`, projectRoot, target.relative),
298
+ resolved: target.resolved,
299
+ symlinks: target.symlinks,
300
+ };
301
+ }
302
+ // 2. Escape por symlink: el path lexical esta adentro, el real no.
303
+ if (!target.insidePhysically) {
304
+ const detalle = target.symlinks.length > 0
305
+ ? `El camino atraviesa el symlink \`${toPosix(target.symlinks[0])}\` que apunta afuera del proyecto.`
306
+ : "El camino resuelve a una ubicación fuera del proyecto.";
307
+ return {
308
+ ...deny("symlink-escape", `\`${rawPath}\` escapa del proyecto por symlink. ${detalle} ` +
309
+ `Resuelve a \`${target.resolved}\`, que está fuera de \`${projectRoot}\`.`, projectRoot, target.relative),
310
+ resolved: target.resolved,
311
+ symlinks: target.symlinks,
312
+ };
313
+ }
314
+ // 3. Archivos sensibles: no se pueden habilitar con el scope.
315
+ if (!options.ignoreSensitive && isSensitive(target.relative)) {
316
+ return {
317
+ ...deny("sensitive", `\`${target.relative}\` es un archivo sensible (credenciales, claves o configuración de git). ` +
318
+ `La oficina nunca escribe esos archivos, pase lo que pase el scope. ` +
319
+ `Si de verdad hace falta, pedile al usuario que lo haga él.`, projectRoot, target.relative),
320
+ resolved: target.resolved,
321
+ symlinks: target.symlinks,
322
+ };
323
+ }
324
+ // 4. Forbidden de la card.
325
+ if (matchesScope(target.relative, policy.forbidden)) {
326
+ return {
327
+ ...deny("forbidden", `\`${target.relative}\` está en el scope forbidden de la card. ` +
328
+ `Forbidden: ${policy.forbidden.join(", ")}. No lo toques. ` +
329
+ `Si realmente hace falta, pedí que se amplíe el scope.`, projectRoot, target.relative),
330
+ resolved: target.resolved,
331
+ symlinks: target.symlinks,
332
+ };
333
+ }
334
+ // 5. Canal de entrega de la oficina: siempre escribible.
335
+ const officeWritable = options.officeWritable ?? OFFICE_WRITABLE;
336
+ if (matchesScope(target.relative, [...officeWritable])) {
337
+ return {
338
+ allowed: true,
339
+ kind: "allowed",
340
+ reason: "ok",
341
+ resolved: target.resolved,
342
+ relative: target.relative,
343
+ symlinks: target.symlinks,
344
+ };
345
+ }
346
+ // 6. mayTouch.
347
+ if (!matchesScope(target.relative, policy.mayTouch)) {
348
+ return {
349
+ ...deny("out-of-scope", `\`${target.relative}\` está fuera del scope de la card. ` +
350
+ `Podés tocar: ${policy.mayTouch.join(", ")}. ` +
351
+ `Si el archivo es necesario, pedí por el bus que se amplíe el scope, ` +
352
+ `o pedí ayuda a un peer que lo cubra.`, projectRoot, target.relative),
353
+ resolved: target.resolved,
354
+ symlinks: target.symlinks,
355
+ };
356
+ }
357
+ return {
358
+ allowed: true,
359
+ kind: "allowed",
360
+ reason: "ok",
361
+ resolved: target.resolved,
362
+ relative: target.relative,
363
+ symlinks: target.symlinks,
364
+ };
365
+ }
366
+ function deny(kind, reason, _root, relative) {
367
+ return {
368
+ allowed: false,
369
+ kind,
370
+ reason,
371
+ resolved: "",
372
+ relative,
373
+ symlinks: [],
374
+ };
375
+ }
376
+ /** Quita comillas y comillas invertidas de un token de shell. */
377
+ function unquote(token) {
378
+ const t = token.trim();
379
+ if ((t.startsWith('"') && t.endsWith('"') && t.length >= 2) ||
380
+ (t.startsWith("'") && t.endsWith("'") && t.length >= 2)) {
381
+ return t.slice(1, -1);
382
+ }
383
+ return t;
384
+ }
385
+ /** El ultimo token no-vacio de una lista de argumentos. */
386
+ function lastToken(args) {
387
+ const list = Array.isArray(args) ? args : args.split(/\s+/);
388
+ for (let i = list.length - 1; i >= 0; i -= 1) {
389
+ const t = (list[i] ?? "").trim();
390
+ if (!t)
391
+ continue;
392
+ if (t.startsWith("-"))
393
+ continue;
394
+ return unquote(t);
395
+ }
396
+ return undefined;
397
+ }
398
+ /** Todos los tokens no-opcion de una lista de argumentos. */
399
+ function plainTokens(args) {
400
+ const list = Array.isArray(args) ? args : args.split(/\s+/);
401
+ return list
402
+ .filter((t) => t.trim().length > 0 && !t.trim().startsWith("-"))
403
+ .map(unquote);
404
+ }
405
+ /**
406
+ * Extrae los paths que un comando de shell puede escribir.
407
+ *
408
+ * ## Por qué no es un parser de shell
409
+ *
410
+ * Un parser completo de shell (con here-docs, substituciones, funciones,
411
+ * `eval`, interpreters embebidos) es un proyecto entero y sigue siendo
412
+ * evadible. Lo que hace falta aca es otra cosa: **no dejar pasar en
413
+ * silencio** una escritura que no entendimos.
414
+ *
415
+ * Por eso la funcion devuelve tambien los targets `uncertain`: cuando
416
+ * vemos una escritura pero no sabemos a donde apunta, el comando no se
417
+ * autoriza a ciegas — se deja pasar con el tripwire armed, y el
418
+ * tripwire es el que detecta la escribir fuera del scope despues.
419
+ */
420
+ export function analyzeShellCommand(command) {
421
+ const out = [];
422
+ if (typeof command !== "string" || command.trim().length === 0)
423
+ return out;
424
+ const add = (target, op, uncertain = false) => {
425
+ if (target === undefined) {
426
+ out.push({ target: "", op, uncertain: true });
427
+ return;
428
+ }
429
+ const clean = unquote(target).trim();
430
+ if (!clean || clean === "-" || clean === "&1")
431
+ return;
432
+ out.push({ target: clean, op, uncertain: false });
433
+ };
434
+ // Redirecciones: > >> 2> &> |>
435
+ const redirect = /(?:^|[^0-9>|&])(?:\d*>>?|&>>?|\|>)\s*("[^"]*"|'[^']*'|[^\s;|&>]+)/g;
436
+ for (const m of command.matchAll(redirect)) {
437
+ add(m[1], "redirect");
438
+ }
439
+ // tee
440
+ for (const m of command.matchAll(/\btee\b((?:\s+-{1,2}\S+)*)\s+("[^"]*"|'[^']*'|[^\s;|&]+)/g)) {
441
+ add(m[2], "tee");
442
+ }
443
+ // cp / mv / install -> destino = ultimo argumento
444
+ for (const m of command.matchAll(/\b(cp|mv|install)\s+((?:[^\s;|&]|\s)+?)(?:\s*[;|&]|$)/g)) {
445
+ const args = plainTokens(m[2] ?? "");
446
+ if (args.length === 0) {
447
+ add(undefined, m[1] ?? "cp", true);
448
+ continue;
449
+ }
450
+ // `cp -r a b c` -> el destino es `c`; `cp a b` -> `b`.
451
+ const dest = args.length >= 2 ? args[args.length - 1] : args[0];
452
+ add(dest, m[1] ?? "cp");
453
+ }
454
+ // rm -> todos los argumentos son blancos
455
+ for (const m of command.matchAll(/\brm\s+((?:[^\s;|&]|\s)+?)(?:\s*[;|&]|$)/g)) {
456
+ const args = plainTokens(m[1] ?? "");
457
+ if (args.length === 0)
458
+ add(undefined, "rm", true);
459
+ else
460
+ for (const a of args)
461
+ add(a, "rm");
462
+ }
463
+ // mkdir / touch / truncate / mktemp
464
+ for (const m of command.matchAll(/\b(mkdir|touch|truncate)\s+((?:[^\s;|&]|\s)+?)(?:\s*[;|&]|$)/g)) {
465
+ const args = plainTokens(m[2] ?? "");
466
+ if (args.length === 0)
467
+ add(undefined, m[1] ?? "mkdir", true);
468
+ else
469
+ for (const a of args)
470
+ add(a, m[1] ?? "mkdir");
471
+ }
472
+ // sed -i
473
+ for (const m of command.matchAll(/\bsed\s+((?:[^\s;|&]|\s)+?)(?:\s*[;|&]|$)/g)) {
474
+ const raw = m[1] ?? "";
475
+ if (!/(^|\s)-[a-zA-Z]*i/.test(raw))
476
+ continue;
477
+ const args = plainTokens(raw.replace(/(^|\s)-[a-zA-Z]*i(\S*)/, " "));
478
+ const target = lastToken(args);
479
+ add(target, "sed -i");
480
+ }
481
+ // dd of=
482
+ for (const m of command.matchAll(/\bdd\b[^\n;|&]*?\bof=("[^"]*"|'[^']*'|\S+)/g)) {
483
+ add(m[1], "dd");
484
+ }
485
+ // curl -o / wget -O
486
+ for (const m of command.matchAll(/\b(curl|wget)\b[^\n;|&]*?\s-[oO]\s*("[^"]*"|'[^']*'|\S+)/g)) {
487
+ add(m[2], m[1] ?? "curl");
488
+ }
489
+ // git checkout / restore / clean
490
+ for (const m of command.matchAll(/\bgit\s+(?:checkout|restore|clean)\b([^\n;|&]*)/g)) {
491
+ const rest = (m[1] ?? "").trim();
492
+ const afterDash = /--\s+(.+)$/.exec(rest);
493
+ if (afterDash) {
494
+ for (const p of plainTokens(afterDash[1] ?? ""))
495
+ add(p, "git");
496
+ continue;
497
+ }
498
+ const args = plainTokens(rest);
499
+ if (args.length === 0) {
500
+ add(undefined, "git", true);
501
+ continue;
502
+ }
503
+ for (const p of args)
504
+ add(p, "git");
505
+ }
506
+ // chmod / chown
507
+ for (const m of command.matchAll(/\b(chmod|chown)\s+((?:[^\s;|&]|\s)+?)(?:\s*[;|&]|$)/g)) {
508
+ const target = lastToken(plainTokens(m[2] ?? ""));
509
+ add(target, m[1] ?? "chmod");
510
+ }
511
+ // Interpreters embebidos: python -c / node -e / ruby -e / perl -e.
512
+ // OJO: el flag es `-c` en python y `-e` en node/ruby/perl. Un regex con
513
+ // `-e?` matcheaba el guion suelto y nunca llegaba al codigo.
514
+ const INTERPRETER = /\b(python3?|node|ruby|perl)\b\s+(?:-[ec]\s+|--eval\s+)?("[^"]*"|'[^']*')/g;
515
+ for (const m of command.matchAll(INTERPRETER)) {
516
+ const code = unquote(m[2] ?? "");
517
+ const literales = [...code.matchAll(/["']([^"']{2,})["']/g)].map((x) => x[1] ?? "");
518
+ const paths = literales.filter((s) => s.includes("/") || /\.[A-Za-z0-9]{1,6}$/.test(s));
519
+ if (paths.length === 0) {
520
+ add(undefined, `${m[1] ?? "python"} -c`, true);
521
+ continue;
522
+ }
523
+ for (const p of paths)
524
+ add(p, `${m[1] ?? "python"} -c`);
525
+ }
526
+ // Heredoc a un interpreter: el codigo entra por stdin, no se puede ver.
527
+ for (const m of command.matchAll(/\b(python3?|node|ruby|perl)\b\s*<<-?\s*['"]?\w+['"]?/g)) {
528
+ add(undefined, `${m[1] ?? "python"} heredoc`, true);
529
+ }
530
+ // Escrituras que no sabemos interpretar: el verbo esta pero no el target.
531
+ if (/\b(python3?|node|ruby|perl)\b\s+(?:-[ec]\s+|--eval\s+|<<)/.test(command)) {
532
+ const yaCubierto = out.some((c) => c.op.includes("-c") || c.op.includes("heredoc"));
533
+ if (!yaCubierto)
534
+ out.push({ target: "", op: "interpreter", uncertain: true });
535
+ }
536
+ // Deduplicar por (op, target).
537
+ const seen = new Set();
538
+ return out.filter((c) => {
539
+ const key = `${c.op}::${c.target}`;
540
+ if (seen.has(key))
541
+ return false;
542
+ seen.add(key);
543
+ return true;
544
+ });
545
+ }
546
+ //# sourceMappingURL=sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../src/core/sandbox.ts"],"names":[],"mappings":"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"}
@@ -40,6 +40,13 @@ export interface InvariantContext {
40
40
  limits: Limits;
41
41
  /** Veredicto de los gates de la card, si se corrieron. */
42
42
  gatesPassed?: boolean;
43
+ /**
44
+ * Veredicto de la verificación por requisitos.
45
+ *
46
+ * `undefined` significa "no se evaluó" (por ejemplo, en un flujo que no
47
+ * toca el cierre). `false` significa "se evaluó y le faltan requisitos".
48
+ */
49
+ evidenceComplete?: boolean;
43
50
  }
44
51
  /**
45
52
  * Verifica los invariantes duros.
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/core/state.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACzE,OAAO,EAAkB,KAAK,MAAM,EAAE,MAAM,YAAY,CAAA;AAGxD,uEAAuE;AACvE,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,SAAS,EAAE,SAAS,SAAS,EAAE,CAWpE,CAAA;AAED,sCAAsC;AACtC,eAAO,MAAM,oBAAoB,EAAE,SAAS,SAAS,EAAa,CAAA;AAElE,+CAA+C;AAC/C,eAAO,MAAM,oBAAoB,EAAE,SAAS,SAAS,EAA6B,CAAA;AAElF,qBAAa,sBAAuB,SAAQ,KAAK;IAE7C,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM;gBAFV,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM;CAKtB;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IAE9C,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM;gBADd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM;CAK1B;AAED,qCAAqC;AACrC,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,GAAG,OAAO,CAErE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,SAAS,EACb,KAAK,GAAE,OAAO,CAAC,IAAI,CAAM,GACxB,IAAI,CAUN;AAMD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,KAAK,EAAE,WAAW,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,CA8D5D;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,MAAuB,GAAG,WAAW,GAAG,SAAS,CAiFnG;AAED,8BAA8B;AAC9B,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAuB,GAAG,WAAW,CA0BxG;AAED,kCAAkC;AAClC,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,SAAS,EAAE,SAAS,SAAS,EAAE,CAQnE,CAAA;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,GAAG,OAAO,CAExE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,SAAS,GAAG,IAAI,CAMrE;AAMD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,cAAc,CA2CjE;AAED,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAGzD;AAED,oCAAoC;AACpC,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,EAAE,CAExD"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/core/state.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACzE,OAAO,EAAkB,KAAK,MAAM,EAAE,MAAM,YAAY,CAAA;AAGxD,uEAAuE;AACvE,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,SAAS,EAAE,SAAS,SAAS,EAAE,CAWpE,CAAA;AAED,sCAAsC;AACtC,eAAO,MAAM,oBAAoB,EAAE,SAAS,SAAS,EAAa,CAAA;AAElE,+CAA+C;AAC/C,eAAO,MAAM,oBAAoB,EAAE,SAAS,SAAS,EAA6B,CAAA;AAElF,qBAAa,sBAAuB,SAAQ,KAAK;IAE7C,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM;gBAFV,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM;CAKtB;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IAE9C,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM;gBADd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM;CAK1B;AAED,qCAAqC;AACrC,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,GAAG,OAAO,CAErE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,SAAS,EACb,KAAK,GAAE,OAAO,CAAC,IAAI,CAAM,GACxB,IAAI,CAUN;AAMD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,KAAK,EAAE,WAAW,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,CA8E5D;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,MAAuB,GAAG,WAAW,GAAG,SAAS,CAkFnG;AAED,8BAA8B;AAC9B,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAuB,GAAG,WAAW,CA2BxG;AAED,kCAAkC;AAClC,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,SAAS,EAAE,SAAS,SAAS,EAAE,CAQnE,CAAA;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,GAAG,OAAO,CAExE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,SAAS,GAAG,IAAI,CAMrE;AAMD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,cAAc,CA2CjE;AAED,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAGzD;AAED,oCAAoC;AACpC,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,EAAE,CAExD"}
@@ -77,7 +77,7 @@ export function transitionCard(state, cardId, to, patch = {}) {
77
77
  * y "el modelo no puede romperlo".
78
78
  */
79
79
  export function assertInvariants(ctx) {
80
- const { card, state, limits, gatesPassed } = ctx;
80
+ const { card, state, limits, gatesPassed, evidenceComplete } = ctx;
81
81
  if (card) {
82
82
  // Invariante duro: el revisor nunca es el owner.
83
83
  if (limits.reviewerNeqOwner && card.owner && card.reviewer && card.owner === card.reviewer) {
@@ -89,6 +89,18 @@ export function assertInvariants(ctx) {
89
89
  throw new InvariantViolationError("gate-required-before-done", `${card.id}: marcada DONE sin gates en PASS`);
90
90
  }
91
91
  }
92
+ // Invariante duro: sin evidencia de los requisitos obligatorios no hay
93
+ // DONE.
94
+ //
95
+ // Los gates dicen que el codigo esta sano. No dicen que el objetivo del
96
+ // usuario este cumplido. Un proyecto puede compilar, pasar todos sus
97
+ // tests y tener la autenticacion mal porque ningun test contemplaba ese
98
+ // escenario. Por eso los dos veredictos son obligatorios.
99
+ if (limits.evidenceRequiredBeforeDone && card.state === "done") {
100
+ if (evidenceComplete !== true) {
101
+ throw new InvariantViolationError("evidence-required-before-done", `${card.id}: marcada DONE sin evidencia de todos los requisitos obligatorios`);
102
+ }
103
+ }
92
104
  // Invariante duro: una card no puede reventar el presupuesto de reviews.
93
105
  if (card.rejections > limits.maxReviewRejections) {
94
106
  throw new InvariantViolationError("max-review-rejections", `${card.id}: ${card.rejections} rechazos exceden el máximo ${limits.maxReviewRejections}`);
@@ -175,6 +187,7 @@ export function migrateState(raw, limits = DEFAULT_LIMITS) {
175
187
  createdAt: input.createdAt ?? input.created ?? new Date().toISOString(),
176
188
  updatedAt: input.updatedAt ?? input.updated ?? new Date().toISOString(),
177
189
  status: (input.status ?? "planning"),
190
+ halt: input.halt ?? null,
178
191
  cards,
179
192
  order,
180
193
  budget: {
@@ -225,6 +238,7 @@ export function createRun(objective, runId, limits = DEFAULT_LIMITS) {
225
238
  iteration: 0,
226
239
  pendingGates: [],
227
240
  sessions: {},
241
+ halt: null,
228
242
  };
229
243
  }
230
244
  /** Transiciones de la corrida. */