@dforce2055/dai 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/CHANGELOG.md CHANGED
@@ -3,6 +3,21 @@
3
3
  Formato basado en [Keep a Changelog](https://keepachangelog.com/). Versionado semver
4
4
  (ver `VERSION`).
5
5
 
6
+ ## [0.6.0] — 2026-07-12
7
+
8
+ Self-update del CLI y blindaje de autoría del repo.
9
+
10
+ ### Agregado
11
+ - `dai upgrade` (alias `update`): actualiza el CLI global a la última publicada
12
+ (`npm i -g …@latest`), con `--check` y `--dry-run`. No toca el repo: reporta el
13
+ drift del scaffold pero deja el `dai sync` explícito del mantenedor (ADR-0012).
14
+
15
+ ### Interno
16
+ - Guard de autoría: rechaza commits autorados/co-autorados por agentes de IA
17
+ (check de CI `authorship` + hook local + `governance/human-authorship.md`).
18
+ - Eliminado `.mailmap` (ya no cumplía función).
19
+ - **111 tests** (+1: `planUpgrade`).
20
+
6
21
  ## [0.5.0] — 2026-07-10
7
22
 
8
23
  **`archive` en el flujo** ([ADR-0011](docs/adr/0011-archive-gate-de-aprobacion.md)): cerrar el CÓMO
@@ -166,6 +181,7 @@ ClickUp y Jira Cloud.
166
181
  - Tests de las rutas de red (jira/clickup/forge) con `fetch` mockeado. Sin links rotos;
167
182
  `files` de npm sin tests ni secretos.
168
183
 
184
+ [0.6.0]: https://github.com/dforce2055/dai/releases/tag/v0.6.0
169
185
  [0.5.0]: https://github.com/dforce2055/dai/releases/tag/v0.5.0
170
186
  [0.4.0]: https://github.com/dforce2055/dai/releases/tag/v0.4.0
171
187
  [0.3.1]: https://github.com/dforce2055/dai/releases/tag/v0.3.1
package/CONTRIBUTING.md CHANGED
@@ -16,6 +16,13 @@ git clone <tu-fork> dai && cd dai
16
16
  npm test # 48 tests, sin `npm install` (no hay deps)
17
17
  ```
18
18
 
19
+ Opcional, para que los hooks del repo te avisen antes de commitear (cero
20
+ dependencias, sin husky):
21
+
22
+ ```bash
23
+ git config core.hooksPath .githooks # valida convención de commits + autoría humana
24
+ ```
25
+
19
26
  ## Reglas de oro
20
27
 
21
28
  1. **Cero dependencias de runtime.** El CLI corre en redes cerradas sin `npm install`
@@ -29,6 +36,11 @@ npm test # 48 tests, sin `npm install` (no hay deps)
29
36
  necesita un ADR (ver abajo).
30
37
  4. **Secretos nunca en el repo.** Tokens solo en `.env` (gitignored) o el secret
31
38
  store del CI. git usa **SSH**, las APIs usan **tokens scopeados** (ADR-0007).
39
+ 5. **Autoría humana.** El código de `dai` lo **autora y revisa una persona**. No se
40
+ aceptan commits cuyo *author*, *committer* o `Co-authored-by` sea un agente o bot
41
+ (Cursor, Copilot, Claude, …). Un agente puede ayudarte, pero el cambio lo firmas
42
+ tú: con tu identidad y sin el trailer del agente. Un check de CI lo bloquea en cada
43
+ PR (ver [`governance/human-authorship.md`](governance/human-authorship.md)).
32
44
 
33
45
  ## Flujo (la propia metodología)
34
46
 
package/README.md CHANGED
@@ -127,7 +127,7 @@ flowchart TD
127
127
 
128
128
  | # | Fase | Cómo | Quién |
129
129
  |---|---|---|---|
130
- | 1 | **Instalar el CLI** | `npm i -g @dforce2055/dai` (o `npm link` en dev) · `dai --version` | dev |
130
+ | 1 | **Instalar el CLI** | `npm i -g @dforce2055/dai` (o `npm link` en dev) · `dai --version` · actualizar después: `dai upgrade` | dev |
131
131
  | 2 | **Bootstrap del repo** | `dai init` → `.dai` + Claude/Copilot/Cursor + config + PR template | dev/lead |
132
132
  | 3a | **Definir el QUÉ** | `/grill-user-story` (una US) · `/grill-epic` (algo grande) · `/doc-to-backlog` (un doc) — te interrogan hasta una US testeable | PO / analista |
133
133
  | 3b | **Publicar la US** | la skill la sube al tracker vía **MCP**, o con **`dai publish <us.md>`** (crea el issue vía token, sin MCP) → devuelve el key | PO / IA |
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.6.0
package/cli/dai.mjs CHANGED
@@ -28,7 +28,7 @@ import { parsePrRef, getPR, postComment } from "./lib/forge-api.mjs";
28
28
  import { composePrBody, prTitle, forgeTool } from "./lib/pr.mjs";
29
29
  import { dirsEqual } from "./lib/fsutil.mjs";
30
30
  import { parseFlags, parseAssistants, isAssistantToken } from "./lib/args.mjs";
31
- import { versionDrift } from "./lib/semver.mjs";
31
+ import { versionDrift, planUpgrade } from "./lib/semver.mjs";
32
32
  import { skillToPrompt, skillToCursor, constitution, constitutionCursorRule, envFor, mergeEnv, upsertBlock, reconcileGitignore } from "./lib/bootstrap.mjs";
33
33
 
34
34
  const HERE = dirname(fileURLToPath(import.meta.url));
@@ -822,6 +822,43 @@ function reportDrift(repo = process.cwd()) {
822
822
  return status;
823
823
  }
824
824
 
825
+ // ── upgrade: self-update del CLI global (ADR-0012) ────────────────────────────
826
+ // Actualiza el paquete npm global de dai a la última publicada. NO toca el repo:
827
+ // si hay drift del scaffold, solo lo reporta (el `dai sync` queda explícito, del
828
+ // mantenedor). El nombre sale de package.json → robusto si cambia el scope.
829
+ function cmdUpgrade(opts) {
830
+ const name = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).name;
831
+ const currentV = readFileSync(join(ROOT, "VERSION"), "utf8").trim();
832
+ const manual = C.cy(`npm i -g ${name}@latest`);
833
+ info(`dai upgrade — CLI v${currentV} (${name})`);
834
+
835
+ let latestV;
836
+ try {
837
+ latestV = execFileSync(npmBin("npm"), ["view", name, "version"], { encoding: "utf8" }).trim();
838
+ } catch {
839
+ fail(`no pude consultar el registry (¿sin red?). Actualizá a mano: ${manual}`, 1);
840
+ }
841
+
842
+ const plan = planUpgrade(currentV, latestV);
843
+ if (plan.action === "unknown") { warn(`no pude comparar versiones (actual '${currentV}', última '${latestV}')`); return; }
844
+ if (plan.action === "up-to-date") { ok(`ya estás en la última (v${currentV})`); reportDrift(); return; }
845
+ if (plan.action === "ahead") { info(`tu CLI (v${currentV}) es más nuevo que el registry (v${latestV}) — nada que actualizar`); reportDrift(); return; }
846
+
847
+ // plan.action === "upgrade"
848
+ if (opts.check) { process.stdout.write(`${C.b(C.y("⬆️ hay update"))} — v${plan.from} → v${plan.to}. Corré ${C.cy("dai upgrade")}\n`); return; }
849
+ if (opts.dryRun) { info(`[dry-run] correría: npm i -g ${name}@latest (v${plan.from} → v${plan.to})`); return; }
850
+
851
+ info(`actualizando v${plan.from} → v${plan.to} …`);
852
+ try {
853
+ execFileSync(npmBin("npm"), ["install", "-g", `${name}@latest`], { stdio: "inherit" });
854
+ } catch {
855
+ fail(`el install falló. Probá a mano: ${manual}`, 1);
856
+ }
857
+ ok(`CLI actualizado a v${plan.to}`);
858
+ const st = reportDrift();
859
+ if (st === null) process.stdout.write(" (No estás en un repo dai — entrá al repo y, si hace falta, corré `dai sync`.)\n");
860
+ }
861
+
825
862
  // ── doctor: diagnóstico ───────────────────────────────────────────────────────
826
863
  function cmdDoctor() {
827
864
  loadEnv();
@@ -901,6 +938,8 @@ switch (cmd) {
901
938
  case "install": cmdInstall(opts).catch((e) => fail(String(e.message))); break;
902
939
  case "init": cmdInit(pos[0], opts).catch((e) => fail(String(e.message))); break;
903
940
  case "sync": cmdSync(pos[0], opts); break;
941
+ case "upgrade":
942
+ case "update": cmdUpgrade(opts); break;
904
943
  case "docs": cmdDocs(pos[0]); break;
905
944
  case "doctor": cmdDoctor(); break;
906
945
  case "version": cmdVersion(); break;
@@ -926,6 +965,7 @@ switch (cmd) {
926
965
  " ej: --for claude,cursor · --for copilot · --for all\n" +
927
966
  " --pm md|jira|clickup · --openspec (con flags salteas las preguntas)\n" +
928
967
  " sync [<repo>] [--dry-run] [--for <asistentes>] refresca skills/constitución/templates a la versión del CLI (aditivo; no toca .env ni OpenSpec)\n" +
968
+ " upgrade [--check] [--dry-run] (alias: update) actualiza el CLI global a la última (npm i -g …@latest) y avisa si el repo quedó atrasado (ADR-0012)\n" +
929
969
  " docs <destino> documentación conceptual → <destino>\n" +
930
970
  " doctor diagnóstico del entorno\n\n" +
931
971
  " (config: .env — ver .env.example)\n"
@@ -28,3 +28,18 @@ export function versionDrift(repoV, cliV) {
28
28
  if (cmp < 0) return "cli-behind";
29
29
  return c.major > r.major ? "major-behind" : "minor-behind";
30
30
  }
31
+
32
+ // Plan de `dai upgrade` (ADR-0012): compara el CLI instalado (currentV) con la
33
+ // última publicada en el registry (latestV). Núcleo puro; el I/O (npm view /
34
+ // npm i -g) vive en el comando.
35
+ // up-to-date → iguales → { action, version }
36
+ // ahead → el CLI local es más nuevo → { action, current, latest }
37
+ // upgrade → hay una versión más nueva → { action, from, to }
38
+ // unknown → alguna versión no parsea → { action }
39
+ export function planUpgrade(currentV, latestV) {
40
+ const cmp = compareVersions(currentV, latestV);
41
+ if (cmp === null) return { action: "unknown" };
42
+ if (cmp === 0) return { action: "up-to-date", version: currentV };
43
+ if (cmp > 0) return { action: "ahead", current: currentV, latest: latestV };
44
+ return { action: "upgrade", from: currentV, to: latestV };
45
+ }
@@ -0,0 +1,59 @@
1
+ # ADR-0012 — `dai upgrade` (self-update del CLI)
2
+
3
+ - **Estado:** aceptado
4
+ - **Fecha:** 2026-07-12
5
+ - **Decide:** lead / arquitecto de la metodología
6
+
7
+ ## Contexto
8
+
9
+ dai tiene **dos cosas versionadas e independientes**: el **CLI** (el paquete npm
10
+ global, una copia por dev) y el **scaffold del repo** (`.dai/`, skills, `.dai/VERSION`,
11
+ compartido y commiteado). El drift entre ambos ya se **detecta** (`versionDrift`,
12
+ ADR-0010) y `dai doctor`/`dai version` imprimen el estado.
13
+
14
+ Pero actualizar el **CLI** era **manual**: cuando el detector veía el CLI atrasado,
15
+ imprimía el texto `npm i -g @dforce2055/dai` para que el dev lo copiara y corriera a
16
+ mano. No había comando. Fricción repetida, y un detector que empujaba a algo que no
17
+ existía como acción de dai.
18
+
19
+ Escenario típico: un dev vuelve el lunes con el CLI en v0.2.0 y ya salió v0.5.0.
20
+ Tiene que actualizar su CLI (y después traer el repo con `git pull`, que el
21
+ mantenedor ya sincronizó).
22
+
23
+ ## Decisión
24
+
25
+ Agregamos **`dai upgrade`** (alias `update`): **self-update del CLI global** a la
26
+ última versión publicada en el registry.
27
+
28
+ - **Scope: solo el CLI.** Corre `npm i -g <name>@latest` (el `<name>` sale de
29
+ `package.json`, robusto si cambia el scope). `--check` solo informa; `--dry-run`
30
+ muestra el comando sin correrlo.
31
+ - **NO toca el repo.** Tras actualizar, **reporta** el drift del scaffold (`reportDrift`)
32
+ pero **no corre `dai sync`**. El `sync` queda como acto **explícito del mantenedor**:
33
+ un dev con CLI viejo que sincronizara **degradaría** el `.dai/` del repo, y sincronizar
34
+ desde cada máquina genera commits en conflicto.
35
+ - **El núcleo es puro y testeado** (`planUpgrade` en `lib/semver.mjs`): decide
36
+ `up-to-date | ahead | upgrade`. El I/O (`npm view` / `npm i -g`) es la cáscara del
37
+ comando, no se unit-testea (mismo criterio que el `fetch`).
38
+
39
+ ## Consecuencias
40
+
41
+ - **Más fácil:** un comando cierra el loop de "CLI atrasado" en vez de copiar un hint.
42
+ La separación de roles queda nítida: **`upgrade` = tu CLI** (self-update),
43
+ **`sync` = el repo** (mantenedor, commiteado).
44
+ - **Se acepta pagar:** depende del **registry** (es online; en redes cerradas —ADR-0006—
45
+ falla con mensaje claro y fallback al `npm i -g` manual). Asume **npm** como package
46
+ manager del global (no pnpm/yarn/volta/asdf); si el install falla, deriva al comando
47
+ manual. No auto-sincroniza el repo (a propósito).
48
+
49
+ ## Alternativas consideradas
50
+
51
+ - **Auto-correr `dai sync` tras el upgrade** — descartado: un dev degradaría el repo con
52
+ un CLI viejo y sincronizar desde cada máquina genera conflictos. El `sync` es del
53
+ mantenedor.
54
+ - **Pinnear dai como devDependency por-repo** (sin global → sin drift, alineado en
55
+ `npm install`) — descartado como default: dai se diseñó **global y zero-dep para redes
56
+ cerradas** (ADR-0006), y no todos los repos son npm. Queda como opción del consumidor,
57
+ no del método.
58
+ - **No hacer nada** (dejar el hint manual) — descartado: fricción repetida sobre una
59
+ acción que el propio detector ya recomendaba.
@@ -35,6 +35,10 @@
35
35
 
36
36
  Se dejan pasar sin validar: `Merge …`, `Revert …`, `fixup!`/`squash!` y commits de bots (`🤖…`).
37
37
 
38
+ > Esto es solo la validación de **formato**. La **autoría** es otra regla: en el repo
39
+ > de dai no se aceptan commits autorados ni co-autorados por un agente
40
+ > (ver [`human-authorship.md`](human-authorship.md)).
41
+
38
42
  ## Relación con la trazabilidad
39
43
 
40
44
  El link formal QUÉ↔CÓMO vive en el `implements.yaml` (no en el mensaje del commit). Pero si
@@ -0,0 +1,61 @@
1
+ # Autoría humana — quién firma el código de dai
2
+
3
+ > El código de `dai` lo **autora y revisa una persona**. Un agente puede ayudarte a
4
+ > escribirlo, pero el commit lo firmas tú: con tu identidad, y sin dejar al agente
5
+ > como co-autor. No es una postura sobre cómo trabajas — es una regla de contribución
6
+ > de **este repo**, y un check la blinda, sin depender de que nadie "se acuerde"
7
+ > (mismo espíritu que [`ci-rules.md`](ci-rules.md) y [`commit-convention.md`](commit-convention.md)).
8
+
9
+ ## La regla
10
+
11
+ Ningún commit que entre a `dai` puede tener un **agente o bot** como:
12
+
13
+ - **author** — quien escribió el cambio,
14
+ - **committer** — quien lo registró, ni
15
+ - **`Co-authored-by:`** — un co-autor en el mensaje.
16
+
17
+ Concretamente se rechazan identidades de asistentes de código (Cursor, GitHub
18
+ Copilot, Claude, Devin, Codeium, Windsurf, … y cualquier `…[bot]`). La lista es
19
+ editable en [`scripts/no-agent-authors.sh`](../scripts/no-agent-authors.sh).
20
+
21
+ ## Por qué
22
+
23
+ - **La persona firma ([Art. 5](../docs/MANIFIESTO.md#art-5)).** El código que entra a
24
+ `dai` es una decisión de una persona que lo entiende y se hace responsable. Un
25
+ commit firmado por un agente diluye ese "alguien responde por esto".
26
+ - **No vibe coding ([Art. 7](../docs/MANIFIESTO.md#art-7)).** El aporte no es "lo que
27
+ salió del chat": es un cambio que revisaste, entendiste y hiciste tuyo.
28
+ - **Higiene del historial.** El grafo de *Contributors* del repo se arma con
29
+ author/committer/co-author. Un trailer de agente mete al bot como contribuidor —
30
+ y sacarlo después obliga a reescribir historia.
31
+
32
+ ## Distinción importante (no es scope de la metodología)
33
+
34
+ Esto **no** dice cómo debes usar agentes en *tus* proyectos. La metodología es
35
+ agnóstica de la herramienta de abajo ([Art. 2](../docs/MANIFIESTO.md#art-2)): usa los
36
+ agentes como quieras. Esta regla gobierna **la contribución al repo de dai**, igual
37
+ que el naming de ramas o la convención de commits. Por eso vive en `governance/` y
38
+ **no** se publica como parte de las plantillas de la metodología.
39
+
40
+ ## Cómo cumplirla
41
+
42
+ Si un agente te ayudó con un cambio: **revísalo, apropiate de él y commitealo con tu
43
+ identidad**, sin el trailer `Co-authored-by`. Configura tu identidad una vez:
44
+
45
+ ```bash
46
+ git config user.name "Tu Nombre"
47
+ git config user.email "tu@email"
48
+ ```
49
+
50
+ ## Cómo se blinda
51
+
52
+ Dos capas, un solo script ([`scripts/no-agent-authors.sh`](../scripts/no-agent-authors.sh),
53
+ cero dependencias):
54
+
55
+ | Capa | Qué | Cuándo |
56
+ |---|---|---|
57
+ | **Hook local** | [`.githooks/commit-msg`](../.githooks/commit-msg) valida el commit en curso (`--pending`). | Al commitear, si activaste `git config core.hooksPath .githooks`. Es opt-in y salteable — feedback temprano, no barrera. |
58
+ | **CI (el gate)** | El job `authorship` de [`ci.yml`](../.github/workflows/ci.yml) valida todos los commits del PR (`--range`). | En cada PR. Como *required check* **bloquea el merge**. No es salteable. |
59
+
60
+ El hook avisa temprano; el CI es la barrera real. El commit siempre es de una persona
61
+ antes de que un mantenedor lo firme ([`CONTRIBUTING.md`](../CONTRIBUTING.md)).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dforce2055/dai",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Metodología de desarrollo asistido por IA — CLI de acciones deterministas (trazabilidad QUÉ↔CÓMO).",
5
5
  "repository": { "type": "git", "url": "git+https://github.com/dforce2055/dai.git" },
6
6
  "homepage": "https://dforce2055.github.io/dai/",