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,98 @@
1
+ # Compatibilidad
2
+
3
+ Qué versiones de OpenCode y del API de plugin soporta AI-OFFICE, y **qué se
4
+ verificó de cada una**.
5
+
6
+ La regla de este proyecto: lo que no está probado, no está soportado. Un
7
+ número de versión escrito a mano sin prueba de por medio es una promesa que
8
+ nadie verificó.
9
+
10
+ ---
11
+
12
+ ## Versiones
13
+
14
+ | Componente | Versión | Cómo se verificó |
15
+ |---|---|---|
16
+ | `@opencode-ai/plugin` | `1.15.11` | Compilación con `strict` + `noUncheckedIndexedAccess` contra los tipos reales del paquete. Un hook que no exista en el tipo **no compila**. |
17
+ | Node.js | `>=20` | CI corre la matriz `[20, 22]` en `ubuntu-latest` y `windows-latest`. |
18
+ | TypeScript | `>=5.6` | `tsc --noEmit` en modo `strict`. |
19
+
20
+ La API de plugin es la que manda: el plugin importa sus tipos, así que
21
+ **la compatibilidad se verifica en tiempo de compilación**, no por
22
+ inspección visual.
23
+
24
+ ## Hooks que usa el plugin
25
+
26
+ | Hook | Para qué | Si el host no lo soporta |
27
+ |---|---|---|
28
+ | `event` (`session.idle`) | Auto-reanudación de la corrida | La corrida sigue, pero no se reanuda sola. |
29
+ | `event` (`message.updated`) | Contabilidad de costo | No hay registro de costo. La oficina sigue funcionando. |
30
+ | `tool.execute.before` | Guard de scope | **Sin protección**: el scope pasa a ser una instrucción, no una restricción. |
31
+ | `tool.execute.after` | Tripwire | No hay detección de escrituras coladas. |
32
+ | `permission.ask` | Segunda capa de enforcement | Queda solo la primera capa. |
33
+ | `experimental.chat.system.transform` | El estado viaja con cada llamada | El agente tiene que preguntar el estado. |
34
+ | `experimental.session.compacting` | El estado sobrevive a la compactación | La compactación pierde el contexto de la oficina. |
35
+ | `experimental.compaction.autocontinue` | Freno de emergencia | El freno frena el trabajo, pero la auto-continuación post-compactación sigue. |
36
+
37
+ ## Mínimo verificable
38
+
39
+ La parte del plugin que **no degrada bien** es el guard de scope. Por eso
40
+ el proyecto declara un mínimo:
41
+
42
+ - **`@opencode-ai/plugin` >= 1.15.11**: a partir de esta versión existen
43
+ `tool.execute.before`, `tool.execute.after` y `permission.ask` con las
44
+ firmas que usa el plugin.
45
+
46
+ Si se instala con una versión anterior, el typecheck falla en la
47
+ instalación del consumidor y eso es intencional: es mejor un error de
48
+ compilación que una instalación que parece protegida y no lo está.
49
+
50
+ ## Pruebas de integración por versión
51
+
52
+ El CI corre la matriz completa:
53
+
54
+ ```
55
+ os: [ubuntu-latest, windows-latest]
56
+ node: [20, 22]
57
+ ```
58
+
59
+ En cada combinación se corre:
60
+
61
+ 1. `npm run typecheck` — la API de plugin compila.
62
+ 2. `npm run test` — la suite completa (los tests del plugin usan un host
63
+ simulado con la misma forma que el real).
64
+ 3. `npm run build` — el `dist/` se genera.
65
+ 4. `npm pack --dry-run` — el paquete se puede armar.
66
+
67
+ Y en un job aparte, el smoke del CLI sobre el `dist/` real.
68
+
69
+ ## OpenCode
70
+
71
+ | Versión de OpenCode | Soporte |
72
+ |---|---|
73
+ | La que trae el host actual | Sí. Es la que se prueba en desarrollo. |
74
+ | Versiones anteriores | No declaradas. El plugin usa `session.prompt` con partes `subtask`, `session.promptAsync`, `session.get` y `client.app.log`. |
75
+
76
+ El plugin **no fija** una versión de OpenCode: se instala como plugin y usa
77
+ el API que el host expone. Lo que sí fija es la versión del **API de
78
+ plugin**, que es lo que usa directamente.
79
+
80
+ ## Requisitos del usuario, no del plugin
81
+
82
+ | Requisito | Por qué |
83
+ |---|---|
84
+ | Node >= 20 | `fs.promises`, `structuredClone`, ESM estable. |
85
+ | Un repo de `agency-agents` (opcional) | Sin él, la fábrica de roles funciona igual, pero los agentes salen de la plantilla genérica en vez del catálogo curado. |
86
+ | npm CLI >= 11.15 (opcional) | Solo para `npm stage publish`, el flujo de publicación por etapas de npm. La oficina no lo usa: publica directo. |
87
+
88
+ ---
89
+
90
+ ## Cómo se mantiene esta tabla
91
+
92
+ Las cifras de versiones salen del `package.json` real y se verifican en el
93
+ CI. El bloque de hechos de `README.md` y `docs/QUE-ES.md` se genera con
94
+ `npm run docs:sync` y el CI **falla** si se desincroniza:
95
+
96
+ ```bash
97
+ npm run docs:check # sale con 1 si la doc no coincide con el código
98
+ ```
@@ -0,0 +1,190 @@
1
+ # Garantías
2
+
3
+ Qué promete AI-OFFICE, **qué mecanismo lo hace cumplir** y **dónde termina
4
+ la garantía**.
5
+
6
+ La regla de este documento: si algo no se puede verificar con un test o con
7
+ un mecanismo de código, no se promete. Un README que vende más de lo que el
8
+ código hace es peor que un README modesto: hace que el usuario confíe en
9
+ una protección que no existe.
10
+
11
+ ---
12
+
13
+ ## 1. El scope es una restricción
14
+
15
+ ### Qué se garantiza
16
+
17
+ | Vector de escape | Cómo se frena | Test |
18
+ |---|---|---|
19
+ | `..` en la ruta | Se resuelve a absoluta y se verifica la contención | `sandbox.test.ts` |
20
+ | Path absoluto fuera del proyecto | Ídem | `sandbox.test.ts` |
21
+ | Symlink que apunta afuera | Se resuelve el symlink y **se revalida** la contención | `sandbox.test.ts` |
22
+ | Symlink roto | Se deniega (no se sabe a dónde apunta) | `sandbox.test.ts` |
23
+ | `mayTouch: ["**"]` como comodín | `**` significa "el proyecto", nunca "el filesystem" | `sandbox.test.ts` |
24
+ | Path sensível (`.env`, `.npmrc`, `.git/**`, claves) | Nunca escribible, pase lo que pase el scope | `sandbox.test.ts` |
25
+ | Diff multi-archivo | Se chequean **todos** los archivos del diff | `scopeGuard.test.ts` |
26
+ | Tool de shell escribiendo afuera | El comando se analiza antes de ejecutarse | `sandbox.test.ts` |
27
+ | Subagente delegado | Hereda el scope por la cadena de padres de sesión | `scopeGuard.test.ts` |
28
+ | Path con byte NUL | Se rechaza | `sandbox.test.ts` |
29
+
30
+ ### Las tres capas
31
+
32
+ 1. **`tool.execute.before`** — el guard propio. Cubre toda tool que el motor
33
+ reporte, incluidas las de shell.
34
+ 2. **`permission.ask`** — el framework. Deniega a nivel del motor.
35
+ 3. **`tool.execute.after`** — el tripwire. Detecta lo que se coló.
36
+
37
+ ### Lo que NO se garantiza
38
+
39
+ - **Un shell con interpreter embebido puede escribir fuera.** `python -c`,
40
+ `node -e`, heredocs y `eval` esconden paths que ningún análisis previo
41
+ puede extraer con certeza. Cuando el comando no se puede interpretar, se
42
+ deja pasar y **el tripwire lo detecta después**.
43
+ - **El tripwire no revierte.** Detecta y reporta; no deshace. Y vigila el
44
+ proyecto, no el filesystem entero.
45
+ - **Una escritura fuera del proyecto ya formada no se revierte.** El
46
+ contención previene; no limpia.
47
+
48
+ ---
49
+
50
+ ## 2. Las invariantes se evalúan donde corresponde
51
+
52
+ Ocho invariantes duros, en código, no en el prompt:
53
+
54
+ | Invariante | Dónde se evalúa |
55
+ |---|---|
56
+ | `reviewer-neq-owner` | Toda revisión |
57
+ | `gate-required-before-done` | Al cerrar la card |
58
+ | `evidence-required-before-done` | Al cerrar la card |
59
+ | `max-review-rejections` | Al revisar |
60
+ | `max-help-chain-hops` | Al enrutar ayuda |
61
+ | `max-cards` | Al planear |
62
+ | `max-agent-calls` | Al reclamar |
63
+ | `max-spawns-total` | Al arrancar un subagente |
64
+
65
+ > **Bug corregido en esta versión**: `closeCard` evaluaba los invariantes
66
+ > con la card en `review`, no en `done`. Como los dos invariantes de cierre
67
+ > solo tienen sentido en `done`, **nunca disparaban**. Ahora se validan
68
+ > contra el estado al que la card llegaría.
69
+
70
+ El `evidence-required-before-done` es **fail-closed**: si la evidencia no se
71
+ evaluó, la card no se cierra. "No lo sé" no es "sí".
72
+
73
+ ---
74
+
75
+ ## 3. El cierre exige evidencia, no solo checks verdes
76
+
77
+ Pirámide de verificación:
78
+
79
+ | Nivel | Qué es | Cómo se comprueba |
80
+ |---|---|---|
81
+ | 1 — Estático | Tipos, lint, secretos, dependencias | Los gates técnicos |
82
+ | 2 — Comportamiento | Unitarias, integración, regresión | El gate `test` + evidencia de artefacto |
83
+ | 3 — Requisitos | Cada criterio de aceptación con su evidencia | El grafo de evidencia |
84
+ | 4 — Auditoría | Revisión independiente, riesgos | Evidencia de auditoría (revisor ≠ owner) |
85
+
86
+ Una card se cierra solo si **los gates pasan Y cada requisito obligatorio
87
+ tiene evidencia suficiente**.
88
+
89
+ ### El grafo requisito → evidencia
90
+
91
+ Cada criterio de aceptación es un requisito. Cada evidencia (artefacto,
92
+ test, gate, inspección, auditoría) se vincula a un requisito con un nivel.
93
+ El grafo responde: *¿qué falta para poder cerrar?*
94
+
95
+ ```
96
+ T-00 · Implementar login
97
+ [✓] endpoint de login nivel 2 artefacto
98
+ [ ] sin errores de validación nivel 0 — FALTA
99
+ [!] cubierto por revisión independiente nivel 4 auditoría
100
+ ```
101
+
102
+ **Niveles y tope por tipo**: un artefacto llega a nivel 2; un test, a 2; un
103
+ gate, a 1 (o 2 si es la fase `test`); una inspección, a 3; una auditoría
104
+ (revisor ≠ owner), a 4. `evidenceFromAudit` **lanza** si owner y reviewer
105
+ son el mismo: una auto-revisión con cara de auditoría es peor que no tener
106
+ auditoría.
107
+
108
+ ---
109
+
110
+ ## 4. La concurrencia se sostiene
111
+
112
+ | Garantía | Mecanismo | Test |
113
+ |---|---|---|
114
+ | Un lease por task | Ciclo leer-evaluar-escribir bajo un lock de directorio | `concurrency.test.ts` |
115
+ | Seq único por canal | Asignación y escritura bajo el lock del canal | `concurrency.test.ts` |
116
+ | Idempotencia real | Check y escritura bajo el mismo lock | `concurrency.test.ts` |
117
+ | Un id nunca escapa del directorio | `safeId` + `safeJoin` | `ids`, `concurrency.test.ts` |
118
+ | Un JSON a medio escribir no se lee | `writeAtomic` (tmp + rename) | — |
119
+ | Un JSON vacío no se pone en cuarentena | `readJson` distingue "en curso" de "corrupto" | — |
120
+
121
+ ### El bug de Windows
122
+
123
+ `fs.mkdir` sobre un directorio que otro proceso está borrando da **EPERM**,
124
+ no `EEXIST`. Sin manejar ese caso, dos emisores concurrentes se rompían en
125
+ Windows. Está cubierto y hay un test de carrera de 40 rondas.
126
+
127
+ ---
128
+
129
+ ## 5. Una caída no destruye la corrida
130
+
131
+ `reconcile()` compara **estado vs artefactos vs filesystem** y reporta:
132
+
133
+ | Situación | Qué hace |
134
+ |---|---|
135
+ | Card en `review` sin `handoff.md` | El agente murió entregando. Se avisa. |
136
+ | Card en `done` sin `gates.md` | Se cerró sin gate. **Crítico.** |
137
+ | Card en progreso sin owner | Nadie la va a terminar. **Crítico.** |
138
+ | Lease de una card que no existe | Se libera. |
139
+ | Lease de una card terminada | Se libera. |
140
+ | Agente `working` sobre una card `done` | Se libera el agente. |
141
+ | Artefacto de una card inexistente | Se reporta. |
142
+ | Card bloqueada por dependencia irresoluble | Se reporta. |
143
+
144
+ **Lo que no hace**: completar cards, inventar evidencia ni "arreglar" el
145
+ estado. Un sistema que se auto-repara inventando evidencia es peor que uno
146
+ que dice "esto está roto".
147
+
148
+ ---
149
+
150
+ ## 6. El costo se controla
151
+
152
+ - El costo real viene del motor (`AssistantMessage.cost`), no se estima.
153
+ - Se contabiliza por card, por agente y por fase (implementación, review,
154
+ reparación).
155
+ - Presupuestos por corrida, por card y por fase.
156
+ - Estimación previa a una corrida grande.
157
+ - Detección de trabajo redundante (releer el mismo contexto, repetir el
158
+ mismo comando).
159
+ - Freno de emergencia: `oficina_stop` detiene la corrida; el
160
+ auto-continuación y el despacho se negan mientras esté detenida.
161
+
162
+ ---
163
+
164
+ ## 7. Lo que NO se promete
165
+
166
+ - **No es un sandbox.** Un plugin de OpenCode no puede contener el
167
+ filesystem entero. El guard es una política de aplicación, y su límite
168
+ está en la sección 1.
169
+ - **No garantiza que el objetivo del usuario se cumpla.** Garantiza que
170
+ **cada criterio de aceptación declarado** tenga evidencia. Si el criterio
171
+ estaba mal escrito, la evidencia lo va a reflejar.
172
+ - **No mide la calidad de la evidencia.** Un `handoff.md` que dice "listo"
173
+ es evidencia de nivel 2. Que sea verdad o no depende de la revisión.
174
+ - **No reemplaza al reviewer.** El invariante `reviewer-neq-owner` impide
175
+ que el owner se apruebe a sí mismo, pero no evalúa el juicio de quien
176
+ revisa.
177
+ - **El benchmark mide selección, no resultado.** Dice que el sistema elige
178
+ bien al rol. No dice que el agente haga bien el trabajo.
179
+
180
+ ---
181
+
182
+ ## Cómo se verifica todo esto
183
+
184
+ ```bash
185
+ npm test # 385 tests: adversariales, de carrera y de recuperación
186
+ npm run typecheck # strict + noUncheckedIndexedAccess
187
+ npm run coverage # cobertura por línea
188
+ npm run docs:check # la documentación coincide con el código
189
+ npm run bench:selection # el matcher le gana a las alternativas simples
190
+ ```
@@ -0,0 +1,144 @@
1
+ # Plan de cierre — AI-OFFICE v2.1
2
+
3
+ Fecha: 2026-10-22 · Base: `oficina-opencode@2.0.1` · Commit base: `5232fe3`
4
+
5
+ ## Contexto
6
+
7
+ El review externoidentified 5 prioridades. Contra el snapshot viejo (decía 201 tests,
8
+ pesos 40/35/20/5, 4 commits) algunas cosas **ya están resueltas**:
9
+
10
+ | Punto del review | Estado real |
11
+ |---|---|
12
+ | Pesos 40/35/20/5 | YA RESUELTO — ahora 35/28/18/10/5 + recall/focus de misión |
13
+ | "201 tests" | YA RESUELTO — 213 tests, 14 archivos |
14
+ | "4 commits" | YA RESUELTO — 6 commits |
15
+ | README clone con URL vieja | YA RESUELTO — 13 referencias corregidas, 2.0.1 publicado |
16
+
17
+ El resto **es real y verificado contra el código**:
18
+
19
+ | # | Hallazgo verificado | Dónde |
20
+ |---|---|---|
21
+ | 1 | `path.relative` sin realpath → **escape por symlink** | `scopeGuard.ts:95-98` |
22
+ | 2 | `mayTouch: ["**"]` autoriza **cualquier** path, incluso `../../etc/passwd` | `scopeGuard.ts:53` |
23
+ | 3 | Solo cubre `edit/write/patch/multiedit/apply_patch` — **no** `bash` ni `task` | `scopeGuard.ts:23` |
24
+ | 4 | `extractPath` de `patch` solo lee el **primer** archivo del diff | `scopeGuard.ts:67-74` |
25
+ | 5 | Sin protección de archivos sensibles (`.npmrc`, `.git/**`, `*.pem`) | — |
26
+ | 6 | Subagente delegado **no hereda scope**: sin binding de sesión, el guard sale temprano | `scopeGuard.ts:88-90` |
27
+ | 7 | `LeaseStore.acquire` es **TOCTOU**: read → check → write. Doble claim posible | `leases.ts:42-57` |
28
+ | 8 | `Bus.nextSeq` es TOCTOU → **seq duplicados** entre emisores concurrentes | `bus.ts:88-99` |
29
+ | 9 | Idempotencia del bus es check-then-write → **mensajes duplicados** | `bus.ts:108-111` |
30
+ | 10 | `cardId`/`agentId`/`messageId` sin sanitizar → **path traversal** al escribir | `paths.ts:153-165`, `bus.ts:211` |
31
+ | 11 | `spawnsUsed` nunca se incrementa → invariante `max-spawns-total` **nunca dispara** | `retry.ts:83` sin callers |
32
+ | 12 | `transitionRun` importado y **nunca llamado**; el status se muta directo | `office.ts:28` |
33
+ | 13 | `state.pendingGates` nunca se puebla; `state.iteration` nunca se incrementa | `types.ts:352` |
34
+ | 14 | `savePlan` no valida `maxCards` → se pueden planear 50 cards con límite 20 | `office.ts:467` |
35
+ | 15 | `LeaseStore.heartbeat` existe y **nadie lo llama** → los leases expiran duro | `leases.ts:62` |
36
+ | 16 | No hay grafo de requisitos→evidencia | — |
37
+ | 17 | No hay control de costo, tokens, tiempo ni parada de emergencia | — |
38
+ | 18 | No hay benchmark de selección de agentes | — |
39
+ | 19 | Cifras de la documentación hardcodeadas, sin fuente única | `README.md` |
40
+ | 20 | Sin matriz de compatibilidad de versiones | — |
41
+
42
+ ## Hallazgo nuevo (no estaba en el review)
43
+
44
+ La API del plugin tiene **3 hooks sin usar** que resuelven partes del problema mejor
45
+ de lo que había planeado:
46
+
47
+ - `permission.ask` → **denegar a nivel framework**, no solo en nuestro hook.
48
+ - `tool.execute.after` → **tripwire**: detectar escrituras que pasaron el hook.
49
+ - `chat.message` / `chat.params` → **nombre del agente y modelo por sesión**
50
+ (clave para P5 y para resolver el scope de subagentes).
51
+ - `event` con `message.updated` → `AssistantMessage.cost` + `tokens{input,output,reasoning,cache}`
52
+ + `modelID` + `providerID` + latencia. **El costo ya viene del motor.**
53
+ - `experimental.compaction.autocontinue` → **freno de emergencia** (desactivar el auto-continuar).
54
+
55
+ ## Diseño
56
+
57
+ ### P1 — Scope guard real (3 capas)
58
+
59
+ | Capa | Mecanismo | Cubre |
60
+ |---|---|---|
61
+ | 1. Framework | `permission.ask` → `deny` | todas las tools, **incluidas las de subagentes** |
62
+ | 2. Propio | `tool.execute.before` → `sandbox.check` | resolución real, symlinks, sensibles, shell |
63
+ | 3. Detección | `tool.execute.after` → tripwire | lo que se coló (writes por shell) |
64
+
65
+ `src/core/sandbox.ts` (nuevo):
66
+ - `resolveTarget(projectRoot, raw)` → verifica NUL, normaliza con `path.resolve`,
67
+ **camina cada componente** con `lstat` para detectar symlinks, resuelve con
68
+ `realpath` el ancestro existente más profundo, y **re-valida la contención**.
69
+ - `SENSITIVE_PATTERNS` → `.env*`, `.npmrc`, `.netrc`, `.git/**`, `*.pem|key|p12`,
70
+ `id_rsa*`, `.ssh/**`, `.aws/**`, `.gnupg/**`, `credentials.json`, `service-account*.json`.
71
+ - `analyzeShellCommand(cmd)` → extrae paths de escritura (`>`, `>>`, `tee`, `cp`,
72
+ `mv`, `rm`, `sed -i`, `dd`, `install`, `truncate`, `touch`, `python -c`, `node -e`).
73
+ - `checkScope(...)` → veredicto único con motivo legible.
74
+
75
+ Resolución de scope de subagentes: mapa `sessionID → agent` construido con
76
+ `chat.message` + `message.updated`, y `agent → card` desde el roster. El scope
77
+ **hereda por el árbol de padres** de la sesión.
78
+
79
+ ### P2 — Requirement-to-Evidence Graph
80
+
81
+ `src/core/evidence.ts` (nuevo):
82
+ - Cada `acceptance[]` de una card es un **requisito** (nivel 1 = obligatorio).
83
+ - Cada requisito se enlaza a: artefactos, tests citados, gates, y veredicto manual.
84
+ - Niveles: 1 estático, 2 comportamiento, 3 validación de requisitos, 4 auditoría.
85
+ - Invariante nuevo: `evidence-required-before-done` — sin evidencia de todos los
86
+ requisitos obligatorios, la card **no** pasa a `done`.
87
+ - Gate nuevo: fase `verify`.
88
+
89
+ ### P3 — Documentación generada
90
+
91
+ - `src/core/docsync.ts` → única fuente de verdad: escanea el catálogo real y
92
+ emite `{ agents, divisions, divisionsList, tools, hooks, tests }`.
93
+ - `scripts/sync-docs.ts` → regenera las cifras en README y docs; **falla** si hay drift.
94
+ - CI: paso que corre el script y falla si el diff no está vacío.
95
+ - `docs/COMPATIBILITY.md` → matriz OpenCode × `@opencode-ai/plugin` generada.
96
+
97
+ ### P4 — Concurrencia y recuperación
98
+
99
+ `src/core/atomic.ts` (nuevo):
100
+ - `putOnce(path, content)` → `open(path, "wx")`: winner único, losers leen el existente.
101
+ - `allocateSeq(channelDir)` → asignacion atomica con `wx` + reintento incremental.
102
+ - `withLock(lockPath, fn)` → mutex por directorio (`mkdir` atómico).
103
+
104
+ Aplicado a: lease acquire (O_EXCL + takeover de expirado), seq del bus,
105
+ idempotencia del bus, y `safeId()` para todos los ids que se usan como nombre de archivo.
106
+
107
+ `src/core/recovery.ts` (nuevo): reconciliación al arrancar — estado vs artefactos
108
+ vs filesystem. Detecta: card en `review` sin artefactos, lease huérfano, artefacto
109
+ sin card, `done` sin gate.
110
+
111
+ ### P5 — Costo y control
112
+
113
+ `src/core/cost.ts` (nuevo): ledger por corrida/card/agente/fase.
114
+ - Entrada: evento `message.updated` → `AssistantMessage.cost` + `tokens` + modelo.
115
+ - Presupuestos: por corrida (tokens/USD), por card, y separados por fase
116
+ (implementación / review / reparación).
117
+ - Estimación previa a una corrida grande.
118
+ - Métricas: costo por requisito completado y verificado, tokens por card,
119
+ lecturas repetidas del mismo path (trabajo redundante).
120
+ - Freno: `oficina_stop` + `experimental.compaction.autocontinue` desactivado +
121
+ el hook de idle respeta el flag.
122
+
123
+ ## Orden de ejecución
124
+
125
+ 1. `types.ts` — contratos nuevos (todo lo demás depende de esto).
126
+ 2. `sandbox.ts` + `scopeGuard.ts` + tests adversariales. **(P1)**
127
+ 3. `atomic.ts` + `leases.ts` + `bus.ts` + `paths.ts` + tests de concurrencia. **(P4)**
128
+ 4. `evidence.ts` + gate `verify` + invariante. **(P2)**
129
+ 5. `cost.ts` + tools + hook de costo. **(P5)**
130
+ 6. `recovery.ts` + hook de arranque. **(P4)**
131
+ 7. `docsync.ts` + `scripts/sync-docs.ts` + CI + `COMPATIBILITY.md`. **(P3)**
132
+ 8. Benchmark de selección.
133
+ 9. Verificación total: typecheck, tests, cobertura, build, e2e, npm pack, publish.
134
+
135
+ ## Criterio de terminado
136
+
137
+ - [ ] typecheck strict limpio
138
+ - [ ] todos los tests pasan (base: 213)
139
+ - [ ] cobertura ≥ 87% stmts
140
+ - [ ] build limpio
141
+ - [ ] e2e verde contra el paquete instalado
142
+ - [ ] `npm pack` sin warnings
143
+ - [ ] documentación generada coincide con el código (CI lo verifica)
144
+ - [ ] publicado en npm
package/docs/QUE-ES.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # ¿Qué es AI-OFFICE?
2
2
 
