ozali 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -5
- package/cli/bin/ozali.mjs +6 -0
- package/cli/lib/commands.mjs +276 -13
- package/cli/lib/util.mjs +18 -0
- package/cli/test/smoke.test.mjs +10 -2
- package/docs/guia-de-uso.md +166 -0
- package/package.json +1 -1
- package/skill/references/engram-convention.md +38 -0
package/README.md
CHANGED
|
@@ -46,18 +46,38 @@ npx --ignore-scripts ozali@<versión> init
|
|
|
46
46
|
git clone <repo> && node ozali/cli/bin/ozali.mjs init
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
> **`dlx`/`npx` son efímeros:** ejecutan `init` pero **no dejan `ozali` en tu PATH**. Para usar
|
|
50
|
+
> `ozali doctor`/`update`/`sync` después, instala globalmente (el propio `init` te lo recuerda):
|
|
51
|
+
>
|
|
52
|
+
> ```bash
|
|
53
|
+
> pnpm add -g ozali@<versión> # recomendado
|
|
54
|
+
> npm install -g ozali@<versión>
|
|
55
|
+
> ```
|
|
56
|
+
|
|
49
57
|
### Uso del CLI
|
|
50
58
|
|
|
51
59
|
```bash
|
|
52
|
-
ozali init # detecta agente, instala la skill, aísla el histórico, configura Engram
|
|
53
|
-
ozali doctor # health-check read-only (fuente de verdad, Engram, Strict TDD, runner…)
|
|
60
|
+
ozali init # detecta agente, instala la skill, aísla el histórico, instala+configura Engram
|
|
61
|
+
ozali doctor # health-check read-only (fuente de verdad, Engram, Cloud, Strict TDD, runner…)
|
|
54
62
|
ozali update # actualiza la skill instalada a la versión del paquete
|
|
55
63
|
ozali sync # lleva el histórico (docs + Engram) al repo de conocimiento de equipo
|
|
56
64
|
```
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
`init` también escribe un **perfil base de permisos** (`.claude/settings.json` para Claude Code,
|
|
67
|
+
`opencode.json` para opencode) para reducir confirmaciones: deja libres comandos seguros y bloquea
|
|
68
|
+
los destructivos. Es un template — tus reglas se conservan al re-correr `init`. Como Claude Code
|
|
69
|
+
**ignora** los permisos de un proyecto hasta confiar en él, `init` ofrece marcar el workspace como
|
|
70
|
+
confiable (`hasTrustDialogAccepted` en `~/.claude.json`); usa `--no-trust` para omitirlo.
|
|
71
|
+
|
|
72
|
+
`init` también **instala Engram** y registra su MCP con `engram setup <agente>`: en modo
|
|
73
|
+
interactivo te pregunta (default sí), y con `--yes` lo instala automáticamente. Usa brew en
|
|
74
|
+
macOS/Linux, `go install` en Windows, con binario precompilado como fallback. Si prefieres no
|
|
75
|
+
instalarlo, pasa `--no-engram` (arranca en modo `docs`). Opcionalmente habilita **Engram Cloud**
|
|
76
|
+
(réplica de equipo opt-in) además del git-sync.
|
|
77
|
+
|
|
78
|
+
Flags útiles: `--yes` (no interactivo), `--dry-run` (init sin escribir), `--no-engram`, `--no-trust`,
|
|
79
|
+
`--agent`, `--scope`, `--knowledge-repo`, `--import`/`--push`/`--cloud` (sync). Modelo mental completo
|
|
80
|
+
en [docs/intended-usage.md](docs/intended-usage.md).
|
|
61
81
|
|
|
62
82
|
## Agentes soportados
|
|
63
83
|
|
|
@@ -68,6 +88,7 @@ Claude Code y opencode (perfiles de permisos para ambos en
|
|
|
68
88
|
|
|
69
89
|
| Tema | Doc |
|
|
70
90
|
|---|---|
|
|
91
|
+
| Guía de uso (usuarios generales) | [docs/guia-de-uso.md](docs/guia-de-uso.md) |
|
|
71
92
|
| Uso previsto (modelo mental) | [docs/intended-usage.md](docs/intended-usage.md) |
|
|
72
93
|
| Seguridad del instalador (npx/pnpm) | [docs/security.md](docs/security.md) |
|
|
73
94
|
| Histórico aislado y memoria de equipo | [docs/team-history.md](docs/team-history.md) |
|
package/cli/bin/ozali.mjs
CHANGED
|
@@ -24,8 +24,11 @@ ${c.bold("Opciones comunes:")}
|
|
|
24
24
|
--agent <a> (init) claude-code | opencode | both.
|
|
25
25
|
--scope <s> (init) project | global.
|
|
26
26
|
--knowledge-repo <p> (init) Ruta del repo de conocimiento.
|
|
27
|
+
--no-engram (init) No usar Engram; arranca en modo docs.
|
|
28
|
+
--no-trust (init) No marcar el workspace como confiable en Claude Code.
|
|
27
29
|
--import (sync) Importa del repo de conocimiento a local.
|
|
28
30
|
--push (sync) Hace push al remoto del repo de conocimiento.
|
|
31
|
+
--cloud (sync) Replica también a Engram Cloud (si está habilitado).
|
|
29
32
|
-h, --help Esta ayuda. -v, --version Versión.
|
|
30
33
|
|
|
31
34
|
${c.bold("Instalación segura:")}
|
|
@@ -42,6 +45,9 @@ function parseArgs(argv) {
|
|
|
42
45
|
else if (a === "--dry-run") opts.dryRun = true;
|
|
43
46
|
else if (a === "--import") opts.import = true;
|
|
44
47
|
else if (a === "--push") opts.push = true;
|
|
48
|
+
else if (a === "--cloud") opts.cloud = true;
|
|
49
|
+
else if (a === "--no-engram") opts.noEngram = true;
|
|
50
|
+
else if (a === "--no-trust") opts.noTrust = true;
|
|
45
51
|
else if (a === "--agent") opts.agent = argv[++i];
|
|
46
52
|
else if (a === "--scope") opts.scope = argv[++i];
|
|
47
53
|
else if (a === "--knowledge-repo") opts.knowledgeRepo = argv[++i];
|
package/cli/lib/commands.mjs
CHANGED
|
@@ -4,7 +4,8 @@ import path from "node:path";
|
|
|
4
4
|
import {
|
|
5
5
|
c, ok, warn, err, info, step,
|
|
6
6
|
SKILL_SRC, exists, ensureDir, copyDir, readJSON, writeJSON,
|
|
7
|
-
ensureGitignore, tryExec,
|
|
7
|
+
ensureGitignore, tryExec, spawnCmd, which,
|
|
8
|
+
projectName, pkgVersion, DEFAULT_KNOWLEDGE, HOME,
|
|
8
9
|
} from "./util.mjs";
|
|
9
10
|
import { detectAll } from "./detect.mjs";
|
|
10
11
|
import { ask, confirm, select } from "./prompt.mjs";
|
|
@@ -47,14 +48,47 @@ export async function init(cwd, opts) {
|
|
|
47
48
|
const knowledgeRepo = opts.knowledgeRepo || await ask("Ruta del repo de conocimiento (histórico aislado)", DEFAULT_KNOWLEDGE);
|
|
48
49
|
|
|
49
50
|
// Engram
|
|
50
|
-
let memoryMode = "
|
|
51
|
-
if (
|
|
51
|
+
let memoryMode = "docs";
|
|
52
|
+
if (opts.noEngram) {
|
|
53
|
+
info("--no-engram: arranco en modo " + c.bold("docs") + " (sin usar Engram).");
|
|
54
|
+
} else if (env.engram.available) {
|
|
55
|
+
memoryMode = "hybrid";
|
|
52
56
|
ok(`Engram disponible (${env.engram.bin}). Modo de memoria: ${c.bold("hybrid")} (docs + Engram).`);
|
|
53
57
|
} else {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
info("
|
|
57
|
-
|
|
58
|
+
warn("Engram no está instalado.");
|
|
59
|
+
// --dry-run no instala; --yes usa el default (sí); interactivo pregunta.
|
|
60
|
+
if (opts.dryRun) info("(dry-run) Aquí instalaría y configuraría Engram.");
|
|
61
|
+
const installNow = opts.dryRun ? false
|
|
62
|
+
: (opts.yes ? true : await confirm("¿Instalo y configuro Engram ahora?", true));
|
|
63
|
+
if (installNow) {
|
|
64
|
+
if (opts.yes) info("Modo no interactivo: instalando Engram automáticamente…");
|
|
65
|
+
const installed = installEngram();
|
|
66
|
+
if (installed) {
|
|
67
|
+
if (agent === "claude-code" || agent === "both") {
|
|
68
|
+
info("Registrando MCP en Claude Code…");
|
|
69
|
+
spawnCmd("engram", ["setup", "claude-code"]);
|
|
70
|
+
}
|
|
71
|
+
if (agent === "opencode" || agent === "both") {
|
|
72
|
+
info("Registrando MCP en opencode…");
|
|
73
|
+
spawnCmd("engram", ["setup", "opencode"]);
|
|
74
|
+
}
|
|
75
|
+
memoryMode = "hybrid";
|
|
76
|
+
ok("Engram listo. Modo de memoria: " + c.bold("hybrid") + ".");
|
|
77
|
+
info("Reinicia tu agente para que cargue el servidor MCP de Engram.");
|
|
78
|
+
} else {
|
|
79
|
+
warn("Instalación no completada. Continúo en modo " + c.bold("docs") + ".");
|
|
80
|
+
printEngramManualInstructions(agent);
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
info("Modo " + c.bold("docs") + " activo. Cuando instales Engram, corre " + c.bold("ozali doctor") + " para activar hybrid.");
|
|
84
|
+
printEngramManualInstructions(agent);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Engram Cloud (opt-in) — réplica de equipo además del git-sync. Solo si Engram quedó disponible.
|
|
89
|
+
let cloud = { enabled: false };
|
|
90
|
+
if (memoryMode === "hybrid" && !opts.dryRun) {
|
|
91
|
+
cloud = await maybeEnableEngramCloud(projectName(cwd), opts);
|
|
58
92
|
}
|
|
59
93
|
|
|
60
94
|
if (opts.dryRun) { warn("--dry-run: no escribo nada. Plan mostrado arriba."); return 0; }
|
|
@@ -67,7 +101,12 @@ export async function init(cwd, opts) {
|
|
|
67
101
|
copyDir(SKILL_SRC, target);
|
|
68
102
|
ok(`Skill instalada en ${c.bold(path.relative(cwd, target) || target)}.`);
|
|
69
103
|
|
|
70
|
-
// 2)
|
|
104
|
+
// 2) perfiles base de permisos (idempotentes, merge mínimo) por agente
|
|
105
|
+
if (agent === "claude-code" || agent === "both") {
|
|
106
|
+
ensureClaudeCodeProfile(cwd, scope);
|
|
107
|
+
// Claude Code ignora los permisos de un .claude/settings.json de proyecto hasta confiar en él.
|
|
108
|
+
if (scope === "project" && !opts.noTrust) await ensureClaudeWorkspaceTrust(cwd, opts);
|
|
109
|
+
}
|
|
71
110
|
if (agent === "opencode" || agent === "both") {
|
|
72
111
|
ensureOpencodeProfile(cwd);
|
|
73
112
|
}
|
|
@@ -92,7 +131,7 @@ export async function init(cwd, opts) {
|
|
|
92
131
|
|
|
93
132
|
// 5) config local (gitignored)
|
|
94
133
|
const config = {
|
|
95
|
-
version: pkgVersion(), agent, scope, knowledgeRepo, memoryMode,
|
|
134
|
+
version: pkgVersion(), agent, scope, knowledgeRepo, memoryMode, cloud,
|
|
96
135
|
project: projectName(cwd), createdAt: new Date().toISOString(),
|
|
97
136
|
};
|
|
98
137
|
writeJSON(CONFIG_PATH(cwd), config);
|
|
@@ -100,6 +139,15 @@ export async function init(cwd, opts) {
|
|
|
100
139
|
|
|
101
140
|
// --- siguientes pasos ---
|
|
102
141
|
step("Siguientes pasos");
|
|
142
|
+
// Detect if ozali is NOT permanently in PATH (i.e., run via pnpm dlx / npx without global install).
|
|
143
|
+
if (!which("ozali")) {
|
|
144
|
+
const v = pkgVersion();
|
|
145
|
+
warn("ozali no está en tu PATH — fue ejecutado via dlx/npx sin instalación permanente.");
|
|
146
|
+
info(`Instala globalmente para usar ${c.bold("ozali doctor")}, ${c.bold("ozali sync")}, etc.:`);
|
|
147
|
+
console.log(` ${c.bold(`pnpm add -g ozali@${v}`)} ${c.dim("← recomendado")}`);
|
|
148
|
+
console.log(` ${c.dim("o")} ${c.bold(`npm install -g ozali@${v}`)}`);
|
|
149
|
+
console.log("");
|
|
150
|
+
}
|
|
103
151
|
console.log(` 1. Abre tu agente en este proyecto.`);
|
|
104
152
|
console.log(` 2. Escribe ${c.bold('"diagnostica el proyecto"')} o ${c.bold('"ozali"')} para arrancar el bootstrap (calibración + generación de la skill ${c.bold("cdk")}).`);
|
|
105
153
|
console.log(` 3. Tras trabajar, corre ${c.bold("ozali sync")} para llevar el histórico al repo de conocimiento.`);
|
|
@@ -107,6 +155,165 @@ export async function init(cwd, opts) {
|
|
|
107
155
|
return 0;
|
|
108
156
|
}
|
|
109
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Instala Engram con la mejor ruta disponible para el SO actual.
|
|
160
|
+
* macOS/Linux: Homebrew (recomendado) o Go install como fallback.
|
|
161
|
+
* Windows: Go install (recomendado) o binario manual.
|
|
162
|
+
* Devuelve true si el binario quedó disponible en PATH.
|
|
163
|
+
*/
|
|
164
|
+
function installEngram() {
|
|
165
|
+
const plat = process.platform;
|
|
166
|
+
|
|
167
|
+
if (plat === "darwin" || plat === "linux") {
|
|
168
|
+
if (which("brew")) {
|
|
169
|
+
info("Instalando: " + c.bold("brew install gentleman-programming/tap/engram"));
|
|
170
|
+
spawnCmd("brew", ["install", "gentleman-programming/tap/engram"]);
|
|
171
|
+
} else if (which("go")) {
|
|
172
|
+
info("Homebrew no encontrado — instalando con Go:");
|
|
173
|
+
info(" " + c.bold("go install github.com/Gentleman-Programming/engram/cmd/engram@latest"));
|
|
174
|
+
spawnCmd("go", ["install", "github.com/Gentleman-Programming/engram/cmd/engram@latest"]);
|
|
175
|
+
} else {
|
|
176
|
+
warn("No se encontró Homebrew ni Go. Opciones de instalación:");
|
|
177
|
+
info(" a) Homebrew " + c.dim("(recomendado)") + ": " + c.cyan("https://brew.sh") + " → " + c.bold("brew install gentleman-programming/tap/engram"));
|
|
178
|
+
info(" b) Binario precompilado: " + c.cyan("https://github.com/Gentleman-Programming/engram/releases"));
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
} else if (plat === "win32") {
|
|
182
|
+
if (which("go")) {
|
|
183
|
+
info("Instalando: " + c.bold("go install github.com/Gentleman-Programming/engram/cmd/engram@latest"));
|
|
184
|
+
spawnCmd("go", ["install", "github.com/Gentleman-Programming/engram/cmd/engram@latest"]);
|
|
185
|
+
} else {
|
|
186
|
+
warn("Go no encontrado. Opciones de instalación en Windows:");
|
|
187
|
+
info(" a) Go 1.24+ " + c.dim("(recomendado)") + ": " + c.cyan("https://go.dev/dl/") + " → " + c.bold("go install github.com/Gentleman-Programming/engram/cmd/engram@latest"));
|
|
188
|
+
info(" b) Binario .zip: " + c.cyan("https://github.com/Gentleman-Programming/engram/releases"));
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
info("Guía de instalación completa: " + c.cyan("https://github.com/Gentleman-Programming/engram/blob/main/docs/INSTALLATION.md"));
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const bin = which("engram");
|
|
197
|
+
if (bin) { ok("Engram instalado (" + bin + ")."); return true; }
|
|
198
|
+
warn("El binario engram no aparece en PATH tras la instalación.");
|
|
199
|
+
info("Puede que necesites reiniciar la terminal o ajustar tu PATH.");
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Engram Cloud opt-in: réplica de equipo en tiempo real, adicional al git-sync.
|
|
205
|
+
* Configura el servidor y enrola el proyecto. Devuelve { enabled, server }.
|
|
206
|
+
*/
|
|
207
|
+
async function maybeEnableEngramCloud(project, opts) {
|
|
208
|
+
if (opts.yes) return { enabled: false };
|
|
209
|
+
const enable = await confirm("¿Habilitar Engram Cloud para el equipo? (réplica opt-in, requiere un servidor)", false);
|
|
210
|
+
if (!enable) {
|
|
211
|
+
info("Cloud omitido. El histórico de equipo viaja por git-sync (" + c.bold("ozali sync") + ").");
|
|
212
|
+
return { enabled: false };
|
|
213
|
+
}
|
|
214
|
+
const server = await ask("URL del servidor de Engram Cloud", "http://127.0.0.1:18080");
|
|
215
|
+
info(`Configurando Engram Cloud → ${server}`);
|
|
216
|
+
spawnCmd("engram", ["cloud", "config", "--server", server]);
|
|
217
|
+
info(`Enrolando el proyecto "${project}"…`);
|
|
218
|
+
if (spawnCmd("engram", ["cloud", "enroll", project]) === 0) {
|
|
219
|
+
ok("Engram Cloud habilitado. Replica con " + c.bold("ozali sync --cloud") + ".");
|
|
220
|
+
return { enabled: true, server };
|
|
221
|
+
}
|
|
222
|
+
warn("No se pudo enrolar el proyecto en Engram Cloud. Continúo solo con git-sync.");
|
|
223
|
+
return { enabled: false, server };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function printEngramManualInstructions(agent) {
|
|
227
|
+
const plat = process.platform;
|
|
228
|
+
info("Para activar memoria buscable/acumulativa (modo " + c.bold("hybrid") + "):");
|
|
229
|
+
if (plat === "darwin" || plat === "linux") {
|
|
230
|
+
info(" 1. " + c.bold("brew install gentleman-programming/tap/engram") + " " + c.dim("(o binario: github.com/Gentleman-Programming/engram/releases)"));
|
|
231
|
+
} else if (plat === "win32") {
|
|
232
|
+
info(" 1. " + c.bold("go install github.com/Gentleman-Programming/engram/cmd/engram@latest") + " " + c.dim("(requiere Go 1.24+)"));
|
|
233
|
+
info(" " + c.dim("o binario .zip: github.com/Gentleman-Programming/engram/releases"));
|
|
234
|
+
} else {
|
|
235
|
+
info(" 1. " + c.cyan("https://github.com/Gentleman-Programming/engram/blob/main/docs/INSTALLATION.md"));
|
|
236
|
+
}
|
|
237
|
+
if (agent === "claude-code" || agent === "both") info(" 2. " + c.bold("engram setup claude-code"));
|
|
238
|
+
if (agent === "opencode" || agent === "both") info(" 2. " + c.bold("engram setup opencode"));
|
|
239
|
+
info(" 3. Corre " + c.bold("ozali doctor") + " — el modo subirá a hybrid automáticamente.");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Perfil base de permisos para Claude Code: lectura/comandos comunes libres,
|
|
243
|
+
// destructivos denegados. Es un TEMPLATE — el usuario puede añadir más entradas y
|
|
244
|
+
// re-correr init no las pisa (hace unión de listas).
|
|
245
|
+
const CLAUDE_PERMS = {
|
|
246
|
+
allow: [
|
|
247
|
+
"WebFetch", "WebSearch",
|
|
248
|
+
"Bash(python *)", "Bash(python3 *)",
|
|
249
|
+
"Bash(node *)", "Bash(npm *)", "Bash(npx *)", "Bash(pnpm *)", "Bash(yarn *)",
|
|
250
|
+
"Bash(go *)", "Bash(mvn *)", "Bash(java *)",
|
|
251
|
+
"Bash(git status)", "Bash(git diff *)", "Bash(git log *)", "Bash(git add *)", "Bash(git commit *)",
|
|
252
|
+
"Bash(ozali *)", "Bash(engram *)",
|
|
253
|
+
"PowerShell(python *)", "PowerShell(node *)", "PowerShell(npm *)", "PowerShell(npx *)",
|
|
254
|
+
"PowerShell(mvn *)", "PowerShell(java *)",
|
|
255
|
+
],
|
|
256
|
+
deny: [
|
|
257
|
+
"Bash(rm -rf *)", "Bash(git push *)",
|
|
258
|
+
"PowerShell(Remove-Item *)",
|
|
259
|
+
],
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
function mergeUnique(existing, additions) {
|
|
263
|
+
const out = Array.isArray(existing) ? [...existing] : [];
|
|
264
|
+
const seen = new Set(out);
|
|
265
|
+
let added = 0;
|
|
266
|
+
for (const item of additions) if (!seen.has(item)) { out.push(item); seen.add(item); added++; }
|
|
267
|
+
return { out, added };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function ensureClaudeCodeProfile(cwd, scope) {
|
|
271
|
+
const settingsPath = scope === "global"
|
|
272
|
+
? path.join(HOME, ".claude", "settings.json")
|
|
273
|
+
: path.join(cwd, ".claude", "settings.json");
|
|
274
|
+
const cfg = readJSON(settingsPath, {});
|
|
275
|
+
cfg.permissions = cfg.permissions || {};
|
|
276
|
+
const allowMerge = mergeUnique(cfg.permissions.allow, CLAUDE_PERMS.allow);
|
|
277
|
+
const denyMerge = mergeUnique(cfg.permissions.deny, CLAUDE_PERMS.deny);
|
|
278
|
+
cfg.permissions.allow = allowMerge.out;
|
|
279
|
+
cfg.permissions.deny = denyMerge.out;
|
|
280
|
+
writeJSON(settingsPath, cfg);
|
|
281
|
+
const rel = path.relative(cwd, settingsPath) || settingsPath;
|
|
282
|
+
if (allowMerge.added + denyMerge.added > 0) {
|
|
283
|
+
ok(`Perfil base de permisos de Claude Code en ${c.bold(rel)} (${allowMerge.added} allow / ${denyMerge.added} deny añadidos; tus reglas se conservan).`);
|
|
284
|
+
} else {
|
|
285
|
+
info(`Permisos de Claude Code ya cubiertos en ${c.bold(rel)} (sin cambios).`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Marca el proyecto como confiable en Claude Code (~/.claude.json). Sin esto, Claude Code
|
|
290
|
+
// ignora los permisos de un .claude/settings.json de proyecto ("workspace not trusted").
|
|
291
|
+
async function ensureClaudeWorkspaceTrust(cwd, opts) {
|
|
292
|
+
const p = path.join(HOME, ".claude.json");
|
|
293
|
+
if (!exists(p)) {
|
|
294
|
+
info("Claude Code aún no tiene ~/.claude.json; al abrirlo aquí, acepta el diálogo de confianza.");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
let cfg;
|
|
298
|
+
try { cfg = JSON.parse(fs.readFileSync(p, "utf8")); }
|
|
299
|
+
catch { warn("No pude leer ~/.claude.json; acepta el diálogo de confianza de Claude Code manualmente."); return; }
|
|
300
|
+
cfg.projects = cfg.projects || {};
|
|
301
|
+
cfg.projects[cwd] = cfg.projects[cwd] || {};
|
|
302
|
+
if (cfg.projects[cwd].hasTrustDialogAccepted === true) {
|
|
303
|
+
info("Claude Code ya confía en este workspace.");
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const doTrust = opts.yes ? true
|
|
307
|
+
: await confirm("¿Marcar este proyecto como confiable en Claude Code? (necesario para que apliquen los permisos)", true);
|
|
308
|
+
if (!doTrust) {
|
|
309
|
+
info("Workspace no marcado como confiable: Claude Code ignorará los permisos hasta que aceptes su diálogo.");
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
cfg.projects[cwd].hasTrustDialogAccepted = true;
|
|
313
|
+
fs.writeFileSync(p, JSON.stringify(cfg, null, 2)); // formato que usa Claude Code (indent 2, sin newline final)
|
|
314
|
+
ok("Workspace marcado como confiable en Claude Code (los permisos de .claude/settings.json ya aplican).");
|
|
315
|
+
}
|
|
316
|
+
|
|
110
317
|
function ensureOpencodeProfile(cwd) {
|
|
111
318
|
const p = path.join(cwd, "opencode.json");
|
|
112
319
|
const base = readJSON(p, {});
|
|
@@ -135,6 +342,9 @@ export function doctor(cwd) {
|
|
|
135
342
|
add("Fuente de verdad", env.sot.found, env.sot.found ? `${env.sot.doc} + ${env.sot.dir}/` : "ausente (corre la skill 'ozali')");
|
|
136
343
|
add("Skill ozali instalada", env.skill.installed, env.skill.installed ? env.skill.paths.map((p) => path.relative(cwd, p) || p).join(", ") : "no instalada (ozali init)");
|
|
137
344
|
add("Engram", env.engram.available, env.engram.available ? env.engram.bin : "no instalado → modo docs");
|
|
345
|
+
const cloudOn = !!(cfg && cfg.cloud && cfg.cloud.enabled);
|
|
346
|
+
// Cloud es opt-in: "off" es un estado válido (no cuenta como fallo).
|
|
347
|
+
add("Engram Cloud", true, cloudOn ? `enrolado → ${cfg.cloud.server || "server por defecto"}` : "off (opt-in, git-sync activo)");
|
|
138
348
|
add("Repo de conocimiento", !!(cfg && cfg.knowledgeRepo && exists(cfg.knowledgeRepo)), cfg ? cfg.knowledgeRepo : "sin configurar (ozali init)");
|
|
139
349
|
|
|
140
350
|
// Strict TDD (de la fuente de verdad)
|
|
@@ -154,6 +364,23 @@ export function doctor(cwd) {
|
|
|
154
364
|
console.log("");
|
|
155
365
|
if (bad === 0) ok("Todo en orden. ozali está listo para trabajar.");
|
|
156
366
|
else warn(`${bad} punto(s) a atender. Revisa los ✖ de arriba.`);
|
|
367
|
+
|
|
368
|
+
// Auto-upgrade: si Engram acaba de instalarse y el config aún dice "docs", subir a hybrid.
|
|
369
|
+
if (cfg && cfg.memoryMode === "docs" && env.engram.available) {
|
|
370
|
+
cfg.memoryMode = "hybrid";
|
|
371
|
+
writeJSON(CONFIG_PATH(cwd), cfg);
|
|
372
|
+
ok("Engram detectado → modo de memoria actualizado a " + c.bold("hybrid") + " en .ozali/config.json.");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Estado de sync de Engram (informativo).
|
|
376
|
+
if (env.engram.available) {
|
|
377
|
+
const status = tryExec("engram", ["sync", "--status"], { cwd });
|
|
378
|
+
if (status) {
|
|
379
|
+
step("Estado de sync (Engram)");
|
|
380
|
+
for (const line of status.split(/\r?\n/)) console.log(" " + c.dim(line));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
157
384
|
return bad === 0 ? 0 : 1;
|
|
158
385
|
}
|
|
159
386
|
|
|
@@ -192,13 +419,45 @@ export function sync(cwd, opts) {
|
|
|
192
419
|
const docsLocal = path.join(cwd, ".ozali", "docs");
|
|
193
420
|
const engramLocal = path.join(cwd, ".engram");
|
|
194
421
|
|
|
422
|
+
// Engram Cloud (opt-in): réplica bidireccional adicional al git-sync.
|
|
423
|
+
if (opts.cloud) {
|
|
424
|
+
if (cfg.cloud && cfg.cloud.enabled && tryExec("engram", ["--version"])) {
|
|
425
|
+
info(`Replicando con Engram Cloud (proyecto "${project}")…`);
|
|
426
|
+
if (spawnCmd("engram", ["sync", "--cloud", "--project", project], { cwd }) === 0) ok("Réplica con Engram Cloud completada.");
|
|
427
|
+
else warn("engram sync --cloud no terminó correctamente. Revisa el output de arriba.");
|
|
428
|
+
} else if (!(cfg.cloud && cfg.cloud.enabled)) {
|
|
429
|
+
warn("--cloud pedido, pero Engram Cloud no está habilitado. Corre " + c.bold("ozali init") + " para habilitarlo, o usa git-sync sin --cloud.");
|
|
430
|
+
} else {
|
|
431
|
+
warn("--cloud pedido, pero Engram no responde. Omito la réplica cloud.");
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
195
435
|
if (opts.import) {
|
|
196
436
|
// Repo de conocimiento → local
|
|
437
|
+
// 0) Traer lo más reciente del equipo (si el knowledge repo tiene remoto).
|
|
438
|
+
if (exists(path.join(kRepo, ".git")) && tryExec("git", ["remote", "get-url", "origin"], { cwd: kRepo })) {
|
|
439
|
+
info("Actualizando el repo de conocimiento (git pull)…");
|
|
440
|
+
if (spawnCmd("git", ["pull", "--ff-only"], { cwd: kRepo }) !== 0) {
|
|
441
|
+
warn("git pull no pudo completarse; importo lo que haya localmente.");
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// 1) Docs
|
|
197
445
|
const srcDocs = path.join(projDir, "docs");
|
|
198
446
|
if (exists(srcDocs)) { copyDir(srcDocs, docsLocal); ok("Docs importados a .ozali/docs/."); }
|
|
447
|
+
else info("Aún no hay docs en el repo de conocimiento para este proyecto.");
|
|
448
|
+
// 2) Engram: copiar los chunks del repo de conocimiento → .engram/ ANTES de importar
|
|
449
|
+
// (engram sync --import lee de .engram/ en el cwd; sin esta copia, un dev nuevo
|
|
450
|
+
// no importaría nada).
|
|
199
451
|
if (cfg.memoryMode === "hybrid" && tryExec("engram", ["--version"])) {
|
|
200
|
-
|
|
201
|
-
|
|
452
|
+
const srcEngram = path.join(kRepo, "engram", project);
|
|
453
|
+
if (exists(srcEngram)) {
|
|
454
|
+
copyDir(srcEngram, engramLocal);
|
|
455
|
+
info("Chunks de Engram copiados a .engram/. Importando…");
|
|
456
|
+
if (spawnCmd("engram", ["sync", "--import"], { cwd }) === 0) ok("Memorias importadas a Engram local.");
|
|
457
|
+
else warn("engram sync --import no terminó correctamente. Revisa el output de arriba.");
|
|
458
|
+
} else {
|
|
459
|
+
info("Aún no hay export de Engram en el repo de conocimiento para este proyecto.");
|
|
460
|
+
}
|
|
202
461
|
}
|
|
203
462
|
info("Import completo. Revisa .ozali/docs/.");
|
|
204
463
|
return 0;
|
|
@@ -208,8 +467,12 @@ export function sync(cwd, opts) {
|
|
|
208
467
|
// 1) Engram export (si hybrid + disponible)
|
|
209
468
|
if (cfg.memoryMode === "hybrid" && tryExec("engram", ["--version"])) {
|
|
210
469
|
info("Exportando memorias con engram sync…");
|
|
211
|
-
|
|
212
|
-
|
|
470
|
+
if (spawnCmd("engram", ["sync"], { cwd }) === 0) {
|
|
471
|
+
if (exists(engramLocal)) { copyDir(engramLocal, path.join(kRepo, "engram", project)); ok("Export de Engram copiado al repo de conocimiento."); }
|
|
472
|
+
else info("engram sync no generó .engram/ (sin memorias nuevas).");
|
|
473
|
+
} else {
|
|
474
|
+
warn("engram sync falló; sincronizo solo docs. Revisa el output de arriba.");
|
|
475
|
+
}
|
|
213
476
|
} else if (cfg.memoryMode === "hybrid") {
|
|
214
477
|
warn("Modo hybrid pero Engram no responde; sincronizo solo docs.");
|
|
215
478
|
}
|
package/cli/lib/util.mjs
CHANGED
|
@@ -79,6 +79,24 @@ export function tryExec(cmd, args = [], opts = {}) {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** Ejecuta un comando con I/O visible en la terminal. Devuelve el exit code (0 = éxito). */
|
|
83
|
+
export function spawnCmd(cmd, args = [], opts = {}) {
|
|
84
|
+
try {
|
|
85
|
+
execFileSync(cmd, args, { stdio: "inherit", ...opts });
|
|
86
|
+
return 0;
|
|
87
|
+
} catch (e) {
|
|
88
|
+
return e.status ?? 1;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Detecta el gestor de paquetes disponible (pnpm > bun > yarn > npm). */
|
|
93
|
+
export function detectPkgManager() {
|
|
94
|
+
for (const pm of ["pnpm", "bun", "yarn", "npm"]) {
|
|
95
|
+
if (which(pm)) return pm;
|
|
96
|
+
}
|
|
97
|
+
return "npm";
|
|
98
|
+
}
|
|
99
|
+
|
|
82
100
|
export function which(bin) {
|
|
83
101
|
const finder = process.platform === "win32" ? "where" : "which";
|
|
84
102
|
return tryExec(finder, [bin]);
|
package/cli/test/smoke.test.mjs
CHANGED
|
@@ -57,12 +57,20 @@ test("doctor en proyecto vacío reporta pendientes (exit 1)", () => {
|
|
|
57
57
|
test("init --yes instala la skill, aísla el histórico y escribe config", () => {
|
|
58
58
|
const dir = tmpProject();
|
|
59
59
|
try {
|
|
60
|
-
run(["init", "--yes", "--agent", "claude-code", "--scope", "project", "--knowledge-repo", path.join(dir, ".k")], dir);
|
|
60
|
+
run(["init", "--yes", "--no-engram", "--no-trust", "--agent", "claude-code", "--scope", "project", "--knowledge-repo", path.join(dir, ".k")], dir);
|
|
61
61
|
assert.ok(fs.existsSync(path.join(dir, ".claude", "skills", "ozali", "SKILL.md")), "SKILL.md instalada");
|
|
62
62
|
assert.ok(fs.existsSync(path.join(dir, ".ozali", "config.json")), "config escrita");
|
|
63
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(dir, ".ozali", "config.json"), "utf8"));
|
|
64
|
+
assert.equal(cfg.memoryMode, "docs", "--no-engram deja modo docs");
|
|
63
65
|
const gi = fs.readFileSync(path.join(dir, ".gitignore"), "utf8");
|
|
64
66
|
assert.match(gi, /\.ozali\//);
|
|
65
67
|
assert.match(gi, /\.engram\//);
|
|
68
|
+
// perfil base de permisos de Claude Code
|
|
69
|
+
const settingsPath = path.join(dir, ".claude", "settings.json");
|
|
70
|
+
assert.ok(fs.existsSync(settingsPath), "settings.json de Claude Code escrito");
|
|
71
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
72
|
+
assert.ok(Array.isArray(settings.permissions.allow) && settings.permissions.allow.length > 0, "allow no vacío");
|
|
73
|
+
assert.ok(settings.permissions.deny.includes("Bash(rm -rf *)"), "deny bloquea rm -rf");
|
|
66
74
|
} finally {
|
|
67
75
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
68
76
|
}
|
|
@@ -71,7 +79,7 @@ test("init --yes instala la skill, aísla el histórico y escribe config", () =>
|
|
|
71
79
|
test("init --dry-run no escribe nada", () => {
|
|
72
80
|
const dir = tmpProject();
|
|
73
81
|
try {
|
|
74
|
-
run(["init", "--dry-run", "--yes"], dir);
|
|
82
|
+
run(["init", "--dry-run", "--yes", "--no-engram"], dir);
|
|
75
83
|
assert.ok(!fs.existsSync(path.join(dir, ".ozali")), "no debe crear .ozali en dry-run");
|
|
76
84
|
assert.ok(!fs.existsSync(path.join(dir, ".claude")), "no debe crear .claude en dry-run");
|
|
77
85
|
} finally {
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Guía de uso — ozali
|
|
2
|
+
|
|
3
|
+
← [README](../README.md) · ¿buscas el modelo mental? → [intended-usage.md](intended-usage.md)
|
|
4
|
+
|
|
5
|
+
Guía rápida y sin tecnicismos para empezar a usar **ozali** en cualquier proyecto. Si nunca lo
|
|
6
|
+
has usado, lee esto de principio a fin: son 5 minutos.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ¿Qué es ozali, en simple?
|
|
11
|
+
|
|
12
|
+
ozali le enseña a tu asistente de IA (Claude Code u opencode) **cómo trabajar en tu proyecto**:
|
|
13
|
+
entiende tu código, propone un plan, pide tu aprobación antes de ejecutar, y **recuerda** lo que
|
|
14
|
+
el equipo hizo antes. Tú sigues mandando; ozali pone el método y el orden.
|
|
15
|
+
|
|
16
|
+
Piensa en tres piezas:
|
|
17
|
+
|
|
18
|
+
| Pieza | Para qué sirve |
|
|
19
|
+
|---|---|
|
|
20
|
+
| **El comando `ozali`** | Lo corres en la terminal. Instala y mantiene todo. |
|
|
21
|
+
| **La skill `ozali`** | Vive dentro de tu agente. Diagnostica el proyecto y prepara el trabajo. |
|
|
22
|
+
| **Engram** *(opcional)* | La "memoria" del equipo: lo aprendido se guarda y se comparte. |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Paso a paso (primera vez)
|
|
27
|
+
|
|
28
|
+
### 1. Instala ozali
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm add -g ozali # recomendado
|
|
32
|
+
# o: npm install -g ozali
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
> Puedes probarlo sin instalar con `pnpm dlx ozali init`, pero entonces el comando `ozali` **no
|
|
36
|
+
> queda disponible** después. Para usar `doctor`, `sync`, etc. en el día a día, instálalo con `-g`.
|
|
37
|
+
|
|
38
|
+
### 2. Entra a tu proyecto y arranca
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cd mi-proyecto
|
|
42
|
+
ozali init
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
El asistente te hará unas preguntas (qué agente usas, dónde guardar la memoria, si instalar
|
|
46
|
+
Engram). Puedes aceptar los valores por defecto si dudas. Al terminar, deja listo:
|
|
47
|
+
|
|
48
|
+
- la skill instalada en tu agente,
|
|
49
|
+
- los permisos base para que el agente **no te pregunte por cada comando** (ver más abajo),
|
|
50
|
+
- el aislamiento del histórico (no ensucia tu repo).
|
|
51
|
+
|
|
52
|
+
### 3. Abre tu agente y diagnostica
|
|
53
|
+
|
|
54
|
+
Abre Claude Code (u opencode) en el proyecto y escribe:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
diagnostica el proyecto
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
ozali revisará tu código, preparará la documentación base y te mostrará un **plan**. Nada se
|
|
61
|
+
ejecuta hasta que tú apruebes (eso es el 🛑 **GATE**).
|
|
62
|
+
|
|
63
|
+
### 4. Trabaja normal
|
|
64
|
+
|
|
65
|
+
Pídele cosas a tu agente como siempre: *"crea el endpoint de login"*, *"corrige este bug"*, etc.
|
|
66
|
+
ozali se encarga de hacerlo con orden y de ir guardando el registro.
|
|
67
|
+
|
|
68
|
+
### 5. Guarda y comparte (si trabajas en equipo)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
ozali sync
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Lleva el histórico y la memoria al repo de conocimiento del equipo. Tus compañeros lo traen con
|
|
75
|
+
`ozali sync --import`.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Los comandos (chuleta)
|
|
80
|
+
|
|
81
|
+
| Comando | Qué hace |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `ozali init` | Prepara el proyecto (lo corres una vez por repo). |
|
|
84
|
+
| `ozali doctor` | Revisa que todo esté bien. Es solo lectura, no cambia nada. |
|
|
85
|
+
| `ozali update` | Actualiza la skill a la última versión. |
|
|
86
|
+
| `ozali sync` | Sube el histórico/memoria al repo de equipo. |
|
|
87
|
+
| `ozali sync --import` | Baja lo que el equipo ya guardó. |
|
|
88
|
+
|
|
89
|
+
¿Algo no funciona? Corre **`ozali doctor`** primero: te dice qué falta y cómo arreglarlo.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Sobre los permisos (menos interrupciones)
|
|
94
|
+
|
|
95
|
+
Por defecto, los agentes piden confirmación **antes de cada comando**. Eso cansa. Durante `init`,
|
|
96
|
+
ozali crea un archivo `.claude/settings.json` con una lista de permisos razonable: deja correr
|
|
97
|
+
libremente cosas seguras (instalar dependencias, correr tests, leer archivos, `git status`…) y
|
|
98
|
+
**bloquea las peligrosas** (`rm -rf`, `git push`).
|
|
99
|
+
|
|
100
|
+
Es **un punto de partida, no una jaula**: puedes editar ese archivo y añadir lo que tu proyecto
|
|
101
|
+
necesite. Si vuelves a correr `ozali init`, tus reglas **se conservan** (no se borran ni se
|
|
102
|
+
duplican).
|
|
103
|
+
|
|
104
|
+
> **Confianza del workspace (Claude Code):** por seguridad, Claude Code **ignora** los permisos de
|
|
105
|
+
> un proyecto hasta que confías en él (verás un aviso *"this workspace has not been trusted"*).
|
|
106
|
+
> `init` te ofrece marcarlo como confiable automáticamente; si prefieres hacerlo tú, abre Claude
|
|
107
|
+
> Code en la carpeta y acepta el diálogo de confianza una vez. Con `--no-trust` ozali no lo toca.
|
|
108
|
+
|
|
109
|
+
Ejemplo — permitir Docker además de lo de fábrica:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"permissions": {
|
|
114
|
+
"allow": ["Bash(docker *)"],
|
|
115
|
+
"deny": []
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Engram: la memoria del equipo (opcional)
|
|
123
|
+
|
|
124
|
+
Durante `init`, ozali **instala Engram por ti**: en modo interactivo te pregunta (basta con aceptar),
|
|
125
|
+
y en modo automático (`--yes`) lo instala solo. Lo baja con brew (macOS/Linux) o `go` (Windows), y si
|
|
126
|
+
no puede, te deja las instrucciones. Con Engram, tu agente **recuerda** decisiones, bugs resueltos y
|
|
127
|
+
convenciones — y los recupera en futuras sesiones.
|
|
128
|
+
|
|
129
|
+
¿No quieres Engram ahora? Pasa `--no-engram` y ozali arranca en modo `docs` (la memoria se queda en
|
|
130
|
+
documentos legibles). Cuando quieras activarlo, instálalo y corre `ozali doctor`: el modo sube a
|
|
131
|
+
`hybrid` solo.
|
|
132
|
+
|
|
133
|
+
Dos detalles si lo usas en equipo:
|
|
134
|
+
|
|
135
|
+
- **Idioma:** guarda la memoria compartida **en español** (y búscala en español). El buscador no
|
|
136
|
+
mezcla idiomas: si guardas en español y buscas en inglés, no encuentra nada.
|
|
137
|
+
- **Privado vs. compartido:** lo que marques como `scope: project` lo ve todo el equipo. Para notas
|
|
138
|
+
personales usa `scope: personal` (aunque hoy el sync comparte ambos — para algo realmente privado,
|
|
139
|
+
no lo pongas en el proyecto de equipo).
|
|
140
|
+
|
|
141
|
+
Más detalle técnico en [engram-convention.md](../skill/references/engram-convention.md).
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Preguntas frecuentes
|
|
146
|
+
|
|
147
|
+
**¿Tengo que usar Engram?**
|
|
148
|
+
No. Es opcional. Sin él, ozali funciona en modo `docs`.
|
|
149
|
+
|
|
150
|
+
**¿ozali toca mi código sin permiso?**
|
|
151
|
+
No. Siempre hay un plan y un GATE de aprobación antes de ejecutar.
|
|
152
|
+
|
|
153
|
+
**Escribí `ozali` y la terminal dice "command not found".**
|
|
154
|
+
Lo corriste con `dlx`/`npx`, que es temporal. Instálalo global: `pnpm add -g ozali`.
|
|
155
|
+
|
|
156
|
+
**¿Esto ensucia mi repositorio?**
|
|
157
|
+
No. El histórico y la memoria se aíslan (`.ozali/`, `.engram/` van al `.gitignore`).
|
|
158
|
+
|
|
159
|
+
**Veo "this workspace has not been trusted" y mis permisos no aplican.**
|
|
160
|
+
Es el aviso de seguridad de Claude Code: ignora los permisos del proyecto hasta confiar en él.
|
|
161
|
+
Deja que `init` lo marque como confiable, o abre Claude Code en la carpeta y acepta el diálogo una
|
|
162
|
+
vez. Tras eso, el aviso desaparece y los permisos surten efecto.
|
|
163
|
+
|
|
164
|
+
**¿Funciona en Windows?**
|
|
165
|
+
Sí. Los permisos base incluyen variantes PowerShell, y Engram se instala con `go install` o un
|
|
166
|
+
binario descargable.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ozali",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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": {
|
|
@@ -33,6 +33,44 @@ Sin remoto, el nombre de carpeta. Mantén UN nombre por proyecto (evita drift `m
|
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
36
|
+
## 1.5. Memoria de equipo (scope + idioma)
|
|
37
|
+
|
|
38
|
+
La memoria de Engram es **compartida**: lo que un dev guarda en `scope: project` lo recuperan los
|
|
39
|
+
agentes de sus compañeros tras `ozali sync` / `ozali sync --import`. Dos convenciones son
|
|
40
|
+
**obligatorias** para que esa memoria compartida sirva como herramienta de equipo.
|
|
41
|
+
|
|
42
|
+
### Scope: `project` vs `personal`
|
|
43
|
+
|
|
44
|
+
| Guarda como `scope: project` | Guarda como `scope: personal` |
|
|
45
|
+
|---|---|
|
|
46
|
+
| Decisiones de arquitectura y trade-offs | Notas y aprendizajes personales |
|
|
47
|
+
| Bugfixes que afectan a otros | Atajos de editor / dotfiles |
|
|
48
|
+
| Convenciones, naming, *gotchas* del repo | Preferencias de estilo o workflow |
|
|
49
|
+
| Contratos de API, quirks de despliegue | Enlaces para releer luego |
|
|
50
|
+
| Contexto de onboarding | TODOs y recordatorios personales |
|
|
51
|
+
|
|
52
|
+
> Regla rápida: **si el agente de un compañero debería encontrarlo, es `scope: project`.** Todos los
|
|
53
|
+
> artefactos automáticos de `cdk` (§2) van en `scope: project`.
|
|
54
|
+
|
|
55
|
+
> ⚠️ **El sync exporta AMBOS scopes.** Hoy `scope` filtra búsqueda, **no** transporte: al sincronizar
|
|
56
|
+
> un proyecto se comparten también sus observaciones `personal`. Si necesitas notas verdaderamente
|
|
57
|
+
> privadas, guárdalas bajo **otro nombre de proyecto** (p. ej. `tu-nombre-notas`) que no sincronices,
|
|
58
|
+
> o no las pongas en Engram.
|
|
59
|
+
|
|
60
|
+
### Idioma de la memoria compartida: **español**
|
|
61
|
+
|
|
62
|
+
FTS5 (el buscador de Engram) **no es multilingüe**: una búsqueda en un idioma no matchea memorias
|
|
63
|
+
guardadas en otro. Por eso este equipo fija una **lingua franca** para `scope: project`:
|
|
64
|
+
|
|
65
|
+
- **`scope: project` → siempre en español.** Títulos, `topic_key` semánticos y contenido.
|
|
66
|
+
- **`scope: personal` → cualquier idioma** (nadie más busca tu scope personal).
|
|
67
|
+
|
|
68
|
+
> 🔑 **Regla de consistencia:** como elegimos español, **tanto al guardar como al buscar**
|
|
69
|
+
> `scope: project` se usa español. Una `mem_search` en inglés sobre memoria guardada en español
|
|
70
|
+
> **no devuelve nada** — la búsqueda se fragmenta y la memoria de equipo deja de funcionar.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
36
74
|
## 2. Naming determinista
|
|
37
75
|
|
|
38
76
|
Todos los artefactos de `cdk` en Engram siguen:
|