ozali 0.7.1 → 0.9.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.
@@ -6,7 +6,7 @@ import fs from "node:fs";
6
6
  import os from "node:os";
7
7
  import path from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
- import { engramAssetName, pickEngramAsset } from "../lib/util.mjs";
9
+ import { engramAssetName, pickEngramAsset, toPortablePath, fromPortablePath } from "../lib/util.mjs";
10
10
 
11
11
  const HERE = path.dirname(fileURLToPath(import.meta.url));
12
12
  const BIN = path.resolve(HERE, "..", "bin", "ozali.mjs");
@@ -257,6 +257,8 @@ test("workspace escribe manifiesto + .code-workspace + jarvis y es idempotente",
257
257
  assert.ok(fs.existsSync(wsFile), ".code-workspace escrito");
258
258
  assert.equal(JSON.parse(fs.readFileSync(wsFile, "utf8")).folders.length, 2, "multi-root con 2 folders");
259
259
  assert.match(fs.readFileSync(path.join(root, "CLAUDE.md"), "utf8"), /ozali-workspace-jarvis:start/, "bloque jarvis en CLAUDE.md");
260
+ // Track 2: la skill ozali queda instalada en la raíz para calibrar miembros desde el workspace
261
+ assert.ok(fs.existsSync(path.join(root, ".claude", "skills", "ozali", "SKILL.md")), "skill ozali en la raíz");
260
262
 
261
263
  // idempotencia: re-correr no duplica
262
264
  run(["workspace", "--yes", "--no-trust"], root);
@@ -268,6 +270,51 @@ test("workspace escribe manifiesto + .code-workspace + jarvis y es idempotente",
268
270
  }
269
271
  });
270
272
 
