ozali 0.5.0 → 0.6.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 CHANGED
@@ -59,9 +59,9 @@ git clone <repo> && node ozali/cli/bin/ozali.mjs init
59
59
  ### Uso del CLI
60
60
 
61
61
  ```bash
62
- ozali init # detecta agente, instala la skill, aísla el histórico, instala+configura Engram
63
- ozali doctor # health-check read-only (fuente de verdad, Engram, Cloud, Strict TDD, runner…)
64
- ozali update # actualiza la instalación (skill ozali + ozali-jarvis + permisos) al paquete
62
+ ozali init # detecta agente, instala skills ozali + ozali-commit, aísla histórico, configura Engram
63
+ ozali doctor # health-check read-only (fuente de verdad, Engram, Cloud, versión de cdk, Strict TDD…)
64
+ ozali update # actualiza skills ozali + ozali-commit + ozali-jarvis + permisos; avisa si cdk quedó atrás
65
65
  ozali sync # lleva el histórico (docs + Engram) al repo de conocimiento de equipo
66
66
  ozali audit # navega/audita la memoria de Engram del proyecto (o general)
67
67
  ```
@@ -78,10 +78,17 @@ pnpm add -g ozali@<versión> # actualiza el CLI global (pinea la versión por
78
78
  ozali update # en cada repo: refresca skill ozali + ozali-jarvis + permisos