3
+ <!-- BEGIN GENERATED: facts -->
4
+ | | |
5
+ |---|---|
6
+ | **Paquete** | `oficina-opencode` v2.1.1 |
7
+ | **Tools** | 16 |
8
+ | **Hooks** | 7 |
9
+ | **API de plugin** | `@opencode-ai/plugin@1.15.11` |
10
+ | **Node** | >=20 |
11
+ | **Catálogo** | 258 agentes resolubles en 18 divisiones |
12
+ | **Divisiones** | academic, design, engineering, finance, game-development, gis, healthcare, marketing, paid-media, product, project-management, research, sales, spatial-computing, specialized, strategy, support, testing |
13
+ | **Límites** | 20 cards · 40 llamadas · 20 spawns · 10 agentes |
14
+ | **Cobertura mínima** | 60% |
15
+ <!-- END GENERATED: facts -->
16
+
17
+
3
18
  > Una oficina de agentes de IA para OpenCode. Le das un objetivo, y el equipo lo trabaja, lo revisa, lo verifica y no para hasta terminarlo.
4
19
 
5
20
  Este documento explica el sistema sin jerga. Si querés saber **cómo está construido por dentro**, está [`docs/ARCHITECTURE.md`](ARCHITECTURE.md). Si querés **usarlo**, está el [README](../README.md).