273
+ test("workspace --doctor revisa cada miembro y saca resumen consolidado", () => {
274
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "ozali-ws-"));
275
+ try {
276
+ wsRepo(root, "api", { config: true, cdk: true, pkg: { name: "api", version: "1.0.0" } });
277
+ wsRepo(root, "web", { config: true, cdk: true, pkg: { name: "web" } });
278
+ const { stdout } = run(["workspace", "--doctor"], root, true); // exit 1 esperado (repos sin sot/engram)
279
+ assert.match(stdout, /health-check/i, "corre doctor por repo");
280
+ assert.match(stdout, /Resumen del workspace/, "imprime el resumen consolidado");
281
+ assert.match(stdout, /api/, "menciona el repo api en el resumen");
282
+ assert.match(stdout, /web/, "menciona el repo web en el resumen");
283
+ assert.ok(!fs.existsSync(path.join(root, "ozali-workspace.json")), "--doctor no escribe config");
284
+ } finally {
285
+ fs.rmSync(root, { recursive: true, force: true });
286
+ }
287
+ });
288
+
289
+ test("workspace --update actualiza cada miembro y salta los sin init", () => {
290
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "ozali-ws-"));
291
+ try {
292
+ wsRepo(root, "api", { config: true, cdk: true, pkg: { name: "api", version: "1.0.0" } });
293
+ wsRepo(root, "bare", { pkg: { name: "bare" } }); // missing-init → se salta
294
+ const { stdout } = run(["workspace", "--update", "--yes", "--no-jarvis"], root);
295
+ assert.match(stdout, /Resumen del workspace/, "imprime el resumen consolidado");
296
+ assert.match(stdout, /Sin ozali init/, "salta el repo sin init");
297
+ assert.ok(!fs.existsSync(path.join(root, "ozali-workspace.json")), "--update no escribe config");
298
+ } finally {
299
+ fs.rmSync(root, { recursive: true, force: true });
300
+ }
301
+ });
302
+
303
+ test("workspace no trata subcarpetas de un repo como miembros (solo repos propios)", () => {
304
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "ozali-ws-"));
305
+ try {
306
+ execFileSync("git", ["init", "-q"], { cwd: root }); // la raíz ES un repo git
307
+ fs.mkdirSync(path.join(root, "src")); // subcarpeta simple, NO repo propio
308
+ fs.writeFileSync(path.join(root, "src", "index.js"), "//\n");
309
+ wsRepo(root, "pkg", { config: true, cdk: true, pkg: { name: "pkg", version: "1.0.0" } }); // repo propio anidado
310
+ const { stdout } = run(["workspace", "--dry-run"], root);
311
+ assert.match(stdout, /\bpkg\b/, "incluye el repo propio anidado");
312
+ assert.doesNotMatch(stdout, /\bsrc\b/, "no incluye la subcarpeta que no es repo propio");
313
+ } finally {
314
+ fs.rmSync(root, { recursive: true, force: true });
315
+ }
316
+ });
317
+
271
318
  test("init --dry-run no escribe nada", () => {
272
319
  const dir = tmpProject();
273
320
  try {
@@ -336,6 +383,49 @@ test("engramAssetName respeta la convención de release por SO/arch", () => {
336
383
  assert.equal(engramAssetName("freebsd", "x64", "1.17.0"), null, "SO no soportado → null");
337
384
  });
338
385
 
386
+ test("toPortablePath convierte absolutos a ~ o relativo", () => {
387
+ const home = os.homedir();
388
+ const cwd = "/project";
389
+ assert.equal(toPortablePath(path.join(home, ".ozali", "knowledge"), cwd), "~/.ozali/knowledge");
390
+ assert.equal(toPortablePath("/project/.k", "/project"), ".k");
391
+ assert.equal(toPortablePath("/outside", "/project"), "/outside");
392
+ });
393
+
394
+ test("fromPortablePath expande ~ y resuelve relativos", () => {
395
+ const home = os.homedir();
396
+ const cwd = "/project";
397
+ assert.equal(fromPortablePath("~/.ozali/knowledge", cwd), path.join(home, ".ozali", "knowledge"));
398
+ assert.equal(fromPortablePath(".k", cwd), "/project/.k");
399
+ assert.equal(fromPortablePath("/abs", cwd), "/abs");
400
+ });
401
+
402
+ test("init guarda knowledgeRepo como path portable", () => {
403
+ const dir = tmpProject();
404
+ try {
405
+ run(["init", "--yes", "--no-engram", "--no-trust", "--no-jarvis", "--agent", "claude-code", "--scope", "project", "--knowledge-repo", path.join(dir, ".k")], dir);
406
+ const cfg = JSON.parse(fs.readFileSync(path.join(dir, ".ozali", "config.json"), "utf8"));
407
+ assert.ok(cfg.knowledgeRepo, "config tiene knowledgeRepo");
408
+ assert.ok(!path.isAbsolute(cfg.knowledgeRepo), "knowledgeRepo NO debe ser absoluto");
409
+ assert.equal(cfg.knowledgeRepo, ".k", "knowledgeRepo debe ser relativo al proyecto");
410
+ } finally {
411
+ fs.rmSync(dir, { recursive: true, force: true });
412
+ }
413
+ });
414
+
415
+ test("sync resuelve knowledgeRepo portable correctamente", () => {
416
+ const dir = tmpProject();
417
+ try {
418
+ run(["init", "--yes", "--no-engram", "--no-trust", "--no-jarvis", "--agent", "claude-code", "--scope", "project", "--knowledge-repo", path.join(dir, ".k")], dir);
419
+ // Crear algo en .ozali/docs/ para que sync tenga qué copiar
420
+ fs.mkdirSync(path.join(dir, ".ozali", "docs"), { recursive: true });
421
+ fs.writeFileSync(path.join(dir, ".ozali", "docs", "test.md"), "# test\n");
422
+ const { stdout } = run(["sync", "--yes"], dir);
423
+ assert.match(stdout, /copiados|Docs copiados/, "sync debe encontrar el repo de conocimiento");
424
+ } finally {
425
+ fs.rmSync(dir, { recursive: true, force: true });
426
+ }
427
+ });
428
+
339
429
  test("pickEngramAsset ignora tags no-semver sin binarios (pi-v*) y draft/prerelease", () => {
340
430
  // Shape real de la API de GitHub: el 'latest' es pi-v0.1.9 (0 assets); los binarios
341
431
  // viven en tags vX.Y.Z. Además metemos un prerelease más nuevo que debe saltarse.
@@ -0,0 +1,182 @@
1
+ # Compilado y uso local (para probar el repo clonado)
2
+
3
+ ← [README](../README.md)
4
+
5
+ Guía para cuando **clonas este repositorio** y quieres compilarlo/empaquetarlo y **probarlo en local**
6
+ antes de publicarlo o para desarrollar sobre él.
7
+
8
+ > **Aclaración importante — no hay "compilación".** `ozali` es un CLI **Node de cero dependencias y
9
+ > ESM puro** (solo usa módulos `node:*`). **No hay build step**, ni TypeScript que transpilar, ni
10
+ > `dist/`. "Compilar" aquí significa una de dos cosas: (a) **correrlo tal cual** desde el fuente, o
11
+ > (b) **empaquetar el artefacto distribuible** (`npm pack` → un `.tgz`, el "archivo base") para
12
+ > instalarlo y probarlo como lo haría un usuario final.
13
+
14
+ ---
15
+
16
+ ## Requisitos
17
+
18
+ - **Node ≥ 16** (`node --version`). Es lo único imprescindible.
19
+ - **git** (el CLI lee identidad/estado del repo).
20
+ - **pnpm** o **npm** (opcional; solo para `test`, `pack`, `link` o instalación global). No hay `npm
21
+ install` que correr: **el repo no tiene dependencias**.
22
+
23
+ ```bash
24
+ git clone https://github.com/jimmybathrooms/ozali.git
25
+ cd ozali
26
+ ```
27
+
28
+ ---
29
+
30
+ ## 1. Correr desde el fuente (sin instalar nada)
31
+
32
+ Como no hay dependencias ni build, el CLI corre directo:
33
+
34
+ ```bash
35
+ node cli/bin/ozali.mjs --version # imprime la versión de package.json (p. ej. 0.8.0)
36
+ node cli/bin/ozali.mjs --help # lista de comandos
37
+ node cli/bin/ozali.mjs doctor # health-check en el cwd
38
+ ```
39
+
40
+ Esta es la forma **más auditable**: ejecutas exactamente el código que ves, sin capa de instalación.
41
+
42
+ ---
43
+
44
+ ## 2. Correr la batería de pruebas
45
+
46
+ ```bash
47
+ npm test # (o: pnpm test) → node --test cli/test/*.test.mjs
48
+ ```
49
+
50
+ Debe salir todo en verde. Los tests son `node:test` puro (sin dependencias) y cubren init, doctor,
51
+ update, workspace, seguridad del paquete y los helpers de release de Engram.
52
+
53
+ ---
54
+
55
+ ## 3. Empaquetar el "archivo base" (tarball distribuible)
56
+
57
+ El artefacto que se publicaría en npm es un tarball `.tgz`. Se genera con:
58
+
59
+ ```bash
60
+ npm pack # (o: pnpm pack)
61
+ # → crea ozali-<version>.tgz en la raíz del repo (el cwd)
62
+ ```
63
+
64
+ Para tu versión actual produce, por ejemplo, **`ozali-0.8.0.tgz`**. Es **idéntico** a lo que recibiría
65
+ un usuario desde npm: incluye solo lo declarado en `package.json > files` (`cli/`, `skill/`,
66
+ `skill-commit/`, `templates/`, `docs/`, `README.md`, `LICENSE`).
67
+
68
+ Para **ver qué contiene sin generar el archivo**:
69
+
70
+ ```bash
71
+ npm pack --dry-run # lista archivos, tamaño y nombre del tarball, sin escribir nada
72
+ ```
73
+
74
+ > El `.tgz` es un artefacto temporal: **no lo commitees**. Bórralo tras probar (o añádelo a
75
+ > `.gitignore` como `ozali-*.tgz`).
76
+
77
+ ### Extraer / inspeccionar el tarball
78
+
79
+ El tarball es un `.tar.gz` estándar. Para extraerlo y revisar el "archivo base" tal como quedaría
80
+ instalado:
81
+
82
+ ```bash
83
+ tar -xzf ozali-0.8.0.tgz # crea una carpeta package/ con el contenido publicable
84
+ ls -R package # inspecciona la estructura
85
+ tar -tzf ozali-0.8.0.tgz # o solo LISTA el contenido, sin extraer
86
+ ```
87
+
88
+ La carpeta `package/` es exactamente lo que npm coloca en `node_modules/ozali` al instalar.
89
+
90
+ ---
91
+
92
+ ## 4. Usarlo en local para pruebas
93
+
94
+ Tres formas, de la más cómoda para desarrollar a la más fiel al usuario final:
95
+
96
+ ### a) `npm link` — bucle de desarrollo (refleja tus cambios al instante)
97
+
98
+ ```bash
99
+ npm link # (o: pnpm link --global)
100
+ # ahora 'ozali' está en tu PATH, apuntando al clon local
101
+ ozali --version
102
+ ozali doctor
103
+ ```
104
+
105
+ Como es un symlink al repo, cualquier edición que hagas en `cli/` se ve de inmediato (no hace falta
106
+ re-linkear). Para deshacer: `npm unlink -g ozali` (o `npm rm -g ozali`).
107
+
108
+ ### b) Instalar el tarball global — prueba el artefacto real
109
+
110
+ Instala el `.tgz` como lo haría un usuario, para validar el paquete exacto que publicarías:
111
+
112
+ ```bash
113
+ npm i -g ./ozali-0.8.0.tgz # (o: pnpm add -g ./ozali-0.8.0.tgz)
114
+ ozali --version
115
+ ```
116
+
117
+ Para desinstalar: `npm rm -g ozali`.
118
+
119
+ ### c) Efímero, sin instalar en el PATH — como `npx`/`pnpm dlx`
120
+
121
+ ```bash
122
+ npx ./ozali-0.8.0.tgz init # corre init desde el tarball, sin dejar 'ozali' instalado
123
+ pnpm dlx ./ozali-0.8.0.tgz init # equivalente con pnpm
124
+ ```
125
+
126
+ > **Nota de seguridad:** el flag `--ignore-scripts` que recomendamos para instalar desde npm es
127
+ > irrelevante aquí, porque `ozali` **no tiene dependencias ni lifecycle scripts** (nada que ejecutar
128
+ > en install). Detalle en [security.md](security.md).
129
+
130
+ ---
131
+
132
+ ## 5. Probar en un proyecto sandbox
133
+
134
+ Crea un repo de juguete y corre el flujo real sin ensuciar nada:
135
+
136
+ ```bash
137
+ mkdir /tmp/ozali-sandbox && cd /tmp/ozali-sandbox && git init
138
+
139
+ # opción sin instalar: apunta al bin del clon
140
+ node /ruta/al/clon/ozali/cli/bin/ozali.mjs init --dry-run # muestra el plan, no escribe
141
+ node /ruta/al/clon/ozali/cli/bin/ozali.mjs init --yes --no-engram # init real, sin instalar Engram
142
+ node /ruta/al/clon/ozali/cli/bin/ozali.mjs doctor # verifica el resultado
143
+ ```
144
+
145
+ Para probar **workspaces multi-repo**, crea una carpeta con varios repos git dentro y corre:
146
+
147
+ ```bash
148
+ node /ruta/al/clon/ozali/cli/bin/ozali.mjs workspace --dry-run # inventario + plan, sin escribir
149
+ node /ruta/al/clon/ozali/cli/bin/ozali.mjs workspace --doctor # health-check de todos los miembros
150
+ ```
151
+
152
+ (Si hiciste `npm link`, sustituye todo lo anterior por simplemente `ozali <comando>`.)
153
+
154
+ ---
155
+
156
+ ## 6. Verificar y limpiar
157
+
158
+ ```bash
159
+ ozali --version # debe coincidir con la "version" de package.json
160
+ npm rm -g ozali # desinstala el global (link o tarball)
161
+ rm -f ozali-*.tgz # borra el tarball de prueba
162
+ rm -rf package/ # borra la extracción del tarball, si la hiciste
163
+ ```
164
+
165
+ ---
166
+
167
+ ## Resumen
168
+
169
+ | Quiero… | Comando |
170
+ |---|---|
171
+ | Correr sin instalar | `node cli/bin/ozali.mjs <cmd>` |
172
+ | Correr los tests | `npm test` |
173
+ | Empaquetar el distribuible | `npm pack` → `ozali-<version>.tgz` |
174
+ | Ver el contenido del paquete | `npm pack --dry-run` |
175
+ | Extraer el tarball | `tar -xzf ozali-<version>.tgz` → `package/` |
176
+ | Desarrollar con `ozali` en PATH | `npm link` |
177
+ | Probar el artefacto real | `npm i -g ./ozali-<version>.tgz` |
178
+ | Probar efímero | `npx ./ozali-<version>.tgz init` |
179
+
180
+ > ¿Buscas **publicar** una versión nueva (no solo probar)? Eso es otro flujo: bump de `version` en
181
+ > `package.json` → commit → tag `vX.Y.Z` → push; el tag dispara la publicación a npm con provenance
182
+ > vía GitHub Actions.
@@ -81,6 +81,7 @@ Lleva el histórico y la memoria al repo de conocimiento del equipo. Tus compañ
81
81
  | Comando | Qué hace |
82
82
  |---|---|
83
83
  | `ozali init` | Prepara el proyecto (lo corres una vez por repo). |
84
+ | `ozali workspace` | Configura varios repos de una carpeta para trabajar en conjunto (lo corres en la carpeta raíz). |
84
85
  | `ozali doctor` | Revisa que todo esté bien. Es solo lectura, no cambia nada. |
85
86
  | `ozali update` | Actualiza skill ozali + ozali-jarvis + permisos (y guía cómo regenerar cdk). |
86
87
  | `ozali sync` | Sube el histórico/memoria al repo de equipo. |
@@ -171,6 +172,51 @@ Atajos: `ozali audit --tui` abre un navegador interactivo de la memoria; `ozali
171
172
  busca un término; `ozali audit --general` fuerza el alcance general. Si no tienes Engram, audita el
172
173
  histórico local de `.ozali/docs/`.
173
174
 
175
+ ---
176
+
177
+ ## Trabajar con varios repos a la vez (`ozali workspace`)
178
+
179
+ ¿Tienes una carpeta que agrupa **varios repositorios que se hablan entre sí** (una API, su front, una
180
+ librería compartida) y quieres que tu agente trabaje con todos juntos? Corre **una vez** en esa
181
+ carpeta raíz:
182
+
183
+ ```bash
184
+ ozali workspace # escanea los repos hijos, prepara los que falten y arma la config conjunta
185
+ ozali workspace --dry-run # solo te muestra el inventario y el plan, sin escribir nada
186
+ ozali workspace --yes # sin preguntas: acepta los defaults y las referencias detectadas
187
+ ozali workspace --depth 2 # busca repos hasta 2 niveles (si están en subcarpetas de grupo)
188
+ ```
189
+
190
+ Qué hace, en orden:
191
+
192
+ 1. **Revisa** cada repo y te dice cómo está: `✔ listo`, `⚠ sin calibrar` (le falta correr la skill
193
+ `ozali`) o `✖ sin init` (nunca se preparó).
194
+ 2. **Prepara** los que están `✖ sin init` corriendo `ozali init` por ti, y te **guía** para calibrar
195
+ los que están `⚠ sin calibrar` (abrir ese repo y escribir *"diagnostica el proyecto"* — eso lo
196
+ hace tu agente, no el comando).
197
+ 3. **Detecta las referencias** entre repos (dependencias npm cruzadas, submódulos, `docker-compose`) y
198
+ te pide confirmarlas.
199
+ 4. **Escribe la configuración** para trabajar en conjunto:
200
+ - `ozali-workspace.json` — el mapa de repos, su estado y sus referencias (esto **sí** se commitea).
201
+ - `<carpeta>.code-workspace` — ábrelo en VSCode/Antigravity y verás todos los repos juntos.
202
+ - **ozali-workspace-jarvis** en el `CLAUDE.md`/`AGENTS.md` de la raíz: un orquestador que coordina
203
+ los repos según ese mapa y delega el trabajo de código en el `cdk` de cada uno.
204
+
205
+ **Sin ir a cada proyecto:** desde la carpeta raíz puedes operar todos los repos de una vez:
206
+
207
+ ```bash
208
+ ozali workspace --doctor # revisa la salud de todos los repos y saca un resumen
209
+ ozali workspace --update # actualiza skills, permisos y jarvis de todos los repos
210
+ ```
211
+
212
+ Y para **calibrar** (generar `cdk`) los que estén `⚠ sin calibrar`, ya **no** abres cada proyecto:
213
+ abre tu agente en la carpeta raíz y pídele *"calibra los repos pendientes"* — el orquestador los
214
+ recorre **uno por uno** (cada uno con su aprobación) sin que cambies de proyecto.
215
+
216
+ Es **idempotente**: vuelve a correrlo cuando agregues un repo o cambien las referencias — no duplica
217
+ nada. Cada repo conserva su autonomía (su propio jarvis, su `cdk` y su memoria); el workspace solo
218
+ los **coordina**. Detalle completo en [workspaces.md](workspaces.md).
219
+
174
220
  ## Preguntas frecuentes
175
221
 
176
222
  **¿Tengo que usar Engram?**
@@ -185,6 +231,10 @@ Lo corriste con `dlx`/`npx`, que es temporal. Instálalo global: `pnpm add -g oz
185
231
  **¿Esto ensucia mi repositorio?**
186
232
  No. El histórico y la memoria se aíslan (`.ozali/`, `.engram/` van al `.gitignore`).
187
233
 
234
+ **Tengo varios repos que dependen entre sí. ¿Los conecto?**
235
+ Sí: abre la carpeta que los agrupa y corre `ozali workspace`. Prepara los que falten y arma una
236
+ config para que tu agente trabaje con todos juntos. Ver [arriba](#trabajar-con-varios-repos-a-la-vez-ozali-workspace).
237
+
188
238
  **Veo "this workspace has not been trusted" y mis permisos no aplican.**
189
239
  Es el aviso de seguridad de Claude Code: ignora los permisos del proyecto hasta confiar en él.
190
240
  Deja que `init` lo marque como confiable, o abre Claude Code en la carpeta y acepta el diálogo una
@@ -0,0 +1,93 @@
1
+ # Integración con Obsidian
2
+
3
+ ← [README](README.md)
4
+
5
+ Ozali puede exportar la memoria de equipo (Engram) y el histórico de hitos a un **vault de Obsidian**: un espacio navegable con wikilinks, grafo de conocimiento y Mapas de Contenido (MOCs).
6
+
7
+ ## ¿Qué es el vault?
8
+
9
+ Es una carpeta `obsidian/` dentro de tu **repo de conocimiento** (`ozali-knowledge/` o `~/.ozali/knowledge/`). Contiene:
10
+
11
+ - **README.md** — índice humano del vault
12
+ - **MOCs/** — Mapas de Contenido auto-generados (proyectos, decisiones, bugfixes, métricas)
13
+ - **Memoria/** — exportación de Engram (decisiones, bugfixes, resúmenes técnicos)
14
+ - **.obsidian/** — configuración compartida del equipo (plugins core, hotkeys)
15
+
16
+ ## Requisitos
17
+
18
+ - [Obsidian](https://obsidian.md) instalado (opt-in; ozali lo detecta y ofrece descargar)
19
+ - Engram instalado (`engram obsidian-export` es el motor de exportación)
20
+
21
+ ## Uso
22
+
23
+ ### Generar / actualizar el vault
24
+
25
+ ```bash
26
+ # Desde cualquier proyecto ozali
27
+ ozali sync --obsidian
28
+ ```
29
+
30
+ Esto:
31
+ 1. Exporta Engram al vault (`engram obsidian-export`)
32
+ 2. Actualiza los MOCs dinámicos (lista de proyectos, etc.)
33
+ 3. Commit en el repo de conocimiento
34
+
35
+ ### Sin el flag `--obsidian`
36
+
37
+ ```bash
38
+ ozali sync
39
+ ```
40
+
41
+ Si el vault ya existe, ozali pregunta: *"¿Exportar memoria a Obsidian vault?"*. Responde `sí` para actualizar.
42
+
43
+ ### Abrir el vault en Obsidian
44
+
45
+ 1. Abre Obsidian
46
+ 2. Selecciona **"Open folder as vault"**
47
+ 3. Navega a tu `ozali-knowledge/obsidian/` (o `~/.ozali/knowledge/obsidian/`)
48
+
49
+ ### Estructura recomendada del repo de conocimiento
50
+
51
+ ```
52
+ ozali-knowledge/
53
+ .git/
54
+ engram/ ← export chunks de Engram
55
+ projects/
56
+ {proyecto}/
57
+ docs/ ← docs por hito
58
+ obsidian/ ← 🆕 VAULT DE OBSIDIAN
59
+ .obsidian/
60
+ README.md
61
+ MOCs/
62
+ Memoria/
63
+ ```
64
+
65
+ ## Configuración compartida
66
+
67
+ La carpeta `.obsidian/` del vault **sí se commitea** en el repo de conocimiento. Contiene:
68
+ - Plugins core habilitados (graph, backlinks, quick switcher)
69
+ - Config de lectura (readable line length)
70
+
71
+ **No se commitean** settings personales: temas, snippets, `workspace.json`, notas `Daily/`.
72
+
73
+ ## Flujo de trabajo típico
74
+
75
+ 1. Trabajas en un hito con `cdk` → ozali genera docs en `.ozali/docs/`
76
+ 2. `ozali sync` → lleva docs + Engram al repo de conocimiento
77
+ 3. `ozali sync --obsidian` → regenera el vault con la memoria actualizada
78
+ 4. Abres Obsidian → exploras el grafo, wikilinks, decisiones del equipo
79
+
80
+ ## Troubleshooting
81
+
82
+ | Síntoma | Causa | Solución |
83
+ |---|---|---|
84
+ | `engram obsidian-export` falla | Vault abierto en Obsidian (lock de archivos) | Cierra el vault en Obsidian y reintenta |
85
+ | MOCs vacíos | Sin proyectos en `knowledgeRepo/projects/` | Corre `ozali sync` primero para crear la estructura |
86
+ | No se detecta Obsidian | Instalado en path no estándar | Ignora la advertencia; abre el vault manualmente |
87
+
88
+ ## Cerebro vs Vault
89
+
90
+ - **Cerebro** (`AI.md` + `.ai/`) vive en **cada repo** — es la fuente de verdad.
91
+ - **Vault** (`obsidian/`) vive en el **repo de conocimiento** — es la lectura humana acumulada.
92
+
93
+ No duplicamos el cerebro en el vault. Los MOCs usan **markdown links** para referenciar `AI.md` fuera del vault, y **wikilinks** para todo lo que está dentro (`Memoria/`, `MOCs/`).
@@ -26,6 +26,18 @@ ozali workspace --no-trust # no marca la raíz como confiable en Claude Code
26
26
  Un CLI **no puede auto-dispararse** con solo hacer `cd`; por eso es un comando explícito e
27
27
  **idempotente**: re-córrelo cuando agregues repos o cambien las referencias.
28
28
 
29
+ ### Operar todos los repos desde la raíz
30
+
31
+ Para no entrar repo por repo, dos modos batch corren un comando sobre **todos los miembros**:
32
+
33
+ ```bash
34
+ ozali workspace --doctor # health-check de cada repo miembro + resumen consolidado
35
+ ozali workspace --update # actualiza skills/permisos/jarvis de cada repo + avisa de cdk desfasado
36
+ ```
37
+
38
+ Ambos son **solo CLI** (no necesitan el agente) y saltan los repos `✖ sin init`. La **calibración**
39
+ (generar `cdk`) sí la hace el agente, pero también **sin cambiar de proyecto** — ver abajo.
40
+
29
41
  ## Qué hace, paso a paso
30
42
 
31
43
  1. **Escaneo (read-only).** Enumera los repos git hijos y reporta el estado ozali de cada uno:
@@ -37,10 +49,11 @@ Un CLI **no puede auto-dispararse** con solo hacer `cd`; por eso es un comando e
37
49
  instala skills, aísla histórico, configura Engram). Hereda `agent`/`scope`/`knowledge-repo` de un
38
50
  repo ya inicializado para mantener el equipo consistente.
39
51
 
40
- 3. **Guía de calibración.** El CLI **no calibra** (eso lo hace el agente). Para cada repo
41
- `⚠ sin calibrar`, imprime la instrucción: abre ese repo en tu agente y corre la skill **`ozali`**
42
- ("diagnostica el proyecto") para generar su `cdk`. El orquestador de workspace también queda
43
- cableado para conducir esa calibración repo por repo.
52
+ 3. **Guía de calibración.** El CLI **no calibra** (eso lo hace el agente), pero ya **no** tienes que
53
+ abrir cada repo por separado: `ozali workspace` instala la skill **`ozali`** en la **raíz**, y el
54
+ orquestador `ozali-workspace-jarvis` la usa **en modo target** para calibrar cada repo
55
+ `⚠ sin calibrar` **desde el workspace**, repo por repo y con su propio 🛑 GATE. Abre el agente en la
56
+ raíz y pídele *"calibra los repos pendientes"*.
44
57
 
45
58
  4. **Referencias (auto-detección + confirmación).** Infiere aristas dirigidas `from → to`:
46
59
  - `npm-dep` — el `name` de un repo aparece en `dependencies`/`devDependencies`/`peerDependencies` de otro.
@@ -58,6 +71,7 @@ En la carpeta raíz:
58
71
  | `ozali-workspace.json` | Manifiesto: miembros (ruta, proyecto, estado, fuente de verdad), referencias, `knowledgeRepo`, cloud. Fuente de verdad del workspace. |
59
72
  | `<carpeta>.code-workspace` | Workspace **multi-root** de VSCode/Antigravity: abre todos los repos juntos. Merge idempotente si ya existe. |
60
73
  | `CLAUDE.md` / `AGENTS.md` (raíz) | Bloque marcado del orquestador **`ozali-workspace-jarvis`** + subagente / agente de opencode. |
74
+ | `.claude/skills/ozali/` (raíz) | La skill **`ozali`** instalada en la raíz para **calibrar miembros en modo target** sin cambiar de proyecto (solo Claude Code). |
61
75
 
62
76
  Si la raíz es a su vez un repo git, se añade `.claude/`, `.engram/` y `.ozali/` a su `.gitignore`
63
77
  (los artefactos locales del agente no se commitean). `ozali-workspace.json` y `.code-workspace` **sí**
@@ -78,5 +92,5 @@ miembros). Su trabajo:
78
92
  ## Fuera de alcance (por ahora)
79
93
 
80
94
  - Auto-sugerencia al abrir la carpeta (tarea de editor/hook).
81
- - `ozali doctor`/`sync` agregados a nivel workspace.
95
+ - `ozali sync` agregado a nivel workspace (`--doctor`/`--update` ya existen; falta `--sync`).
82
96
  - Detección de referencias más allá de npm/submódulos/compose (imports TS/py, `go.work`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ozali",
3
- "version": "0.7.1",
3
+ "version": "0.9.0",
4
4
  "description": "Bootstrap interactivo de IA por equipo: calibra el proyecto, genera la skill de ejecución (cdk) con TDD/SDD, y mantiene memoria de equipo (Engram) con histórico aislado.",
5
5
  "type": "module",
6
6
  "bin": {
package/skill/SKILL.md CHANGED
@@ -26,6 +26,28 @@ produce documentación, la calibración, el plan y los artefactos de la skill `c
26
26
 
27
27
  ---
28
28
 
29
+ ## Modo workspace — calibrar un repo miembro (target)
30
+
31
+ Normalmente `ozali` opera sobre el **repo actual** (cwd). Pero cuando te invocan desde la **raíz de un
32
+ workspace** (existe `ozali-workspace.json`) para calibrar un **repo miembro**, operas sobre un
33
+ **target de subcarpeta** en vez del cwd. En ese caso:
34
+
35
+ - **Todas las rutas son relativas al `<target>`**, no a la raíz: la fuente de verdad
36
+ (`<target>/AI.md` + `<target>/.ai/`, o su variante `IA.md` + `.ia/`), la skill que generas
37
+ (`<target>/.claude/skills/cdk/`), el histórico (`<target>/.ozali/docs/`) y
38
+ `<target>/.engram/config.json`.
39
+ - **Identidad git del miembro:** usa `git -C <target> …` (no la raíz).
40
+ - **Engram:** guarda con el `project_name` de `<target>/.engram/config.json` (equivale al
41
+ `members[].project` del manifiesto). **No mezcles** memoria entre repos.
42
+ - **Un target por corrida**, secuencial, con su **propio 🛑 GATE**. No calibres varios a la vez sin
43
+ aprobación.
44
+ - Al terminar, sugiere **re-correr `ozali workspace`** en la raíz para refrescar el estado del miembro
45
+ en el manifiesto (`needs-calibration` → `ready`).
46
+
47
+ Si te invocan sin target (uso normal en un solo repo), ignora esta sección y sigue con el cwd.
48
+
49
+ ---
50
+
29
51
  ## Flujo general (7 fases)
30
52
 
31
53
  ```
@@ -0,0 +1,12 @@
1
+ {
2
+ "alwaysUpdateLinks": true,
3
+ "newFileLocation": "folder",
4
+ "newFileFolderPath": "Memoria",
5
+ "attachmentFolderPath": "Memoria",
6
+ "readableLineLength": true,
7
+ "strictLineBreaks": false,
8
+ "showLineNumber": false,
9
+ "spellcheck": false,
10
+ "tabSize": 2,
11
+ "useTab": false
12
+ }
@@ -0,0 +1,9 @@
1
+ [
2
+ "graph",
3
+ "backlink",
4
+ "page-preview",
5
+ "command-palette",
6
+ "markdown-importer",
7
+ "switcher",
8
+ "outline"
9
+ ]
@@ -0,0 +1,9 @@
1
+ # 🗺️ Bugfixes Recientes
2
+
3
+ Correcciones con impacto en otros módulos o que revelan gotchas del repo.
4
+
5
+ > Este MOC se regenera automáticamente con `ozali sync --obsidian`.
6
+
7
+ <!-- BUGFIXES_START -->
8
+ <!-- Se rellena automáticamente por ozali sync --obsidian desde Engram -->
9
+ <!-- BUGFIXES_END -->
@@ -0,0 +1,9 @@
1
+ # 🗺️ Decisiones del Equipo
2
+
3
+ Decisiones de arquitectura, trade-offs y convenciones que afectan a todos.
4
+
5
+ > Este MOC se regenera automáticamente con `ozali sync --obsidian`.
6
+
7
+ <!-- DECISIONS_START -->
8
+ <!-- Se rellena automáticamente por ozali sync --obsidian desde Engram -->
9
+ <!-- DECISIONS_END -->
@@ -0,0 +1,9 @@
1
+ # 🗺️ Métricas y Tokens
2
+
3
+ Resumen agregado de uso de tokens y eficiencia del equipo.
4
+
5
+ > Este MOC se regenera automáticamente con `ozali sync --obsidian`.
6
+
7
+ <!-- METRICS_START -->
8
+ <!-- Se rellena automáticamente por ozali sync --obsidian desde Engram -->
9
+ <!-- METRICS_END -->
@@ -0,0 +1,17 @@
1
+ # 🗺️ Proyectos
2
+
3
+ Proyectos activos en el workspace ozali.
4
+
5
+ > Este MOC se regenera automáticamente con `ozali sync --obsidian`.
6
+
7
+ ## Activos
8
+
9
+ <!-- PROJECTS_START -->
10
+ <!-- Se rellena automáticamente por ozali sync --obsidian -->
11
+ <!-- PROJECTS_END -->
12
+
13
+ ## Agregar un nuevo proyecto
14
+
15
+ 1. Corre `ozali init` en el repo del proyecto.
16
+ 2. Corre `ozali sync --obsidian` desde cualquier proyecto.
17
+ 3. El proyecto aparecerá aquí automáticamente.
@@ -0,0 +1,19 @@
1
+ # 🗺️ Índice Global
2
+
3
+ Bienvenido al vault de ozali. Usa este índice para navegar la memoria del equipo.
4
+
5
+ ## Mapas de Contenido (MOCs)
6
+
7
+ - [[MOCs/Proyectos]] — Todos los proyectos del equipo
8
+ - [[MOCs/Decisiones del Equipo]] — Decisiones arquitectónicas y trade-offs
9
+ - [[MOCs/Bugfixes Recientes]] — Correcciones con impacto cruzado
10
+ - [[MOCs/Métricas y Tokens]] — Consumo de tokens y eficiencia
11
+
12
+ ## Cómo actualizar este vault
13
+
14
+ ```bash
15
+ # Desde cualquier proyecto ozali
16
+ ozali sync --obsidian
17
+ ```
18
+
19
+ > El vault se regenera automáticamente. No edites `Memoria/` a mano.