79
79
  ```
80
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.
81
+ `ozali update` también **crea ozali-jarvis** y **instala la skill `ozali-commit`** en repos
82
+ inicializados con versiones anteriores. La skill `cdk` la **regenera/migra tu agente** (no el CLI):
83
+ `ozali update` **detecta la versión de contrato** de `cdk` y, si quedó atrás (o aún referencia
84
+ `copsis-commit` de versiones viejas), te avisa y te da los pasos manuales. Tras `ozali update`, abre
85
+ el agente y vuelve a correr la skill `ozali`: su **pre-flight** detecta el `cdk` existente, lo **migra
86
+ automáticamente** al contrato vigente (eliminando `copsis-commit` y cableando `ozali-commit`) y
87
+ estampa la versión — tus docs por hito y el plan congelado se conservan.
88
+
89
+ > **Commit del hito (`ozali-commit`):** `init`/`update` instalan la skill `ozali-commit`
90
+ > (`.claude/skills/ozali-commit/`), sucesora de `copsis-commit`. `cdk` la invoca al cerrar cada hito
91
+ > para generar el commit convencional (feat/fix/hotfix/refactor + scope) tras tu aprobación.
85
92
 
86
93
  `init` también escribe un **perfil base de permisos** (`.claude/settings.json` para Claude Code,
87
94
  `opencode.json` para opencode) para reducir confirmaciones: deja libres comandos seguros y bloquea
@@ -125,6 +132,7 @@ Claude Code y opencode (perfiles de permisos para ambos en
125
132
  | Desplegar Engram Cloud (Google Cloud) | [docs/deploy-cloud-gcloud.md](docs/deploy-cloud-gcloud.md) |
126
133
  | Skill bootstrap | [skill/SKILL.md](skill/SKILL.md) |
127
134
  | Calibración de testing + TDD | [skill/references/calibration-blueprint.md](skill/references/calibration-blueprint.md) |
135
+ | Contrato y versión de `cdk` | [skill/references/cdk-contract.md](skill/references/cdk-contract.md) |
128
136
  | Blueprint de agentes | [skill/references/agents-blueprint.md](skill/references/agents-blueprint.md) |
129
137
  | Memoria híbrida (docs + Engram) | [skill/references/engram-convention.md](skill/references/engram-convention.md) |
130
138
 
package/cli/bin/ozali.mjs CHANGED
@@ -12,10 +12,11 @@ ${c.bold("Uso:")}
12
12
  ozali <comando> [opciones]
13
13
 
14
14
  ${c.bold("Comandos:")}
15
- init Detecta el agente (Claude Code/opencode), instala la skill ozali,
16
- aísla el histórico, configura Engram y el repo de conocimiento.
17
- doctor Health-check read-only del proyecto (fuente de verdad, Engram, TDD…).
18
- update Actualiza la instalación (skill ozali + ozali-jarvis + permisos) al paquete.
15
+ init Detecta el agente (Claude Code/opencode), instala las skills ozali y
16
+ ozali-commit, aísla el histórico, configura Engram y el repo de conocimiento.
17
+ doctor Health-check read-only del proyecto (fuente de verdad, Engram, versión de cdk, TDD…).
18
+ update Actualiza la instalación (skills ozali + ozali-commit + ozali-jarvis + permisos)
19
+ al paquete y avisa si la skill cdk quedó desactualizada.
19
20
  sync Sincroniza el histórico (docs + Engram) con el repo de conocimiento.
20
21
  audit Navega/audita la memoria de Engram del proyecto (o general).
21
22
  cloud Gestiona Engram Cloud: status, upgrade, repair, dashboard, config.
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import os from "node:os";
5
5
  import {
6
6
  c, ok, warn, err, info, step,
7
- SKILL_SRC, TEMPLATES_SRC, exists, ensureDir, copyDir, readJSON, writeJSON,
7
+ SKILL_SRC, COMMIT_SKILL_SRC, TEMPLATES_SRC, exists, ensureDir, copyDir, readJSON, writeJSON,
8
8
  ensureGitignore, tryExec, spawnCmd, which, engramAssetName,
9
9
  projectName, pkgVersion, DEFAULT_KNOWLEDGE, HOME, openURL,
10
10
  } from "./util.mjs";
@@ -23,6 +23,11 @@ function skillTarget(cwd, scope) {
23
23
  return path.join(base, "skills", "ozali");
24
24
  }
25
25
 
26
+ function commitSkillTarget(cwd, scope) {
27
+ const base = scope === "global" ? path.join(process.env.HOME || "", ".claude") : path.join(cwd, ".claude");
28
+ return path.join(base, "skills", "ozali-commit");
29
+ }
30
+
26
31
  function readTeamCloud(cwd) {
27
32
  return readJSON(TEAM_CLOUD_PATH(cwd));
28
33
  }
@@ -166,11 +171,14 @@ export async function init(cwd, opts) {
166
171
 
167
172
  // --- acciones ---
168
173
  step("Aplicando");
169
- // 1) copiar skill
174
+ // 1) copiar skill ozali (bootstrap) + ozali-commit (commit convencional)
170
175
  const target = skillTarget(cwd, scope);
171
176
  ensureDir(path.dirname(target));
172
177
  copyDir(SKILL_SRC, target);
173
178
  ok(`Skill instalada en ${c.bold(path.relative(cwd, target) || target)}.`);
179
+ const commitTarget = commitSkillTarget(cwd, scope);
180
+ copyDir(COMMIT_SKILL_SRC, commitTarget);
181
+ ok(`Skill ${c.bold("ozali-commit")} instalada en ${c.bold(path.relative(cwd, commitTarget) || commitTarget)}.`);
174
182
 
175
183
  // 2) perfiles base de permisos (idempotentes, merge mínimo) por agente
176
184
  if (agent === "claude-code" || agent === "both") {
@@ -761,6 +769,19 @@ export function doctor(cwd) {
761
769
  add("Node ≥ 16", env.node.ok, env.node.version);
762
770
  add("Fuente de verdad", env.sot.found, env.sot.found ? `${env.sot.doc} + ${env.sot.dir}/` : "ausente (corre la skill 'ozali')");
763
771
  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)");
772
+ // Skill cdk (la genera el agente): versión de contrato vs. la vigente del paquete.
773
+ const cdkInfo = detectCdk(cwd);
774
+ const cdkN = cdkCanonicalVersion();
775
+ if (!cdkInfo.installed) {
776
+ add("Skill cdk", true, "no generada aún (corre la skill 'ozali' en tu agente)");
777
+ } else if (cdkInfo.version != null && cdkInfo.version >= cdkN && !cdkInfo.hasCopsis) {
778
+ add("Skill cdk", true, `contrato v${cdkInfo.version} (al día)`);
779
+ } else {
780
+ const reason = cdkInfo.hasCopsis ? "contiene copsis-commit → migra con la skill 'ozali'"
781
+ : cdkInfo.version == null ? "sin versión de contrato (legado) → migra con la skill 'ozali'"
782
+ : `contrato v${cdkInfo.version} < v${cdkN} → migra con la skill 'ozali'`;
783
+ add("Skill cdk", false, reason);
784
+ }
764
785
  add("Engram", env.engram.available, env.engram.available ? env.engram.bin : "no instalado → modo docs");
765
786
  if (env.engram.available) {
766
787
  const online = tryExec("engram", ["doctor"], { cwd }) !== null;
@@ -899,10 +920,15 @@ export function update(cwd, opts = {}) {
899
920
  }
900
921
 
901
922
  // 1) Skill ozali (incluye las references: la base desde la que el agente regenera cdk)
923
+ // + ozali-commit (commit convencional) como skill hermana — la crea en repos previos.
902
924
  if (env.skill.installed) {
903
925
  for (const p of env.skill.paths) {
904
926
  copyDir(SKILL_SRC, p);
905
927
  ok(`Skill ozali actualizada: ${path.relative(cwd, p) || p} → v${pkgVersion()}`);
928
+ const commitDir = path.join(path.dirname(p), "ozali-commit");
929
+ const freshCommit = !exists(commitDir);
930
+ copyDir(COMMIT_SKILL_SRC, commitDir);
931
+ ok(`Skill ozali-commit ${freshCommit ? "instalada" : "actualizada"}: ${path.relative(cwd, commitDir) || commitDir}`);
906
932
  }
907
933
  } else {
908
934
  warn("Skill ozali no instalada en esta ruta (corre " + c.bold("ozali init") + " para instalarla).");
@@ -926,13 +952,27 @@ export function update(cwd, opts = {}) {
926
952
  if (agent === "opencode" || agent === "both") ensureJarvisOpencode(cwd);
927
953
  }
928
954
 
929
- // 4) Skill cdk: la genera el AGENTE (Fase 6), el CLI no la regenera.
955
+ // 4) Skill cdk: la genera/migra el AGENTE (Fase 0.5/6); el CLI solo detecta versión y guía.
930
956
  const cdk = detectCdk(cwd);
957
+ const cdkN = cdkCanonicalVersion();
931
958
  if (cdk.installed) {
932
959
  step("Skill cdk (generada por el agente)");
933
- warn("cdk no se actualiza desde el CLI: la regenera tu agente con el contrato nuevo.");
934
- info("Abre tu agente y vuelve a correr la skill " + c.bold("ozali") + " para regenerar cdk (ozali-jarvis, recall-first §7, telemetría).");
935
- info("Tus docs por hito (" + c.bold(".ozali/docs/cdk/") + ") y el plan congelado se conservan.");
960
+ const upToDate = cdk.version != null && cdk.version >= cdkN && !cdk.hasCopsis;
961
+ if (upToDate) {
962
+ ok(`cdk al día (contrato v${cdk.version}).`);
963
+ } else {
964
+ const reason = cdk.version == null ? "sin versión de contrato (cdk legado)"
965
+ : cdk.version < cdkN ? `contrato v${cdk.version} < v${cdkN} (desactualizado)`
966
+ : "contiene referencias a copsis-commit";
967
+ warn(`cdk desactualizada: ${reason}.`);
968
+ if (cdk.hasCopsis) warn("Detectadas referencias a " + c.bold("copsis-commit") + " (heredadas de versiones anteriores).");
969
+ info("El CLI no regenera cdk. Actualízala manualmente desde tu agente:");
970
+ info(" 1. Abre tu agente en este proyecto.");
971
+ info(" 2. Corre la skill " + c.bold("ozali") + " (escribe " + c.bold('"ozali"') + "): el pre-flight migra cdk al contrato " + c.bold("v" + cdkN) + ", elimina copsis-commit y cablea ozali-commit.");
972
+ info("Tus docs por hito (" + c.bold(".ozali/docs/cdk/") + ") y el plan congelado se conservan.");
973
+ }
974
+ } else {
975
+ info("cdk aún no generada en este repo. Corre la skill " + c.bold("ozali") + " en tu agente para crearla.");
936
976
  }
937
977
 
938
978
  // 5) versión del config
@@ -941,13 +981,39 @@ export function update(cwd, opts = {}) {
941
981
  return 0;
942
982
  }
943
983
 
944
- /** ¿Existe la skill cdk (generada por el agente)? */
984
+ /**
985
+ * ¿Existe la skill cdk (generada por el agente)? Devuelve además la versión de contrato
986
+ * estampada en su frontmatter (`cdk_contract_version`) y si aún referencia `copsis-commit`.
987
+ * version === null ⇒ cdk legado (sin marcador de versión).
988
+ */
945
989
  function detectCdk(cwd) {
946
990
  const paths = [
947
991
  path.join(cwd, ".claude", "skills", "cdk", "SKILL.md"),
948
992
  path.join(HOME, ".claude", "skills", "cdk", "SKILL.md"),
949
993
  ].filter(exists);
950
- return { installed: paths.length > 0, paths };
994
+ if (paths.length === 0) return { installed: false, paths: [], version: null, hasCopsis: false };
995
+ let version = null;
996
+ let hasCopsis = false;
997
+ for (const f of paths) {
998
+ const txt = fs.readFileSync(f, "utf8");
999
+ if (version === null) {
1000
+ const m = txt.match(/cdk_contract_version:\s*(\d+)/i);
1001
+ if (m) version = parseInt(m[1], 10);
1002
+ }
1003
+ if (/copsis-commit/i.test(txt)) hasCopsis = true;
1004
+ }
1005
+ return { installed: true, paths, version, hasCopsis };
1006
+ }
1007
+
1008
+ /** Versión de contrato vigente de cdk (fuente única: skill/references/cdk-contract.md del paquete). */
1009
+ function cdkCanonicalVersion() {
1010
+ try {
1011
+ const txt = fs.readFileSync(path.join(SKILL_SRC, "references", "cdk-contract.md"), "utf8");
1012
+ const m = txt.match(/CDK_CONTRACT_VERSION:\s*(\d+)/i);
1013
+ return m ? parseInt(m[1], 10) : 1;
1014
+ } catch {
1015
+ return 1;
1016
+ }
951
1017
  }
952
1018
 
953
1019
  // ============================================================= sync ===========
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 COMMIT_SKILL_SRC = path.join(PKG_ROOT, "skill-commit");
13
14
  export const TEMPLATES_SRC = path.join(PKG_ROOT, "templates");
14
15
 
15
16
  export function pkgVersion() {
@@ -133,6 +133,80 @@ test("update agrega ozali-jarvis en un repo que no lo tenía", () => {
133
133
  }
134
134
  });
135
135
 
136
+ function initRepo(dir) {
137
+ run(["init", "--yes", "--no-engram", "--no-trust", "--no-jarvis", "--agent", "claude-code", "--scope", "project", "--knowledge-repo", path.join(dir, ".k")], dir);
138
+ }
139
+
140
+ function writeCdkStub(dir, body) {
141
+ const f = path.join(dir, ".claude", "skills", "cdk", "SKILL.md");
142
+ fs.mkdirSync(path.dirname(f), { recursive: true });
143
+ fs.writeFileSync(f, body);
144
+ return f;
145
+ }
146
+
147
+ test("init instala la skill ozali-commit", () => {
148
+ const dir = tmpProject();
149
+ try {
150
+ initRepo(dir);
151
+ assert.ok(fs.existsSync(path.join(dir, ".claude", "skills", "ozali-commit", "SKILL.md")), "ozali-commit SKILL.md instalada");
152
+ } finally {
153
+ fs.rmSync(dir, { recursive: true, force: true });
154
+ }
155
+ });
156
+
157
+ test("doctor marca cdk al día cuando la versión de contrato coincide", () => {
158
+ const dir = tmpProject();
159
+ try {
160
+ initRepo(dir);
161
+ writeCdkStub(dir, "---\nname: cdk\ncdk_contract_version: 1\n---\n# cdk\n");
162
+ const { stdout } = run(["doctor"], dir, true);
163
+ assert.match(stdout, /Skill cdk/, "doctor reporta la fila Skill cdk");
164
+ assert.match(stdout, /contrato v1 \(al día\)/, "doctor marca cdk al día");
165
+ } finally {
166
+ fs.rmSync(dir, { recursive: true, force: true });
167
+ }
168
+ });
169
+
170
+ test("doctor marca cdk desactualizada si referencia copsis-commit", () => {
171
+ const dir = tmpProject();
172
+ try {
173
+ initRepo(dir);
174
+ writeCdkStub(dir, "---\nname: cdk\ncdk_contract_version: 1\n---\n# cdk\ninvoca copsis-commit al cierre del hito\n");
175
+ const { stdout } = run(["doctor"], dir, true);
176
+ assert.match(stdout, /copsis-commit/, "doctor avisa de copsis-commit");
177
+ } finally {
178
+ fs.rmSync(dir, { recursive: true, force: true });
179
+ }
180
+ });
181
+
182
+ test("update avisa y da pasos manuales si cdk está desactualizada (legado)", () => {
183
+ const dir = tmpProject();
184
+ try {
185
+ initRepo(dir);
186
+ writeCdkStub(dir, "---\nname: cdk\n---\n# cdk legado que invoca copsis-commit\n");
187
+ const { stdout } = run(["update"], dir);
188
+ assert.match(stdout, /cdk desactualizada/, "update avisa de cdk desactualizada");
189
+ assert.match(stdout, /copsis-commit/, "update menciona la migración de copsis-commit");
190
+ assert.match(stdout, /skill ozali|pre-flight|migra/i, "update da pasos manuales");
191
+ } finally {
192
+ fs.rmSync(dir, { recursive: true, force: true });
193
+ }
194
+ });
195
+
196
+ test("update también instala ozali-commit en repos previos", () => {
197
+ const dir = tmpProject();
198
+ try {
199
+ initRepo(dir);
200
+ // Simula instalación previa sin ozali-commit.
201
+ fs.rmSync(path.join(dir, ".claude", "skills", "ozali-commit"), { recursive: true, force: true });
202
+ assert.ok(!fs.existsSync(path.join(dir, ".claude", "skills", "ozali-commit", "SKILL.md")), "precondición: sin ozali-commit");
203
+ run(["update"], dir);
204
+ assert.ok(fs.existsSync(path.join(dir, ".claude", "skills", "ozali-commit", "SKILL.md")), "update instala ozali-commit");
205
+ } finally {
206
+ fs.rmSync(dir, { recursive: true, force: true });
207
+ }
208
+ });
209
+
136
210
  test("init --dry-run no escribe nada", () => {
137
211
  const dir = tmpProject();
138
212
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ozali",
3
- "version": "0.5.0",
3
+ "version": "0.6.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": {
@@ -12,6 +12,7 @@
12
12
  "files": [
13
13
  "cli/",
14
14
  "skill/",
15
+ "skill-commit/",
15
16
  "templates/",
16
17
  "docs/",
17
18
  "README.md",
package/skill/SKILL.md CHANGED
@@ -30,6 +30,7 @@ produce documentación, la calibración, el plan y los artefactos de la skill `c
30
30
 