@@ -0,0 +1,27 @@
1
+ {
2
+ "agents": 258,
3
+ "divisions": 18,
4
+ "divisionList": [
5
+ "academic",
6
+ "design",
7
+ "engineering",
8
+ "finance",
9
+ "game-development",
10
+ "gis",
11
+ "healthcare",
12
+ "marketing",
13
+ "paid-media",
14
+ "product",
15
+ "project-management",
16
+ "research",
17
+ "sales",
18
+ "spatial-computing",
19
+ "specialized",
20
+ "strategy",
21
+ "support",
22
+ "testing"
23
+ ],
24
+ "source": "C:\\Users\\Marcos\\agency-agents",
25
+ "generatedAt": "2026-10-22T02:57:59.378Z",
26
+ "sourceCommit": "4e3ac3c"
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oficina-opencode",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "description": "Oficina de Agents — malla de coordinadores + peer mesh para OpenCode. Orquestación durable de subagentes con bus de mensajes en disco, roster de roles, leases, gates de calidad y escalamiento por invariantes.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -68,7 +68,10 @@
68
68
  "test": "vitest run",
69
69
  "test:watch": "vitest",
70
70
  "coverage": "npm run build && vitest run --coverage",
71
- "prepublishOnly": "npm run typecheck && npm run test && npm run build"
71
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build && npm run docs:check",
72
+ "docs:sync": "tsx scripts/sync-docs.ts",
73
+ "docs:check": "tsx scripts/sync-docs.ts --check",
74
+ "bench:selection": "tsx benchmarks/selection/run.ts"
72
75
  },
73
76
  "dependencies": {
74
77
  "@opencode-ai/plugin": "^1.15.11",
@@ -76,6 +79,7 @@
76
79
  },
77
80
  "devDependencies": {
78
81
  "@types/node": "^24.12.2",
82
+ "tsx": "^4.20.3",
79
83
  "typescript": "^5.8.2",
80
84
  "vitest": "^3.2.4",
81
85
  "@vitest/coverage-v8": "^3.2.4"