@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 +16 -0
- package/CONTRIBUTING.md +12 -0
- package/README.md +1 -1
- package/VERSION +1 -1
- package/cli/dai.mjs +41 -1
- package/cli/lib/semver.mjs +15 -0
- package/docs/adr/0012-upgrade-self-update-del-cli.md +59 -0
- package/governance/commit-convention.md +4 -0
- package/governance/human-authorship.md +61 -0
- package/package.json +1 -1
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.
|
|
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"
|
package/cli/lib/semver.mjs
CHANGED
|
@@ -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.
|
|
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/",
|