ozali 0.3.0 → 0.4.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.
- package/README.md +30 -3
- package/cli/bin/ozali.mjs +15 -3
- package/cli/lib/commands.mjs +269 -8
- package/cli/lib/util.mjs +1 -0
- package/cli/test/smoke.test.mjs +58 -2
- package/docs/guia-de-uso.md +35 -1
- package/package.json +2 -1
- package/skill/SKILL.md +17 -4
- package/skill/references/agents-blueprint.md +15 -8
- package/skill/references/doc-templates.md +27 -0
- package/skill/references/engram-convention.md +47 -0
- package/templates/ozali-jarvis-plugin.js +27 -0
- package/templates/ozali-jarvis.md +45 -0
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# ozali
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/ozali)
|
|
6
|
+
|
|
5
7
|
**Bootstrap de IA por equipo — calibra tus proyectos, genera una skill de ejecución con TDD/SDD, y mantiene memoria de equipo (Engram) con histórico aislado.**
|
|
6
8
|
|
|
7
9
|
</div>
|
|
@@ -59,10 +61,28 @@ git clone <repo> && node ozali/cli/bin/ozali.mjs init
|
|
|
59
61
|
```bash
|
|
60
62
|
ozali init # detecta agente, instala la skill, aísla el histórico, instala+configura Engram
|
|
61
63
|
ozali doctor # health-check read-only (fuente de verdad, Engram, Cloud, Strict TDD, runner…)
|
|
62
|
-
ozali update # actualiza la skill
|
|
64
|
+
ozali update # actualiza la instalación (skill ozali + ozali-jarvis + permisos) al paquete
|
|
63
65
|
ozali sync # lleva el histórico (docs + Engram) al repo de conocimiento de equipo
|
|
66
|
+
ozali audit # navega/audita la memoria de Engram del proyecto (o general)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`ozali audit` recorre lo que el equipo ha acumulado en Engram: dentro de un repo propone auditar
|
|
70
|
+
**ese proyecto** o **general** (todos los proyectos); fuera de un repo va directo a general. Usa
|
|
71
|
+
`--tui` para el navegador interactivo, `--search "<texto>"` para buscar y `--general` para forzar el
|
|
72
|
+
alcance. Sin Engram, audita el histórico local de `.ozali/docs/`.
|
|
73
|
+
|
|
74
|
+
### Actualizar a una versión nueva
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pnpm add -g ozali@<versión> # actualiza el CLI global (pinea la versión por el cooldown de pnpm)
|
|
78
|
+
ozali update # en cada repo: refresca skill ozali + ozali-jarvis + permisos
|
|
64
79
|
```
|
|
65
80
|
|
|
81
|
+
`ozali update` también **crea ozali-jarvis** en repos inicializados con versiones anteriores. La skill
|
|
82
|
+
`cdk` la **regenera tu agente** (no el CLI): tras `ozali update`, abre el agente y vuelve a correr la
|
|
83
|
+
skill `ozali` para regenerar `cdk` con el contrato nuevo — tus docs por hito y el plan congelado se
|
|
84
|
+
conservan.
|
|
85
|
+
|
|
66
86
|
`init` también escribe un **perfil base de permisos** (`.claude/settings.json` para Claude Code,
|
|
67
87
|
`opencode.json` para opencode) para reducir confirmaciones: deja libres comandos seguros y bloquea
|
|
68
88
|
los destructivos. Es un template — tus reglas se conservan al re-correr `init`. Como Claude Code
|
|
@@ -75,9 +95,16 @@ macOS/Linux, `go install` en Windows, con binario precompilado como fallback. Si
|
|
|
75
95
|
instalarlo, pasa `--no-engram` (arranca en modo `docs`). Opcionalmente habilita **Engram Cloud**
|
|
76
96
|
(réplica de equipo opt-in) además del git-sync.
|
|
77
97
|
|
|
98
|
+
`init` también crea **ozali-jarvis**, un **orquestador always-on**: persona en `CLAUDE.md`/`AGENTS.md`
|
|
99
|
+
+ subagente + hooks de recordatorio que hace que el agente, **en toda sesión y sin necesidad de
|
|
100
|
+
`/cdk`**, recupere contexto de Engram, registre el trabajo del equipo (memoria en contexto) y delegue
|
|
101
|
+
la ejecución disciplinada en `cdk`. Fija el proyecto en `.engram/config.json` para memoria
|
|
102
|
+
determinista. Con Engram en línea, jarvis y `cdk` operan **recall-first** (reusan memoria en vez de
|
|
103
|
+
releer) para gastar menos tokens/contexto. Omítelo con `--no-jarvis`.
|
|
104
|
+
|
|
78
105
|
Flags útiles: `--yes` (no interactivo), `--dry-run` (init sin escribir), `--no-engram`, `--no-trust`,
|
|
79
|
-
`--agent`, `--scope`, `--knowledge-repo`, `--import`/`--push`/`--cloud` (sync). Modelo
|
|
80
|
-
en [docs/intended-usage.md](docs/intended-usage.md).
|
|
106
|
+
`--no-jarvis`, `--agent`, `--scope`, `--knowledge-repo`, `--import`/`--push`/`--cloud` (sync). Modelo
|
|
107
|
+
mental completo en [docs/intended-usage.md](docs/intended-usage.md).
|
|
81
108
|
|
|
82
109
|
## Agentes soportados
|
|
83
110
|
|
package/cli/bin/ozali.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Seguridad: sin dependencias ni scripts de instalación. Ejecuta seguro con
|
|
4
4
|
// `pnpm dlx ozali` o `npx --ignore-scripts ozali`. Ver docs/security.md.
|
|
5
5
|
import { c, err, pkgVersion } from "../lib/util.mjs";
|
|
6
|
-
import { init, doctor, update, sync } from "../lib/commands.mjs";
|
|
6
|
+
import { init, doctor, update, sync, audit } from "../lib/commands.mjs";
|
|
7
7
|
|
|
8
8
|
const HELP = `
|
|
9
9
|
${c.bold("ozali")} ${c.dim("v" + pkgVersion())} — bootstrap de IA por equipo (TDD/SDD + memoria Engram)
|
|
@@ -15,8 +15,9 @@ ${c.bold("Comandos:")}
|
|
|
15
15
|
init Detecta el agente (Claude Code/opencode), instala la skill ozali,
|
|
16
16
|
aísla el histórico, configura Engram y el repo de conocimiento.
|
|
17
17
|
doctor Health-check read-only del proyecto (fuente de verdad, Engram, TDD…).
|
|
18
|
-
update Actualiza la skill ozali
|
|
18
|
+
update Actualiza la instalación (skill ozali + ozali-jarvis + permisos) al paquete.
|
|
19
19
|
sync Sincroniza el histórico (docs + Engram) con el repo de conocimiento.
|
|
20
|
+
audit Navega/audita la memoria de Engram del proyecto (o general).
|
|
20
21
|
|
|
21
22
|
${c.bold("Opciones comunes:")}
|
|
22
23
|
--yes, -y No interactivo: usa defaults.
|
|
@@ -26,9 +27,13 @@ ${c.bold("Opciones comunes:")}
|
|
|
26
27
|
--knowledge-repo <p> (init) Ruta del repo de conocimiento.
|
|
27
28
|
--no-engram (init) No usar Engram; arranca en modo docs.
|
|
28
29
|
--no-trust (init) No marcar el workspace como confiable en Claude Code.
|
|
30
|
+
--no-jarvis (init/update) No crear/refrescar el orquestador ozali-jarvis.
|
|
29
31
|
--import (sync) Importa del repo de conocimiento a local.
|
|
30
32
|
--push (sync) Hace push al remoto del repo de conocimiento.
|
|
31
33
|
--cloud (sync) Replica también a Engram Cloud (si está habilitado).
|
|
34
|
+
--general (audit) Auditoría general (todos los proyectos en Engram).
|
|
35
|
+
--tui (audit) Abre el navegador interactivo de Engram.
|
|
36
|
+
--search <q> (audit) Busca <q> en la memoria.
|
|
32
37
|
-h, --help Esta ayuda. -v, --version Versión.
|
|
33
38
|
|
|
34
39
|
${c.bold("Instalación segura:")}
|
|
@@ -48,6 +53,10 @@ function parseArgs(argv) {
|
|
|
48
53
|
else if (a === "--cloud") opts.cloud = true;
|
|
49
54
|
else if (a === "--no-engram") opts.noEngram = true;
|
|
50
55
|
else if (a === "--no-trust") opts.noTrust = true;
|
|
56
|
+
else if (a === "--no-jarvis") opts.noJarvis = true;
|
|
57
|
+
else if (a === "--general") opts.general = true;
|
|
58
|
+
else if (a === "--tui") opts.tui = true;
|
|
59
|
+
else if (a === "--search") opts.search = argv[++i];
|
|
51
60
|
else if (a === "--agent") opts.agent = argv[++i];
|
|
52
61
|
else if (a === "--scope") opts.scope = argv[++i];
|
|
53
62
|
else if (a === "--knowledge-repo") opts.knowledgeRepo = argv[++i];
|
|
@@ -66,11 +75,14 @@ async function main() {
|
|
|
66
75
|
if (!cmd || opts.help || cmd === "help") { console.log(HELP); return 0; }
|
|
67
76
|
|
|
68
77
|
const cwd = process.cwd();
|
|
78
|
+
// Banner de versión: confirma qué versión de ozali está corriendo en cada comando.
|
|
79
|
+
console.log(`${c.bold("ozali")} ${c.dim("v" + pkgVersion())}`);
|
|
69
80
|
switch (cmd) {
|
|
70
81
|
case "init": return await init(cwd, opts);
|
|
71
82
|
case "doctor": return doctor(cwd);
|
|
72
|
-
case "update": return update(cwd);
|
|
83
|
+
case "update": return update(cwd, opts);
|
|
73
84
|
case "sync": return sync(cwd, opts);
|
|
85
|
+
case "audit": return await audit(cwd, opts);
|
|
74
86
|
default:
|
|
75
87
|
err(`comando desconocido "${cmd}". Usa ${c.bold("ozali --help")}.`);
|
|
76
88
|
return 1;
|
package/cli/lib/commands.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import {
|
|
5
5
|
c, ok, warn, err, info, step,
|
|
6
|
-
SKILL_SRC, exists, ensureDir, copyDir, readJSON, writeJSON,
|
|
6
|
+
SKILL_SRC, TEMPLATES_SRC, exists, ensureDir, copyDir, readJSON, writeJSON,
|
|
7
7
|
ensureGitignore, tryExec, spawnCmd, which,
|
|
8
8
|
projectName, pkgVersion, DEFAULT_KNOWLEDGE, HOME,
|
|
9
9
|
} from "./util.mjs";
|
|
@@ -111,6 +111,16 @@ export async function init(cwd, opts) {
|
|
|
111
111
|
ensureOpencodeProfile(cwd);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// 2.5) ozali-jarvis: orquestador always-on (memoria Engram + puente a cdk).
|
|
115
|
+
if (!opts.noJarvis) {
|
|
116
|
+
const proj = projectName(cwd);
|
|
117
|
+
// Fija el proyecto para escrituras deterministas de memoria (se deriva igual por cada miembro).
|
|
118
|
+
writeJSON(path.join(cwd, ".engram", "config.json"), { project_name: proj });
|
|
119
|
+
ok(`Proyecto de memoria fijado en ${c.bold(".engram/config.json")} (${proj}).`);
|
|
120
|
+
if (agent === "claude-code" || agent === "both") ensureJarvisClaudeCode(cwd);
|
|
121
|
+
if (agent === "opencode" || agent === "both") ensureJarvisOpencode(cwd);
|
|
122
|
+
}
|
|
123
|
+
|
|
114
124
|
// 3) gitignore del histórico aislado
|
|
115
125
|
if (env.git.isRepo) {
|
|
116
126
|
const { added } = ensureGitignore(cwd, [".ozali/", ".engram/"]);
|
|
@@ -286,6 +296,102 @@ function ensureClaudeCodeProfile(cwd, scope) {
|
|
|
286
296
|
}
|
|
287
297
|
}
|
|
288
298
|
|
|
299
|
+
// ----------------------------------------------------------------- ozali-jarvis
|
|
300
|
+
const JARVIS_BEGIN = "<!-- ozali-jarvis:start -->";
|
|
301
|
+
const JARVIS_END = "<!-- ozali-jarvis:end -->";
|
|
302
|
+
|
|
303
|
+
/** Cuerpo del bloque jarvis para CLAUDE.md / AGENTS.md (sin el frontmatter del template). */
|
|
304
|
+
function jarvisPersonaBody() {
|
|
305
|
+
const tpl = fs.readFileSync(path.join(TEMPLATES_SRC, "ozali-jarvis.md"), "utf8");
|
|
306
|
+
// Quita el frontmatter YAML (--- ... ---) y deja el cuerpo markdown.
|
|
307
|
+
return tpl.replace(/^---[\s\S]*?---\s*/, "").trim();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Inserta/actualiza un bloque marcado en un archivo markdown (idempotente). */
|
|
311
|
+
function upsertMarkedBlock(file, body) {
|
|
312
|
+
const block = `${JARVIS_BEGIN}\n${body}\n${JARVIS_END}`;
|
|
313
|
+
let txt = exists(file) ? fs.readFileSync(file, "utf8") : "";
|
|
314
|
+
const re = new RegExp(`${JARVIS_BEGIN}[\\s\\S]*?${JARVIS_END}`);
|
|
315
|
+
let changed;
|
|
316
|
+
if (re.test(txt)) {
|
|
317
|
+
const next = txt.replace(re, block);
|
|
318
|
+
changed = next !== txt; txt = next;
|
|
319
|
+
} else {
|
|
320
|
+
txt = (txt.trim() ? txt.replace(/\s*$/, "") + "\n\n" : "") + block + "\n";
|
|
321
|
+
changed = true;
|
|
322
|
+
}
|
|
323
|
+
fs.writeFileSync(file, txt);
|
|
324
|
+
return changed;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function ensureJarvisClaudeCode(cwd) {
|
|
328
|
+
// 1) persona en CLAUDE.md (always-on)
|
|
329
|
+
const claudeMd = path.join(cwd, "CLAUDE.md");
|
|
330
|
+
const ch = upsertMarkedBlock(claudeMd, jarvisPersonaBody());
|
|
331
|
+
ok(`ozali-jarvis ${ch ? "escrito" : "ya presente"} en ${c.bold("CLAUDE.md")} (orquestador por defecto).`);
|
|
332
|
+
// 2) subagente
|
|
333
|
+
const agentFile = path.join(cwd, ".claude", "agents", "ozali-jarvis.md");
|
|
334
|
+
ensureDir(path.dirname(agentFile));
|
|
335
|
+
fs.copyFileSync(path.join(TEMPLATES_SRC, "ozali-jarvis.md"), agentFile);
|
|
336
|
+
ok(`Subagente ${c.bold(".claude/agents/ozali-jarvis.md")} instalado.`);
|
|
337
|
+
// 3) hooks de recordatorio (idempotentes)
|
|
338
|
+
ensureJarvisHooks(cwd);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function ensureJarvisHooks(cwd) {
|
|
342
|
+
const p = path.join(cwd, ".claude", "settings.json");
|
|
343
|
+
const cfg = readJSON(p, {});
|
|
344
|
+
cfg.hooks = cfg.hooks || {};
|
|
345
|
+
const reminder = (msg) => ({ hooks: [{ type: "command", command: `echo '[ozali-jarvis] ${msg}'` }] });
|
|
346
|
+
const want = {
|
|
347
|
+
SessionStart: reminder("recall-first: confirma proyecto (mem_current_project) y recupera contexto de Engram (mem_context) antes de actuar."),
|
|
348
|
+
PreCompact: reminder("antes de compactar: persiste el state recuperable en Engram (engram-convention §4)."),
|
|
349
|
+
SessionEnd: reminder("cierre: registra lo trabajado en Engram (scope project, español) y haz mem_session_summary."),
|
|
350
|
+
};
|
|
351
|
+
let added = 0;
|
|
352
|
+
for (const [evt, val] of Object.entries(want)) {
|
|
353
|
+
const arr = cfg.hooks[evt] || (cfg.hooks[evt] = []);
|
|
354
|
+
// idempotencia: no duplicar el recordatorio ozali-jarvis para ese evento
|
|
355
|
+
const has = JSON.stringify(arr).includes("[ozali-jarvis]");
|
|
356
|
+
if (!has) { arr.push(val); added++; }
|
|
357
|
+
}
|
|
358
|
+
writeJSON(p, cfg);
|
|
359
|
+
if (added) ok(`Hooks de recordatorio de ozali-jarvis añadidos a ${c.bold(".claude/settings.json")} (${added}).`);
|
|
360
|
+
else info("Hooks de ozali-jarvis ya presentes en Claude Code.");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function ensureJarvisOpencode(cwd) {
|
|
364
|
+
// 1) persona en AGENTS.md
|
|
365
|
+
const agentsMd = path.join(cwd, "AGENTS.md");
|
|
366
|
+
const ch = upsertMarkedBlock(agentsMd, jarvisPersonaBody());
|
|
367
|
+
ok(`ozali-jarvis ${ch ? "escrito" : "ya presente"} en ${c.bold("AGENTS.md")} (orquestador por defecto).`);
|
|
368
|
+
// 2) agente en opencode.json
|
|
369
|
+
const p = path.join(cwd, "opencode.json");
|
|
370
|
+
const cfg = readJSON(p, {});
|
|
371
|
+
cfg.$schema = cfg.$schema || "https://opencode.ai/config.json";
|
|
372
|
+
cfg.agent = cfg.agent || {};
|
|
373
|
+
if (!cfg.agent["ozali-jarvis"]) {
|
|
374
|
+
cfg.agent["ozali-jarvis"] = {
|
|
375
|
+
mode: "primary",
|
|
376
|
+
description: "Orquestador del proyecto: memoria Engram + puente a la skill cdk.",
|
|
377
|
+
prompt: "{file:./AGENTS.md}",
|
|
378
|
+
};
|
|
379
|
+
writeJSON(p, cfg);
|
|
380
|
+
ok(`Agente ${c.bold("ozali-jarvis")} (primary) añadido a ${c.bold("opencode.json")}.`);
|
|
381
|
+
} else {
|
|
382
|
+
info("Agente ozali-jarvis ya presente en opencode.json.");
|
|
383
|
+
}
|
|
384
|
+
// 3) plugin de recordatorio
|
|
385
|
+
const plugin = path.join(cwd, ".opencode", "plugins", "ozali-jarvis.js");
|
|
386
|
+
if (!exists(plugin)) {
|
|
387
|
+
ensureDir(path.dirname(plugin));
|
|
388
|
+
fs.copyFileSync(path.join(TEMPLATES_SRC, "ozali-jarvis-plugin.js"), plugin);
|
|
389
|
+
ok(`Plugin ${c.bold(".opencode/plugins/ozali-jarvis.js")} instalado.`);
|
|
390
|
+
} else {
|
|
391
|
+
info("Plugin de ozali-jarvis ya presente en opencode.");
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
289
395
|
// Marca el proyecto como confiable en Claude Code (~/.claude.json). Sin esto, Claude Code
|
|
290
396
|
// ignora los permisos de un .claude/settings.json de proyecto ("workspace not trusted").
|
|
291
397
|
async function ensureClaudeWorkspaceTrust(cwd, opts) {
|
|
@@ -342,6 +448,13 @@ export function doctor(cwd) {
|
|
|
342
448
|
add("Fuente de verdad", env.sot.found, env.sot.found ? `${env.sot.doc} + ${env.sot.dir}/` : "ausente (corre la skill 'ozali')");
|
|
343
449
|
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)");
|
|
344
450
|
add("Engram", env.engram.available, env.engram.available ? env.engram.bin : "no instalado → modo docs");
|
|
451
|
+
if (env.engram.available) {
|
|
452
|
+
const online = tryExec("engram", ["doctor"], { cwd }) !== null;
|
|
453
|
+
add("Engram en línea", online, online ? "engram doctor OK" : "engram doctor no responde");
|
|
454
|
+
}
|
|
455
|
+
const jarvis = detectJarvis(cwd);
|
|
456
|
+
// jarvis es opt-in (--no-jarvis): informativo, no cuenta como fallo.
|
|
457
|
+
add("ozali-jarvis", true, jarvis.present ? `configurado (${jarvis.where.join(", ")})` : "no configurado (--no-jarvis)");
|
|
345
458
|
const cloudOn = !!(cfg && cfg.cloud && cfg.cloud.enabled);
|
|
346
459
|
// Cloud es opt-in: "off" es un estado válido (no cuenta como fallo).
|
|
347
460
|
add("Engram Cloud", true, cloudOn ? `enrolado → ${cfg.cloud.server || "server por defecto"}` : "off (opt-in, git-sync activo)");
|
|
@@ -381,9 +494,31 @@ export function doctor(cwd) {
|
|
|
381
494
|
}
|
|
382
495
|
}
|
|
383
496
|
|
|
497
|
+
// Tendencia de uso de tokens (la escribe cdk en cada hito; informativo).
|
|
498
|
+
const metrics = readJSON(path.join(cwd, ".ozali", "metrics", "token-metrics.json"));
|
|
499
|
+
if (metrics && Array.isArray(metrics.hits) && metrics.hits.length) {
|
|
500
|
+
step("Tendencia de tokens (últimos hitos)");
|
|
501
|
+
for (const h of metrics.hits.slice(-3)) {
|
|
502
|
+
const saved = h.savedByRecall ? ` ${c.green("(ahorro recall: " + h.savedByRecall + ")")}` : "";
|
|
503
|
+
console.log(` ${c.dim(h.hito || "?")}: total ${h.total ?? "N/A"}${saved}`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
384
507
|
return bad === 0 ? 0 : 1;
|
|
385
508
|
}
|
|
386
509
|
|
|
510
|
+
/** Detecta si ozali-jarvis está configurado en el proyecto y dónde. */
|
|
511
|
+
function detectJarvis(cwd) {
|
|
512
|
+
const where = [];
|
|
513
|
+
const hasBlock = (f) => exists(f) && fs.readFileSync(f, "utf8").includes(JARVIS_BEGIN);
|
|
514
|
+
if (hasBlock(path.join(cwd, "CLAUDE.md"))) where.push("CLAUDE.md");
|
|
515
|
+
if (hasBlock(path.join(cwd, "AGENTS.md"))) where.push("AGENTS.md");
|
|
516
|
+
if (exists(path.join(cwd, ".claude", "agents", "ozali-jarvis.md"))) where.push("subagente");
|
|
517
|
+
const oc = readJSON(path.join(cwd, "opencode.json"));
|
|
518
|
+
if (oc && oc.agent && oc.agent["ozali-jarvis"]) where.push("opencode");
|
|
519
|
+
return { present: where.length > 0, where };
|
|
520
|
+
}
|
|
521
|
+
|
|
387
522
|
function readStrictTdd(cwd, sot) {
|
|
388
523
|
const f = path.join(cwd, sot.dir, "context", "tech-stack.md");
|
|
389
524
|
if (!exists(f)) return { found: false };
|
|
@@ -393,20 +528,72 @@ function readStrictTdd(cwd, sot) {
|
|
|
393
528
|
}
|
|
394
529
|
|
|
395
530
|
// =========================================================== update ==========
|
|
396
|
-
|
|
397
|
-
|
|
531
|
+
// Lleva una instalación existente al paquete actual: refresca la skill ozali (con sus
|
|
532
|
+
// references), los perfiles de permisos y **crea/refresca ozali-jarvis** (clave para repos
|
|
533
|
+
// inicializados antes de 0.4.0). La skill `cdk` la regenera el AGENTE (no el CLI): se detecta
|
|
534
|
+
// y se guía la regeneración.
|
|
535
|
+
export function update(cwd, opts = {}) {
|
|
536
|
+
step("ozali update — actualizar la instalación al paquete actual");
|
|
398
537
|
const env = detectAll(cwd);
|
|
399
|
-
if (!env.skill.installed) { warn("No hay skill ozali instalada. Corre " + c.bold("ozali init") + " primero."); return 1; }
|
|
400
|
-
for (const p of env.skill.paths) {
|
|
401
|
-
copyDir(SKILL_SRC, p);
|
|
402
|
-
ok(`Actualizada: ${path.relative(cwd, p) || p} → v${pkgVersion()}`);
|
|
403
|
-
}
|
|
404
538
|
const cfgPath = CONFIG_PATH(cwd);
|
|
405
539
|
const cfg = readJSON(cfgPath);
|
|
540
|
+
if (!env.skill.installed && !cfg) {
|
|
541
|
+
warn("No hay instalación de ozali en esta ruta. Corre " + c.bold("ozali init") + " primero.");
|
|
542
|
+
return 1;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// 1) Skill ozali (incluye las references: la base desde la que el agente regenera cdk)
|
|
546
|
+
if (env.skill.installed) {
|
|
547
|
+
for (const p of env.skill.paths) {
|
|
548
|
+
copyDir(SKILL_SRC, p);
|
|
549
|
+
ok(`Skill ozali actualizada: ${path.relative(cwd, p) || p} → v${pkgVersion()}`);
|
|
550
|
+
}
|
|
551
|
+
} else {
|
|
552
|
+
warn("Skill ozali no instalada en esta ruta (corre " + c.bold("ozali init") + " para instalarla).");
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Agente/scope: del config; si falta, infiere del entorno.
|
|
556
|
+
const agent = (cfg && cfg.agent) || (env.agents.opencode.present && !env.agents.claudeCode.present ? "opencode"
|
|
557
|
+
: env.agents.claudeCode.present && env.agents.opencode.present ? "both" : "claude-code");
|
|
558
|
+
const scope = (cfg && cfg.scope) || "project";
|
|
559
|
+
|
|
560
|
+
// 2) Perfiles base de permisos (idempotente: recoge defaults nuevos del paquete)
|
|
561
|
+
if (agent === "claude-code" || agent === "both") ensureClaudeCodeProfile(cwd, scope);
|
|
562
|
+
if (agent === "opencode" || agent === "both") ensureOpencodeProfile(cwd);
|
|
563
|
+
|
|
564
|
+
// 3) ozali-jarvis: crea el orquestador en repos previos a 0.4.0 y refresca el resto.
|
|
565
|
+
if (!opts.noJarvis) {
|
|
566
|
+
const proj = projectName(cwd);
|
|
567
|
+
const engPath = path.join(cwd, ".engram", "config.json");
|
|
568
|
+
if (!exists(engPath)) { writeJSON(engPath, { project_name: proj }); ok(`Proyecto de memoria fijado en ${c.bold(".engram/config.json")} (${proj}).`); }
|
|
569
|
+
if (agent === "claude-code" || agent === "both") ensureJarvisClaudeCode(cwd);
|
|
570
|
+
if (agent === "opencode" || agent === "both") ensureJarvisOpencode(cwd);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// 4) Skill cdk: la genera el AGENTE (Fase 6), el CLI no la regenera.
|
|
574
|
+
const cdk = detectCdk(cwd);
|
|
575
|
+
if (cdk.installed) {
|
|
576
|
+
step("Skill cdk (generada por el agente)");
|
|
577
|
+
warn("cdk no se actualiza desde el CLI: la regenera tu agente con el contrato nuevo.");
|
|
578
|
+
info("Abre tu agente y vuelve a correr la skill " + c.bold("ozali") + " para regenerar cdk (ozali-jarvis, recall-first §7, telemetría).");
|
|
579
|
+
info("Tus docs por hito (" + c.bold(".ozali/docs/cdk/") + ") y el plan congelado se conservan.");
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// 5) versión del config
|
|
406
583
|
if (cfg) { cfg.version = pkgVersion(); cfg.updatedAt = new Date().toISOString(); writeJSON(cfgPath, cfg); }
|
|
584
|
+
ok(`Instalación al día con ozali v${pkgVersion()}.`);
|
|
407
585
|
return 0;
|
|
408
586
|
}
|
|
409
587
|
|
|
588
|
+
/** ¿Existe la skill cdk (generada por el agente)? */
|
|
589
|
+
function detectCdk(cwd) {
|
|
590
|
+
const paths = [
|
|
591
|
+
path.join(cwd, ".claude", "skills", "cdk", "SKILL.md"),
|
|
592
|
+
path.join(HOME, ".claude", "skills", "cdk", "SKILL.md"),
|
|
593
|
+
].filter(exists);
|
|
594
|
+
return { installed: paths.length > 0, paths };
|
|
595
|
+
}
|
|
596
|
+
|
|
410
597
|
// ============================================================= sync ===========
|
|
411
598
|
export function sync(cwd, opts) {
|
|
412
599
|
step(`ozali sync${opts.import ? " --import" : ""} — histórico ↔ repo de conocimiento`);
|
|
@@ -496,3 +683,77 @@ export function sync(cwd, opts) {
|
|
|
496
683
|
}
|
|
497
684
|
return 0;
|
|
498
685
|
}
|
|
686
|
+
|
|
687
|
+
// ============================================================ audit ===========
|
|
688
|
+
// Navega/audita la memoria de Engram: del proyecto actual o general (todos los
|
|
689
|
+
// proyectos). Sin contexto de proyecto en la ruta → general. Sin Engram → audita
|
|
690
|
+
// el histórico local de documentos.
|
|
691
|
+
export async function audit(cwd, opts) {
|
|
692
|
+
step("ozali audit — auditoría de memoria (Engram)");
|
|
693
|
+
const env = detectAll(cwd);
|
|
694
|
+
const cfg = readJSON(CONFIG_PATH(cwd));
|
|
695
|
+
const engramCfg = readJSON(path.join(cwd, ".engram", "config.json"));
|
|
696
|
+
const project = (engramCfg && engramCfg.project_name) || (cfg && cfg.project) || projectName(cwd);
|
|
697
|
+
const hasProjectContext = env.git.isRepo || !!cfg || !!engramCfg;
|
|
698
|
+
|
|
699
|
+
// Resolver alcance: --general fuerza general; sin contexto → general; en repo se propone elegir.
|
|
700
|
+
let scope = (opts.general || !hasProjectContext) ? "general" : "project";
|
|
701
|
+
if (!hasProjectContext) info("Sin contexto de proyecto en esta ruta → auditoría " + c.bold("general") + ".");
|
|
702
|
+
else if (scope === "project" && !opts.general && !opts.yes) {
|
|
703
|
+
scope = await select("¿Qué auditoría quieres?", [
|
|
704
|
+
{ value: "project", label: `Proyecto (${c.bold(project)})` },
|
|
705
|
+
{ value: "general", label: "General (todos los proyectos en Engram)" },
|
|
706
|
+
], 0);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Sin Engram → auditar el histórico local de documentos.
|
|
710
|
+
if (!env.engram.available) {
|
|
711
|
+
warn("Engram no está instalado → auditoría desde documentos locales.");
|
|
712
|
+
return auditFromDocs(cwd, project);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Navegador interactivo.
|
|
716
|
+
if (opts.tui) {
|
|
717
|
+
info("Abriendo el navegador interactivo de Engram (engram tui)…");
|
|
718
|
+
return spawnCmd("engram", ["tui"], { cwd });
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (scope === "general") {
|
|
722
|
+
step("Proyectos en Engram");
|
|
723
|
+
spawnCmd("engram", ["projects", "list"], { cwd });
|
|
724
|
+
step("Estadísticas (global)");
|
|
725
|
+
spawnCmd("engram", ["stats"], { cwd });
|
|
726
|
+
step("Contexto reciente");
|
|
727
|
+
spawnCmd("engram", ["context"], { cwd });
|
|
728
|
+
} else {
|
|
729
|
+
step(`Contexto reciente — ${project}`);
|
|
730
|
+
spawnCmd("engram", ["context", project], { cwd });
|
|
731
|
+
step("Estadísticas (global)");
|
|
732
|
+
spawnCmd("engram", ["stats"], { cwd });
|
|
733
|
+
}
|
|
734
|
+
if (opts.search) {
|
|
735
|
+
step(`Búsqueda: "${opts.search}"`);
|
|
736
|
+
spawnCmd("engram", ["search", opts.search], { cwd });
|
|
737
|
+
}
|
|
738
|
+
info("Navegación interactiva: " + c.bold("ozali audit --tui") + " · búsqueda: " + c.bold('ozali audit --search "<texto>"'));
|
|
739
|
+
return 0;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function auditFromDocs(cwd, project) {
|
|
743
|
+
const docsDir = path.join(cwd, ".ozali", "docs", "cdk");
|
|
744
|
+
if (exists(docsDir)) {
|
|
745
|
+
step(`Hitos documentados localmente — ${project}`);
|
|
746
|
+
const hitos = fs.readdirSync(docsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
747
|
+
if (hitos.length) for (const e of hitos) console.log(" • " + e.name);
|
|
748
|
+
else info("Aún no hay hitos en .ozali/docs/cdk/.");
|
|
749
|
+
} else {
|
|
750
|
+
info("No hay histórico local en .ozali/docs/cdk/ todavía.");
|
|
751
|
+
}
|
|
752
|
+
const metrics = readJSON(path.join(cwd, ".ozali", "metrics", "token-metrics.json"));
|
|
753
|
+
if (metrics && Array.isArray(metrics.hits) && metrics.hits.length) {
|
|
754
|
+
step("Uso de tokens (últimos hitos)");
|
|
755
|
+
for (const h of metrics.hits.slice(-5)) console.log(` ${c.dim(h.hito || "?")}: total ${h.total ?? "N/A"}`);
|
|
756
|
+
}
|
|
757
|
+
info("Instala Engram para auditoría buscable/acumulativa (" + c.bold("ozali doctor") + ").");
|
|
758
|
+
return 0;
|
|
759
|
+
}
|
package/cli/lib/util.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { execFileSync } from "node:child_process";
|
|
|
10
10
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
11
11
|
export const PKG_ROOT = path.resolve(HERE, "..", "..");
|
|
12
12
|
export const SKILL_SRC = path.join(PKG_ROOT, "skill");
|
|
13
|
+
export const TEMPLATES_SRC = path.join(PKG_ROOT, "templates");
|
|
13
14
|
|
|
14
15
|
export function pkgVersion() {
|
|
15
16
|
try {
|
package/cli/test/smoke.test.mjs
CHANGED
|
@@ -33,9 +33,20 @@ test("--version imprime la versión del package.json", () => {
|
|
|
33
33
|
assert.equal(stdout.trim(), pkg.version);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
test("--help menciona los
|
|
36
|
+
test("--help menciona los comandos", () => {
|
|
37
37
|
const { stdout } = run(["--help"]);
|
|
38
|
-
for (const cmd of ["init", "doctor", "update", "sync"]) assert.match(stdout, new RegExp(cmd));
|
|
38
|
+
for (const cmd of ["init", "doctor", "update", "sync", "audit"]) assert.match(stdout, new RegExp(cmd));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("audit imprime cabecera y no rompe (general)", () => {
|
|
42
|
+
const dir = tmpProject();
|
|
43
|
+
try {
|
|
44
|
+
// Sin Engram → fallback a docs; con Engram → comandos read-only. Ambos exit 0.
|
|
45
|
+
const { stdout } = run(["audit", "--general", "--yes"], dir);
|
|
46
|
+
assert.match(stdout, /auditoría de memoria/);
|
|
47
|
+
} finally {
|
|
48
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
49
|
+
}
|
|
39
50
|
});
|
|
40
51
|
|
|
41
52
|
test("comando desconocido sale con código 1", () => {
|
|
@@ -71,6 +82,51 @@ test("init --yes instala la skill, aísla el histórico y escribe config", () =>
|
|
|
71
82
|
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
72
83
|
assert.ok(Array.isArray(settings.permissions.allow) && settings.permissions.allow.length > 0, "allow no vacío");
|
|
73
84
|
assert.ok(settings.permissions.deny.includes("Bash(rm -rf *)"), "deny bloquea rm -rf");
|
|
85
|
+
// ozali-jarvis (Claude Code)
|
|
86
|
+
assert.match(fs.readFileSync(path.join(dir, "CLAUDE.md"), "utf8"), /ozali-jarvis:start/, "bloque jarvis en CLAUDE.md");
|
|
87
|
+
assert.ok(fs.existsSync(path.join(dir, ".claude", "agents", "ozali-jarvis.md")), "subagente jarvis");
|
|
88
|
+
assert.ok(settings.hooks && settings.hooks.SessionStart, "hooks de recordatorio jarvis");
|
|
89
|
+
const eng = JSON.parse(fs.readFileSync(path.join(dir, ".engram", "config.json"), "utf8"));
|
|
90
|
+
assert.ok(eng.project_name, ".engram/config.json con project_name");
|
|
91
|
+
} finally {
|
|
92
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("init --no-jarvis no crea el orquestador", () => {
|
|
97
|
+
const dir = tmpProject();
|
|
98
|
+
try {
|
|
99
|
+
run(["init", "--yes", "--no-engram", "--no-trust", "--no-jarvis", "--agent", "claude-code", "--scope", "project", "--knowledge-repo", path.join(dir, ".k")], dir);
|
|
100
|
+
assert.ok(!fs.existsSync(path.join(dir, "CLAUDE.md")), "sin CLAUDE.md");
|
|
101
|
+
assert.ok(!fs.existsSync(path.join(dir, ".claude", "agents", "ozali-jarvis.md")), "sin subagente jarvis");
|
|
102
|
+
assert.ok(!fs.existsSync(path.join(dir, ".engram", "config.json")), "sin .engram/config.json");
|
|
103
|
+
} finally {
|
|
104
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("init opencode crea jarvis (AGENTS.md + agente + plugin)", () => {
|
|
109
|
+
const dir = tmpProject();
|
|
110
|
+
try {
|
|
111
|
+
run(["init", "--yes", "--no-engram", "--no-trust", "--agent", "opencode", "--scope", "project", "--knowledge-repo", path.join(dir, ".k")], dir);
|
|
112
|
+
assert.match(fs.readFileSync(path.join(dir, "AGENTS.md"), "utf8"), /ozali-jarvis:start/, "bloque jarvis en AGENTS.md");
|
|
113
|
+
const oc = JSON.parse(fs.readFileSync(path.join(dir, "opencode.json"), "utf8"));
|
|
114
|
+
assert.equal(oc.agent["ozali-jarvis"].mode, "primary", "agente jarvis primary en opencode.json");
|
|
115
|
+
assert.ok(fs.existsSync(path.join(dir, ".opencode", "plugins", "ozali-jarvis.js")), "plugin jarvis opencode");
|
|
116
|
+
} finally {
|
|
117
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("update agrega ozali-jarvis en un repo que no lo tenía", () => {
|
|
122
|
+
const dir = tmpProject();
|
|
123
|
+
try {
|
|
124
|
+
run(["init", "--yes", "--no-engram", "--no-trust", "--no-jarvis", "--agent", "claude-code", "--scope", "project", "--knowledge-repo", path.join(dir, ".k")], dir);
|
|
125
|
+
assert.ok(!fs.existsSync(path.join(dir, ".claude", "agents", "ozali-jarvis.md")), "precondición: sin jarvis");
|
|
126
|
+
run(["update"], dir);
|
|
127
|
+
assert.ok(fs.existsSync(path.join(dir, ".claude", "agents", "ozali-jarvis.md")), "update crea subagente jarvis");
|
|
128
|
+
assert.match(fs.readFileSync(path.join(dir, "CLAUDE.md"), "utf8"), /ozali-jarvis:start/, "update crea bloque jarvis");
|
|
129
|
+
assert.ok(fs.existsSync(path.join(dir, ".engram", "config.json")), "update fija .engram/config.json");
|
|
74
130
|
} finally {
|
|
75
131
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
76
132
|
}
|
package/docs/guia-de-uso.md
CHANGED
|
@@ -82,9 +82,10 @@ Lleva el histórico y la memoria al repo de conocimiento del equipo. Tus compañ
|
|
|
82
82
|
|---|---|
|
|
83
83
|
| `ozali init` | Prepara el proyecto (lo corres una vez por repo). |
|
|
84
84
|
| `ozali doctor` | Revisa que todo esté bien. Es solo lectura, no cambia nada. |
|
|
85
|
-
| `ozali update` | Actualiza
|
|
85
|
+
| `ozali update` | Actualiza skill ozali + ozali-jarvis + permisos (y guía cómo regenerar cdk). |
|
|
86
86
|
| `ozali sync` | Sube el histórico/memoria al repo de equipo. |
|
|
87
87
|
| `ozali sync --import` | Baja lo que el equipo ya guardó. |
|
|
88
|
+
| `ozali audit` | Navega/audita la memoria de Engram (qué se ha hecho). |
|
|
88
89
|
|
|
89
90
|
¿Algo no funciona? Corre **`ozali doctor`** primero: te dice qué falta y cómo arreglarlo.
|
|
90
91
|
|
|
@@ -119,6 +120,23 @@ Ejemplo — permitir Docker además de lo de fábrica:
|
|
|
119
120
|
|
|
120
121
|
---
|
|
121
122
|
|
|
123
|
+
## ozali-jarvis: el orquestador (no necesitas /cdk)
|
|
124
|
+
|
|
125
|
+
`init` crea **ozali-jarvis**, el "cerebro" por defecto de tu proyecto. A partir de ahí, **en cada
|
|
126
|
+
sesión y sin escribir `/cdk`**, tu agente:
|
|
127
|
+
|
|
128
|
+
- **recuerda** lo que el equipo trabajó antes (recupera contexto de Engram al iniciar),
|
|
129
|
+
- **registra** lo que van haciendo (decisiones, acciones, aprendizajes) para que quede acumulado,
|
|
130
|
+
- y cuando toca **escribir código con disciplina**, delega en la skill `cdk` (plan, aprobación, TDD).
|
|
131
|
+
|
|
132
|
+
Sigues pudiendo invocar `/cdk` cuando quieras; jarvis simplemente lo usa como referencia y mantiene
|
|
133
|
+
todo en contexto. ¿No lo quieres? `init --no-jarvis`.
|
|
134
|
+
|
|
135
|
+
### Menos tokens, más contexto
|
|
136
|
+
Con Engram en línea, jarvis y `cdk` trabajan **recall-first**: antes de releer archivos o re-analizar,
|
|
137
|
+
reutilizan el análisis/resumen que ya está en memoria (si el código no cambió). Eso gasta **menos
|
|
138
|
+
tokens** y mantiene el contexto enfocado. Puedes ver la tendencia de consumo con `ozali doctor`.
|
|
139
|
+
|
|
122
140
|
## Engram: la memoria del equipo (opcional)
|
|
123
141
|
|
|
124
142
|
Durante `init`, ozali **instala Engram por ti**: en modo interactivo te pregunta (basta con aceptar),
|
|
@@ -142,6 +160,17 @@ Más detalle técnico en [engram-convention.md](../skill/references/engram-conve
|
|
|
142
160
|
|
|
143
161
|
---
|
|
144
162
|
|
|
163
|
+
## Auditar lo que se ha hecho (`ozali audit`)
|
|
164
|
+
|
|
165
|
+
¿Quieres ver qué ha registrado el equipo? `ozali audit`:
|
|
166
|
+
|
|
167
|
+
- **Dentro de un repo:** te propone auditar **ese proyecto** o **todos** (general).
|
|
168
|
+
- **Fuera de un repo** (una ruta cualquiera): va directo a la auditoría **general**.
|
|
169
|
+
|
|
170
|
+
Atajos: `ozali audit --tui` abre un navegador interactivo de la memoria; `ozali audit --search "rfc"`
|
|
171
|
+
busca un término; `ozali audit --general` fuerza el alcance general. Si no tienes Engram, audita el
|
|
172
|
+
histórico local de `.ozali/docs/`.
|
|
173
|
+
|
|
145
174
|
## Preguntas frecuentes
|
|
146
175
|
|
|
147
176
|
**¿Tengo que usar Engram?**
|
|
@@ -161,6 +190,11 @@ Es el aviso de seguridad de Claude Code: ignora los permisos del proyecto hasta
|
|
|
161
190
|
Deja que `init` lo marque como confiable, o abre Claude Code en la carpeta y acepta el diálogo una
|
|
162
191
|
vez. Tras eso, el aviso desaparece y los permisos surten efecto.
|
|
163
192
|
|
|
193
|
+
**Actualicé ozali pero no veo ozali-jarvis (o cdk quedó viejo).**
|
|
194
|
+
Corre `ozali update` en el repo: refresca la skill ozali, los permisos y **crea/actualiza
|
|
195
|
+
ozali-jarvis**. La skill `cdk` la regenera tu agente: tras `ozali update`, abre el agente y vuelve a
|
|
196
|
+
correr la skill `ozali` para regenerar `cdk` con lo nuevo (tus docs por hito y el plan se conservan).
|
|
197
|
+
|
|
164
198
|
**¿Funciona en Windows?**
|
|
165
199
|
Sí. Los permisos base incluyen variantes PowerShell, y Engram se instala con `go install` o un
|
|
166
200
|
binario descargable.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ozali",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
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": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"cli/",
|
|
14
14
|
"skill/",
|
|
15
|
+
"templates/",
|
|
15
16
|
"docs/",
|
|
16
17
|
"README.md",
|
|
17
18
|
"LICENSE"
|
package/skill/SKILL.md
CHANGED
|
@@ -308,12 +308,25 @@ recuperable, acumulativo). Así la herramienta **aprende de lo que el equipo va
|
|
|
308
308
|
completo (naming, llamadas inline, degradación) en
|
|
309
309
|
[`references/engram-convention.md`](references/engram-convention.md).
|
|
310
310
|
|
|
311
|
+
> **Orquestador por defecto (`ozali-jarvis`):** `ozali init` crea el agente **ozali-jarvis**
|
|
312
|
+
> (persona en CLAUDE.md/AGENTS.md + subagente + hooks) que opera **memoria-aware en toda sesión, sin
|
|
313
|
+
> necesidad de `/cdk`**: recupera contexto de Engram al iniciar, registra el trabajo del equipo
|
|
314
|
+
> conforme avanza, y **delega la ejecución disciplinada en `cdk`**. `cdk` y jarvis comparten el mismo
|
|
315
|
+
> contrato de memoria (esta convención).
|
|
316
|
+
|
|
311
317
|
**Mínimos que `cdk` debe cablear** (resumen; el detalle y los `mem_*` exactos están en la convención):
|
|
312
318
|
|
|
313
|
-
- **Al iniciar un hito** →
|
|
314
|
-
`
|
|
315
|
-
|
|
316
|
-
|
|
319
|
+
- **Al iniciar un hito** → handshake "Engram en línea" (`mem_current_project` + `mem_context`) +
|
|
320
|
+
`mem_save_prompt` (captura el prompt real una vez) + `mem_search` de `cdk/{hito}/state`,
|
|
321
|
+
`cdk/_project/testing-capabilities`, `cdk/_project/token-metrics` y `cdk/{hito}/*` →
|
|
322
|
+
`mem_get_observation` **selectivo** de lo relevante. Si hay `state` con pendientes, **reanuda**; con
|
|
323
|
+
`testing-capabilities` resuelve el modo TDD.
|
|
324
|
+
- **Recall-first (ahorro de tokens/contexto)** → antes de releer/re-analizar, reusar el
|
|
325
|
+
`analisis`/`resumen-tecnico` previo si su `ultimo_commit` sigue vigente (guard de staleness); si el
|
|
326
|
+
código cambió, re-analizar solo el delta. Recuperación selectiva y restauración tras compactación
|
|
327
|
+
desde `state`+`bitacora`. Ver convención §7.
|
|
328
|
+
- **Telemetría** → espejar `uso-tokens` a `cdk/{hito}/uso-tokens`, agregar en
|
|
329
|
+
`cdk/_project/token-metrics` y escribir `.ozali/metrics/token-metrics.json` (lo lee `ozali doctor`).
|
|
317
330
|
- **Al aprobar el GATE** → `mem_save` de `cdk/{hito}/plan-aprobado` **una sola vez** (inmutable).
|
|
318
331
|
- **Durante el ciclo** → `mem_update` (upsert) de `cdk/{hito}/bitacora` y `cdk/{hito}/state` tras
|
|
319
332
|
cada transición de fase (el archivo `05` sigue siendo append-only como fuente de verdad).
|
|
@@ -55,13 +55,15 @@ Para cada subagente, `cdk` debe generar un `.claude/agents/<rol>.md` con frontma
|
|
|
55
55
|
### 3. project-analyzer
|
|
56
56
|
- **Misión:** entender el código existente y el impacto del cambio. *No se planea lo que
|
|
57
57
|
no se ha entendido: primero el terreno, después el plano.*
|
|
58
|
-
- **Responsabilidades:** **primero
|
|
59
|
-
`
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
**
|
|
58
|
+
- **Responsabilidades:** **recall antes de recomputar** — primero `mem_search` del `analisis`/
|
|
59
|
+
`resumen-tecnico` previo del área; si existe con `ultimo_commit` vigente, **reúsalo** (vía
|
|
60
|
+
`mem_get_observation` selectivo) en vez de releer archivos grandes; si el código cambió, analiza
|
|
61
|
+
**solo el delta** (convención §7). Ejecuta **siempre** el harness
|
|
62
|
+
`node .claude/skills/cdk/verify-structure.mjs --grep <palabra-clave>` (estructural, barato) como
|
|
63
|
+
**base factual** (estructura real, clases clave, discrepancias doc↔código, candidatos a
|
|
64
|
+
**reutilización antes de crear**). Luego mapea dependencias, identifica **hitos de importancia** y
|
|
65
|
+
riesgos, superficies afectadas y contratos en riesgo. Marca los riesgos **🔴 BLOQUEANTES** que
|
|
66
|
+
detienen el flujo hasta resolverse. Es el autor del **Documento 1 (análisis)**.
|
|
65
67
|
- **Entrada:** tarea. **Salida:** informe de impacto + hitos (+ preguntas abiertas al usuario).
|
|
66
68
|
- **Herramientas:** solo lectura (Read, Grep, Glob) + ejecución del harness (Bash/PowerShell
|
|
67
69
|
acotado a `node verify-structure.mjs`).
|
|
@@ -70,7 +72,10 @@ Para cada subagente, `cdk` debe generar un `.claude/agents/<rol>.md` con frontma
|
|
|
70
72
|
- **Misión:** enrutar el trabajo entre subagentes e integrar resultados.
|
|
71
73
|
- **Responsabilidades:** decide qué subagente actúa y en qué orden; gestiona iteraciones
|
|
72
74
|
executioners ⇄ tester; consolida la salida final. **Alimenta al `project-documenter`** con
|
|
73
|
-
los eventos del ciclo (dudas, decisiones, desvíos) para la bitácora `05`.
|
|
75
|
+
los eventos del ciclo (dudas, decisiones, desvíos) para la bitácora `05`. **Hace cumplir el
|
|
76
|
+
recall-first** (convención §7): recuperación selectiva (no volcar toda la memoria), restauración
|
|
77
|
+
desde `state`+`bitacora` tras compactación, y consulta `cdk/_project/token-metrics` al iniciar para
|
|
78
|
+
ajustar la agresividad del recall.
|
|
74
79
|
- **Modo `--auto`:** si el hito se invocó con `--auto`, recorre el hito de punta a punta
|
|
75
80
|
**sin prompts** —incluida la **lectura de rutas no contempladas** durante el análisis y la
|
|
76
81
|
ejecución post-GATE— con el **único alto en el 🛑 GATE del plan**. Si algo cae **fuera de
|
|
@@ -117,6 +122,8 @@ Para cada subagente, `cdk` debe generar un `.claude/agents/<rol>.md` con frontma
|
|
|
117
122
|
- `03-resumen-tecnico.md`, `04-resumen-usuario.md` y `06-uso-tokens.md` se escriben **al
|
|
118
123
|
cierre**. El `06` solo rellena métricas si el proveedor es **Claude/Anthropic** (fuente
|
|
119
124
|
`/cost`); con otro proveedor deja la estructura + nombre del proveedor y métricas `N/A`.
|
|
125
|
+
Además **espeja** `06` a Engram (`cdk/{hito}/uso-tokens`), actualiza el agregado
|
|
126
|
+
`cdk/_project/token-metrics` y escribe `.ozali/metrics/token-metrics.json` (lo lee `ozali doctor`).
|
|
120
127
|
- **Herramientas:** escritura de documentación (Read, Write).
|
|
121
128
|
|
|
122
129
|
### 8. tester
|
|
@@ -291,6 +291,13 @@ usuario, en:
|
|
|
291
291
|
| Total de tiempo de sesión | <n / N/A> |
|
|
292
292
|
| Costo estimado | <$ / N/A> |
|
|
293
293
|
|
|
294
|
+
## Ahorro por recall (Engram)
|
|
295
|
+
| Métrica | Valor |
|
|
296
|
+
| :-- | :-- |
|
|
297
|
+
| ¿Se reusó memoria? (recall-first) | sí / no |
|
|
298
|
+
| Artefactos reusados | <p. ej. cdk/<hito>/analisis @ <commit>> |
|
|
299
|
+
| Relectura evitada (estimada) | <n tokens / N/A> |
|
|
300
|
+
|
|
294
301
|
## Notas
|
|
295
302
|
- <observaciones sobre el consumo del hito; deja vacío o "N/A" si no puedes encontrar información del proveedor>
|
|
296
303
|
```
|
|
@@ -299,3 +306,23 @@ usuario, en:
|
|
|
299
306
|
> derivar ni inventar cifras de otro sistema: solo registra el nombre del proveedor y deja
|
|
300
307
|
> el resto en `N/A`. El objetivo es **mantener la estructura presente** para no perder la
|
|
301
308
|
> trazabilidad del hito.
|
|
309
|
+
|
|
310
|
+
### Espejo en Engram + agregado (telemetría)
|
|
311
|
+
|
|
312
|
+
Al cerrar, además del doc, `cdk` **espeja** las métricas a Engram y mantiene un agregado por
|
|
313
|
+
proyecto (ver [`engram-convention.md`](engram-convention.md) §7):
|
|
314
|
+
|
|
315
|
+
- `cdk/{hito}/uso-tokens` — métricas del hito (mismo contenido que el doc).
|
|
316
|
+
- `cdk/_project/token-metrics` — agregado de los últimos hitos (para ajustar el recall-first).
|
|
317
|
+
- `.ozali/metrics/token-metrics.json` — copia **local** que lee `ozali doctor` para la tendencia:
|
|
318
|
+
|
|
319
|
+
```json
|
|
320
|
+
{
|
|
321
|
+
"hits": [
|
|
322
|
+
{ "hito": "<slug>", "input": 0, "output": 0, "total": 0, "savedByRecall": 0, "at": "<ISO-8601>" }
|
|
323
|
+
]
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
> `savedByRecall` = estimación de tokens de relectura evitados por reusar memoria. Con proveedor sin
|
|
328
|
+
> métricas, deja `0`/`N/A` pero mantén la estructura.
|
|
@@ -222,3 +222,50 @@ Internamente (Fase C lo implementa):
|
|
|
222
222
|
|
|
223
223
|
> Trazabilidad: cada doc lleva `ultimo_commit`/`Commit:` del repo principal en su encabezado, así
|
|
224
224
|
> el histórico aislado siempre apunta al código exacto que documenta.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 7. Rendimiento: recall-first + telemetría de tokens
|
|
229
|
+
|
|
230
|
+
El objetivo de esta sección es **gastar menos tokens y contexto** reutilizando lo que el equipo ya
|
|
231
|
+
guardó en Engram, en vez de recomputarlo. Aplica tanto al uso **ambiental** (orquestador
|
|
232
|
+
`ozali-jarvis`, sin `/cdk`) como por **hito** (`cdk`).
|
|
233
|
+
|
|
234
|
+
### 7.1 Handshake "Engram en línea"
|
|
235
|
+
Al iniciar sesión/hito: `mem_current_project` (confirma que coincide con `.engram/config.json`) +
|
|
236
|
+
`mem_context`. Si responde → **modo recall-first activo**. Si no responde → degrada a `docs` (§1);
|
|
237
|
+
la ausencia de Engram nunca bloquea.
|
|
238
|
+
|
|
239
|
+
### 7.2 Recall-first con guard de staleness
|
|
240
|
+
**Antes** de releer archivos o re-analizar, `mem_search` del `analisis` / `plan-aprobado` /
|
|
241
|
+
`resumen-tecnico` del área. Cada artefacto lleva `ultimo_commit` (§6):
|
|
242
|
+
|
|
243
|
+
- Si el **SHA actual coincide** (o los archivos del área no cambiaron) → **reusar** el resumen
|
|
244
|
+
conciso vía `mem_get_observation`, **sin releer el código**. Ahorra tokens de entrada.
|
|
245
|
+
- Si **cambió** → re-analizar **solo el delta** (los archivos tocados desde `ultimo_commit`), no todo.
|
|
246
|
+
|
|
247
|
+
> El harness `verify-structure.mjs` (estructural, barato) puede seguir corriendo; lo que se evita es
|
|
248
|
+
> **releer archivos grandes** que ya tienen un resumen vigente en memoria.
|
|
249
|
+
|
|
250
|
+
### 7.3 Recuperación selectiva
|
|
251
|
+
`mem_search` devuelve **previews truncados**. Haz `mem_get_observation` **solo** de los pocos
|
|
252
|
+
artefactos que realmente necesitas. **Nunca** vuelques toda la memoria al contexto.
|
|
253
|
+
|
|
254
|
+
### 7.4 Restauración tras compactación
|
|
255
|
+
Tras una compactación (o reabrir sesión), reconstruye desde `state` (§4) + `bitacora` —ambos
|
|
256
|
+
concisos— en vez de re-derivar todo el contexto releyendo conversación y archivos.
|
|
257
|
+
|
|
258
|
+
### 7.5 Guardar conciso para recall barato
|
|
259
|
+
Guarda resúmenes **estructurados y deduplicados** (no volcados crudos). Mientras más conciso el
|
|
260
|
+
artefacto, menos cuesta recuperarlo después.
|
|
261
|
+
|
|
262
|
+
### 7.6 Telemetría de tokens (medir → mejorar)
|
|
263
|
+
- Espeja el uso de tokens del hito a `cdk/{hito}/uso-tokens` y mantén un agregado por proyecto en
|
|
264
|
+
`cdk/_project/token-metrics`.
|
|
265
|
+
- También escribe un resumen **local** en `.ozali/metrics/token-metrics.json` (últimos N hitos:
|
|
266
|
+
`{ hito, input, output, total, savedByRecall, at }`) para que `ozali doctor` muestre la tendencia
|
|
267
|
+
sin consultar el MCP.
|
|
268
|
+
- **Al iniciar**, recupera `cdk/_project/token-metrics`: si hitos similares fueron pesados, sé más
|
|
269
|
+
agresivo con recall-first (resume antes, evita relecturas grandes).
|
|
270
|
+
|
|
271
|
+
> `savedByRecall` = estimación de relectura evitada por reusar memoria (ver plantilla `06-uso-tokens`).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// ozali-jarvis-plugin — recordatorios de memoria (Engram) para opencode.
|
|
2
|
+
// Generado por `ozali init`. NO es la fuente de verdad del comportamiento:
|
|
3
|
+
// la persona ozali-jarvis vive en AGENTS.md + el agente de opencode.json.
|
|
4
|
+
// Este plugin solo refuerza, en eventos de sesión, el ciclo recall → capturar → resumir.
|
|
5
|
+
// Marca de idempotencia: ozali-jarvis (no reescribir si ya existe esta marca).
|
|
6
|
+
|
|
7
|
+
/** @type {import("@opencode-ai/plugin").Plugin} */
|
|
8
|
+
export const OzaliJarvis = async () => {
|
|
9
|
+
const note = (m) => { try { console.log(`[ozali-jarvis] ${m}`); } catch { /* noop */ } };
|
|
10
|
+
return {
|
|
11
|
+
event: async ({ event }) => {
|
|
12
|
+
switch (event && event.type) {
|
|
13
|
+
case "session.created":
|
|
14
|
+
note("recall-first: confirma proyecto (mem_current_project) y recupera contexto de Engram (mem_context) antes de actuar.");
|
|
15
|
+
break;
|
|
16
|
+
case "experimental.session.compacting":
|
|
17
|
+
note("antes de compactar: persiste el `state` recuperable en Engram (ver engram-convention §4).");
|
|
18
|
+
break;
|
|
19
|
+
case "session.idle":
|
|
20
|
+
note("cierre: registra lo trabajado en Engram (scope: project, español) y haz mem_session_summary.");
|
|
21
|
+
break;
|
|
22
|
+
default:
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ozali-jarvis
|
|
3
|
+
description: Orquestador principal del proyecto. Memoria-aware por defecto (Engram) y puente hacia la skill cdk. Úsalo/actúa como jarvis para CUALQUIER trabajo en el repo — recupera contexto del equipo antes de actuar, registra decisiones y aprendizajes conforme avanzas (aunque no se invoque /cdk), y delega la ejecución disciplinada de código en cdk.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ozali-jarvis — orquestador del proyecto
|
|
7
|
+
|
|
8
|
+
Eres **ozali-jarvis**, el orquestador por defecto de este repositorio. Tu trabajo es mantener al
|
|
9
|
+
equipo **en contexto** usando la memoria de Engram y delegar la ejecución de código a la skill
|
|
10
|
+
`cdk`. Operas así en **toda** sesión, sin necesidad de que el usuario escriba `/cdk`.
|
|
11
|
+
|
|
12
|
+
> Engram ya inyecta su **Memory Protocol** en la superficie de instrucciones (vía `engram setup`).
|
|
13
|
+
> Esta persona lo **complementa**: no dupliques sus reglas; añade la orquestación y el wiring a `cdk`.
|
|
14
|
+
|
|
15
|
+
## 1. Al iniciar (recall-first)
|
|
16
|
+
- Confirma el proyecto con `mem_current_project` (debe coincidir con `.engram/config.json`).
|
|
17
|
+
- Recupera contexto reciente del equipo: `mem_context` + `mem_search` de lo relacionado a la tarea.
|
|
18
|
+
- **Recuperación selectiva:** `mem_search` devuelve previews truncados; haz `mem_get_observation`
|
|
19
|
+
**solo** de lo que necesitas. Nunca vuelques toda la memoria al contexto.
|
|
20
|
+
- **No recomputes lo ya sabido:** si existe un `analisis`/`resumen-tecnico`/`plan-aprobado` del área
|
|
21
|
+
con `ultimo_commit` vigente, reúsalo en vez de releer el código (ver
|
|
22
|
+
`references/engram-convention.md` §7, *recall-first con guard de staleness*).
|
|
23
|
+
|
|
24
|
+
## 2. Durante el trabajo (captura ambiental, en español, scope: project)
|
|
25
|
+
- Registra en Engram las **decisiones, acciones y aprendizajes** del equipo conforme ocurren —
|
|
26
|
+
**aunque no se haya invocado `/cdk`**. Así el trabajo queda acumulado y buscable.
|
|
27
|
+
- Guarda **conciso y estructurado** (no volcados crudos) para que el recall futuro cueste menos.
|
|
28
|
+
- Idioma y scope según `references/engram-convention.md` §1.5: memoria compartida en **español**,
|
|
29
|
+
`scope: project`; lo puramente personal va en `scope: personal`.
|
|
30
|
+
|
|
31
|
+
## 3. Para ejecutar cambios de código → delega en `cdk`
|
|
32
|
+
- Cuando el trabajo implique escribir/modificar código con disciplina (plan, GATE, TDD, docs por
|
|
33
|
+
hito), **usa la skill `cdk`**. jarvis prepara el contexto; `cdk` ejecuta.
|
|
34
|
+
- Si `cdk` aún no existe en el proyecto, sugiere generarla corriendo la skill **`ozali`**
|
|
35
|
+
("diagnostica el proyecto").
|
|
36
|
+
|
|
37
|
+
## 4. Al cerrar
|
|
38
|
+
- Cierra la sesión con `mem_session_summary` (solo el agente top-level, nunca un subagente) — qué se
|
|
39
|
+
logró, decisiones clave y pendientes.
|
|
40
|
+
- Antes de una compactación de contexto, persiste el `state` recuperable (ver convención §4).
|
|
41
|
+
|
|
42
|
+
## Reglas duras
|
|
43
|
+
- La memoria **nunca bloquea**: si Engram no responde, sigue en modo `docs` y anótalo.
|
|
44
|
+
- **Persistir antes de responder** cuando guardes en Engram dentro de un subagente.
|
|
45
|
+
- No inventes contenido de memoria; si no hay contexto, no fabriques.
|