31
31
  ```
32
32
  Fase 0 Identidad y registro → quién corre ozali (git/sesión)
33
+ Fase 0.5 Pre-flight de cdk → ¿existe cdk? ¿versión de contrato vigente? migrar si toca
33
34
  Fase 1 Detección fuente de verdad → ¿existe AI.md/.ai o IA.md/.ia?
34
35
  Fase 2 Generación conocimiento → si falta, evaluar el proyecto y generar AI.md + .ai/
35
36
  Fase 3 Validación + ajustes → ¿coincide con la estructura real? ajustes mínimos
@@ -69,6 +70,45 @@ Guarda estos valores; se reutilizan en Fase 6 y en la documentación.
69
70
 
70
71
  ---
71
72
 
73
+ ## Fase 0.5 — Pre-flight de `cdk` (versión de contrato + migración)
74
+
75
+ > Esta fase resuelve el caso de **un repo que ya tiene `cdk` instalada**: en vez de omitirla,
76
+ > `ozali` decide si está **al día**, **desactualizada** o **ausente**, y actúa en consecuencia.
77
+ > La **versión de contrato vigente** y las reglas de migración están en
78
+ > [`references/cdk-contract.md`](references/cdk-contract.md) — es la **fuente de verdad única**.
79
+
80
+ 1. **Lee la versión de contrato vigente `N`** del marcador `CDK_CONTRACT_VERSION` en
81
+ [`references/cdk-contract.md`](references/cdk-contract.md).
82
+ 2. **Busca el `cdk` instalado**: `.claude/skills/cdk/SKILL.md` (proyecto), luego
83
+ `~/.claude/skills/cdk/SKILL.md` (global). Si existe, lee `cdk_contract_version` de su frontmatter.
84
+ 3. **Decide y registra** la decisión (se usa en las Fases 5 y 6):
85
+
86
+ | Estado del `cdk` instalado | Decisión |
87
+ |---|---|
88
+ | **No existe** | `GENERAR` — flujo normal: continúa el bootstrap hasta el GATE (Fase 5) y genera `cdk` en la Fase 6, estampando `cdk_contract_version: N`. |
89
+ | **Existe sin `cdk_contract_version`** (cdk legado) o con valor **`< N`** | `MIGRAR` — **migración automática** (sin GATE), ver abajo. |
90
+ | **Existe con `cdk_contract_version == N`** | `AL_DÍA` — no regenerar. Infórmalo y continúa el bootstrap solo para refrescar la calibración/documentación si el usuario lo pide. |
91
+
92
+ 4. **Migración automática (`MIGRAR`)** — aplica los deltas del changelog de
93
+ [`references/cdk-contract.md`](references/cdk-contract.md) sobre el `cdk` instalado, **sin** pasar
94
+ por el GATE, porque preserva el `cdk` del usuario y solo actualiza el contrato:
95
+ - **Elimina toda referencia a `copsis-commit`** y reemplázala por **`ozali-commit`** (la skill de
96
+ commit vigente, instalada en `.claude/skills/ozali-commit/`).
97
+ - Aplica el resto de deltas del changelog (recall-first, telemetría, etc. según la versión).
98
+ - **Estampa** `cdk_contract_version: N` en el frontmatter del `cdk`.
99
+ - **Preserva** intactos `.ozali/docs/cdk/` (histórico por hito) y los planes congelados
100
+ (`02-plan-aprobado.md`).
101
+ - **Reporta** al usuario, en texto, qué cambió (referencias migradas, secciones actualizadas,
102
+ versión nueva).
103
+ - **Excepción → GATE:** si un delta exige **regeneración estructural** (reescribir subagentes,
104
+ cambiar el harness, rehacer el wiring de TDD), eso **no** es migración automática: preséntalo en
105
+ el plan del GATE (Fase 5) y regénéralo en la Fase 6.
106
+
107
+ > Sin `cdk` previo, esta fase no hace nada salvo registrar `GENERAR`. La calibración (Fase 3.5) y el
108
+ > resto del flujo continúan igual.
109
+
110
+ ---
111
+
72
112
  ## Fase 1 — Detección de la fuente de verdad
73
113
 
74
114
  Busca en la raíz del repositorio, aceptando **ambas nomenclaturas**:
@@ -217,6 +257,9 @@ Solo tras la aprobación de la Fase 5. Genera:
217
257
  - declarar en el frontmatter una **description con triggers ricos** (verbos y sustantivos
218
258
  que el usuario usaría: "crea", "genera", "corrige", "refactoriza", método, clase,
219
259
  endpoint, componente…) para mejorar la auto-invocación de la skill;
260
+ - **estampar en el frontmatter** `cdk_contract_version: N`, con `N` = la versión de contrato
261
+ vigente de [`references/cdk-contract.md`](references/cdk-contract.md). Esto permite que el
262
+ pre-flight (Fase 0.5) y `ozali doctor`/`ozali update` sepan si el `cdk` está al día;
220
263
  - declarar que ayuda a **generar código** (nuevo componente, fix de bug, análisis de impacto)
221
264
  respetando la fuente de verdad y los estándares del proyecto;
222
265
  - orquestar los 8 subagentes según la fase del trabajo;
@@ -249,9 +292,11 @@ Solo tras la aprobación de la Fase 5. Genera:
249
292
  resumen técnico, `state`) con naming determinista, según
250
293
  [`references/engram-convention.md`](references/engram-convention.md), y al **iniciar** un
251
294
  hito hacer `mem_search` del proyecto/hito para recuperar contexto previo;
252
- - en el **cierre del hito**, invocar la skill **`ozali-commit`** para generar el commit
253
- summary (feature→`feat`, bugfix→`fix`, hotfix→`hotfix`, refactor→`refactor`; scope = módulo
254
- del hito). El GATE del mensaje es independiente del GATE del plan.
295
+ - en el **cierre del hito**, invocar la skill **`ozali-commit`** (instalada por el CLI en
296
+ `.claude/skills/ozali-commit/`; **nunca** `copsis-commit`, nombre heredado de versiones
297
+ anteriores) para generar el commit summary (feature→`feat`, bugfix→`fix`, hotfix→`hotfix`,
298
+ refactor→`refactor`; scope = módulo del hito). El GATE del mensaje es independiente del GATE
299
+ del plan.
255
300
  2. `.claude/agents/<rol>.md` — un subagente real por cada uno de los 8 roles, con su system
256
301
  prompt y herramientas, según [`references/agents-blueprint.md`](references/agents-blueprint.md).
257
302
  3. `.claude/skills/cdk/verify-structure.mjs` — **harness del analista**: script Node sin
@@ -0,0 +1,72 @@
1
+ <!-- CDK_CONTRACT_VERSION: 1 -->
2
+
3
+ # Contrato de la skill `cdk` — versión y migración
4
+
5
+ Esta es la **fuente de verdad única** de la versión del contrato con el que `ozali` genera (y
6
+ mantiene) la skill `cdk`. La lee el **agente** (en la Fase 0.5 y la Fase 6 de
7
+ [`../SKILL.md`](../SKILL.md)) y la parsea el **CLI** (`ozali doctor`/`ozali update`) del paquete.
8
+ Para subir el contrato: incrementa el número del marcador `CDK_CONTRACT_VERSION` de arriba y la
9
+ prosa de abajo, y agrega una entrada al changelog.
10
+
11
+ > **Versión de contrato vigente: `1`**
12
+
13
+ El número vive en **un solo lugar** (el marcador HTML de la primera línea, formato
14
+ `CDK_CONTRACT_VERSION: <entero>`). No lo dupliques en otros archivos.
15
+
16
+ ---
17
+
18
+ ## Cómo se usa la versión
19
+
20
+ - El `cdk` generado **debe estampar** su versión de contrato en el frontmatter de su `SKILL.md`:
21
+
22
+ ```yaml
23
+ ---
24
+ name: cdk
25
+ description: <triggers ricos…>
26
+ cdk_contract_version: 1
27
+ ---
28
+ ```
29
+
30
+ - En el **pre-flight** (Fase 0.5 de `ozali`), el agente compara la versión estampada en el `cdk`
31
+ instalado contra esta versión vigente **N**:
32
+
33
+ | Estado del `cdk` instalado | Acción |
34
+ |---|---|
35
+ | No existe | Generar `cdk` por el flujo normal (GATE Fase 5 → Fase 6), estampando `cdk_contract_version: N`. |
36
+ | Existe **sin** `cdk_contract_version`, o con un valor **< N** | **Desactualizado → migración automática** (sin GATE): aplica los deltas del changelog, estampa `N` y reporta los cambios. Si el delta exige **regeneración estructural** de subagentes, eso sí pasa por el GATE de la Fase 5. |
37
+ | Existe con `cdk_contract_version == N` | Al día → no regenerar; solo informarlo. |
38
+
39
+ > Un `cdk` **sin** el campo `cdk_contract_version` es un `cdk` **legado** (generado por una versión
40
+ > anterior de `ozali`). Trátalo como versión `0`: siempre desactualizado.
41
+
42
+ ---
43
+
44
+ ## Regla de migración (todas las versiones)
45
+
46
+ Al migrar un `cdk` legado o desactualizado, **siempre**:
47
+
48
+ 1. **Elimina toda referencia a `copsis-commit`** (nombre heredado de versiones anteriores) y
49
+ **reemplázala por `ozali-commit`** — la skill de commit vigente que `ozali init`/`ozali update`
50
+ instalan en `.claude/skills/ozali-commit/`.
51
+ 2. **Preserva** el histórico por hito (`.ozali/docs/cdk/`) y los planes congelados
52
+ (`02-plan-aprobado.md`): la migración del contrato **nunca** los borra ni reescribe.
53
+ 3. **Estampa** `cdk_contract_version: N` en el frontmatter del `cdk` migrado.
54
+ 4. **Reporta** al usuario, en texto, qué cambió (referencias migradas, secciones actualizadas,
55
+ versión nueva).
56
+
57
+ ---
58
+
59
+ ## Changelog del contrato
60
+
61
+ ### v1 — contrato base
62
+ Un `cdk` conforme a la v1 debe:
63
+
64
+ - Estampar `cdk_contract_version: 1` en su frontmatter.
65
+ - En el **cierre de hito**, invocar la skill **`ozali-commit`** (nunca `copsis-commit`) para
66
+ generar el commit summary convencional (feature→`feat`, bugfix→`fix`, hotfix→`hotfix`,
67
+ refactor→`refactor`; scope = módulo del hito).
68
+ - Operar **recall-first** (reusar memoria de Engram antes de releer/re-analizar) según
69
+ [`engram-convention.md`](engram-convention.md) §7.
70
+ - Espejar a Engram los artefactos clave del hito y escribir la telemetría de tokens
71
+ (`.ozali/metrics/token-metrics.json`, que lee `ozali doctor`).
72
+ - Respetar la calibración de Strict TDD (Fase 3.5) y el 🛑 GATE del plan.
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: ozali-commit
3
+ description: Genera un "commit summary" convencional (feat/fix/hotfix/refactor) con prefijo, scope y descripción a partir de los cambios EN STAGE y commitea tras tu aprobación. Úsala cuando el usuario quiera "commitea", "haz el commit", "genera el mensaje de commit", "cierra el hito con commit", o cuando otra skill (p. ej. cdk) cierre un hito. Sucesora de copsis-commit.
4
+ ---
5
+
6
+ # Skill: ozali-commit
7
+
8
+ `ozali-commit` produce el **mensaje de commit** de un cambio siguiendo
9
+ [Conventional Commits](https://www.conventionalcommits.org/) y lo **commitea tras tu aprobación**.
10
+ Es la skill de commit vigente del ecosistema `ozali` (sucede a la antigua `copsis-commit`).
11
+
12
+ `cdk` la invoca en el **cierre de cada hito**, pero también puedes usarla suelta en cualquier repo.
13
+
14
+ > **Regla de oro:** nunca commitea sin tu aprobación explícita (🛑 **GATE de mensaje**). El GATE del
15
+ > mensaje es **independiente** del GATE del plan de `cdk`.
16
+
17
+ > **Cero dependencias:** usa `git` directo (ya habilitado en el perfil base de permisos de ozali:
18
+ > `Bash(git add *)`, `Bash(git commit *)`, `Bash(git status)`, `Bash(git diff *)`,
19
+ > `Bash(git log *)`). No requiere herramientas externas.
20
+
21
+ ---
22
+
23
+ ## Flujo
24
+
25
+ ### 1. Inspecciona el stage
26
+ - `git status --short` — qué hay staged vs sin stage.
27
+ - `git diff --cached` — el contenido **EN STAGE** (la fuente de verdad del mensaje).
28
+ - Si **no hay nada en stage**: dilo y pregunta si quieres que haga `git add` de archivos concretos
29
+ (o `git add -A`). **No** hagas `add -A` por tu cuenta sin confirmación.
30
+ - Lee el commit anterior con `git log -1 --pretty=%s` para mantener el estilo del repo.
31
+
32
+ ### 2. Determina el tipo y el scope
33
+ A partir del diff EN STAGE, clasifica el cambio:
34
+
35
+ | Tipo | Cuándo |
36
+ |---|---|
37
+ | `feat` | nueva funcionalidad / capacidad |
38
+ | `fix` | corrección de bug |
39
+ | `hotfix` | corrección urgente en caliente |
40
+ | `refactor` | reestructura sin cambiar comportamiento |
41
+ | `docs` | solo documentación |
42
+ | `test` | solo pruebas |
43
+ | `chore` | mantenimiento (deps, config, build) |
44
+
45
+ - **Scope** = módulo/área tocada del repo (derívalo de las rutas en stage; ej. `cobranza`, `cli`,
46
+ `auth`). Si abarca varias áreas, usa la más representativa o un scope general.
47
+ - Si la skill que invoca aporta tipo/scope (p. ej. `cdk` mapea feature→`feat`, bugfix→`fix`,
48
+ hotfix→`hotfix`, refactor→`refactor`), **respétalos**.
49
+
50
+ ### 3. Redacta el mensaje
51
+ Formato: `tipo(scope): descripción en imperativo, ≤ 72 chars`. Cuerpo opcional (qué/por qué, no el
52
+ cómo). No inventes cambios que no estén en el diff. Idioma: el del repo (mira `git log`).
53
+
54
+ ```
55
+ feat(cobranza): agrega validación de RFC en alta de cotización
56
+
57
+ - valida formato y dígito verificador antes de persistir
58
+ - cubre el caso de RFC genérico (XAXX010101000)
59
+ ```
60
+
61
+ ### 4. 🛑 GATE de mensaje
62
+ Presenta el mensaje completo **como texto visible** y espera aprobación. Si el usuario pide ajustes,
63
+ itera. **No commitees** hasta el "ok".
64
+
65
+ ### 5. Commitea
66
+ Tras la aprobación:
67
+ ```bash
68
+ git commit -m "tipo(scope): descripción" [-m "cuerpo…"]
69
+ ```
70
+ - Commitea **solo lo que está en stage** (no añadas archivos no aprobados).
71
+ - **No** hagas `git push` (es destructivo/saliente y está denegado por defecto): si el usuario lo
72
+ quiere, recuérdaselo para que lo haga él.
73
+ - Reporta el hash corto resultante (`git rev-parse --short HEAD`).
74
+
75
+ ---
76
+
77
+ ## Notas
78
+ - Si el repo usa una convención propia (gitmoji, prefijos de ticket), detéctala desde `git log` y
79
+ síguela.
80
+ - Si el cambio es demasiado grande/heterogéneo para un solo mensaje coherente, **sugiere** dividir
81
+ en varios commits (pero no lo hagas sin aprobación).