@dforce2055/dai 0.14.0 → 0.15.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/.env.dai.example +16 -0
- package/CHANGELOG.md +127 -0
- package/CONTRIBUTING.md +22 -0
- package/README.md +3 -2
- package/VERSION +1 -1
- package/cli/dai.mjs +571 -17
- package/cli/lib/bootstrap.mjs +4 -4
- package/cli/lib/branch-flow.mjs +6 -6
- package/cli/lib/branch-scope.mjs +24 -9
- package/cli/lib/help.mjs +76 -0
- package/cli/lib/notify.mjs +205 -0
- package/cli/lib/pm-adapter.mjs +16 -0
- package/cli/lib/pm-clickup.mjs +17 -0
- package/cli/lib/pm-jira.mjs +92 -8
- package/cli/lib/pr-remote.mjs +14 -0
- package/cli/lib/release-files.mjs +119 -0
- package/cli/lib/release-plan.mjs +221 -0
- package/cli/lib/release-stamp.mjs +87 -0
- package/cli/lib/review-findings.mjs +2 -2
- package/cli/lib/us-format.mjs +2 -2
- package/cli/lib/us.mjs +6 -6
- package/docs/adr/0008-estrategia-de-i18n.md +58 -0
- package/docs/adr/0019-ciclo-de-version-y-aviso-de-release.md +190 -0
- package/docs/adr/README.md +1 -0
- package/docs/guias/index.md +3 -0
- package/docs/guias/releases.md +156 -0
- package/docs/tutoriales/ciclo-de-release.md +266 -0
- package/docs/tutoriales/index.md +6 -0
- package/package.json +1 -1
- package/skills/dai-release/SKILL.md +167 -0
package/.env.dai.example
CHANGED
|
@@ -27,6 +27,22 @@ DAI_PM=md
|
|
|
27
27
|
# rama como producción: no adivina cuál es.
|
|
28
28
|
# DAI_BRANCH_PROD=main
|
|
29
29
|
|
|
30
|
+
# ── Aviso de release a un canal de equipo (opcional) ─────────────────────────
|
|
31
|
+
# Tercer adaptador de dai, con la misma forma que DAI_PM: una variable elige el canal y el
|
|
32
|
+
# canal trae las suyas. Por default no hay aviso: dai no habla hacia afuera sin pedirlo.
|
|
33
|
+
# discord | slack | webex | telegram | webhook | none
|
|
34
|
+
# `webhook` es el genérico: manda los campos del release en JSON a cualquier endpoint
|
|
35
|
+
# (Teams, Mattermost, un sistema interno) e incluye además el texto ya armado.
|
|
36
|
+
# DAI_NOTIFY=webex
|
|
37
|
+
|
|
38
|
+
# El endpoint del canal. ⚠️ ES LA CREDENCIAL: quien lo tiene, puede postear. Nunca lo
|
|
39
|
+
# commitees; dai muestra el host, nunca la URL. Probalo con `dai release notify --test`.
|
|
40
|
+
# DAI_NOTIFY_WEBHOOK=
|
|
41
|
+
|
|
42
|
+
# Solo para telegram: no tiene webhooks de entrada, así que el endpoint es el bot y hace
|
|
43
|
+
# falta decir a qué chat va el mensaje.
|
|
44
|
+
# DAI_NOTIFY_CHAT_ID=
|
|
45
|
+
|
|
30
46
|
# ── Backend md (local, offline) ──────────────────────────────────────────────
|
|
31
47
|
# Carpeta donde viven las US como <ID>.md (p. ej. ABC-482.md).
|
|
32
48
|
DAI_MD_US_DIR=.dai/us
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,131 @@
|
|
|
3
3
|
Formato basado en [Keep a Changelog](https://keepachangelog.com/). Versionado semver
|
|
4
4
|
(ver `VERSION`).
|
|
5
5
|
|
|
6
|
+
## [0.15.1] — 2026-09-10
|
|
7
|
+
|
|
8
|
+
**Una US puede estar impecable y dai igual no leerla. El molde de trazabilidad pide que la
|
|
9
|
+
metadata viva en una tabla, y cuando la US se escribe a mano en Jira esa tabla es una tabla
|
|
10
|
+
de verdad — que dai no sabía leer. El `spec_version` declarado se perdía en el camino y el
|
|
11
|
+
link quedaba estampado en `pendiente`, con un mensaje que mandaba a agregar una fila que ya
|
|
12
|
+
estaba ahí. Esta versión le enseña a dai a leer y escribir tablas.**
|
|
13
|
+
|
|
14
|
+
### Corregido
|
|
15
|
+
- **El `spec_version` que la US declaraba y dai no veía.** `adfToMarkdown` no tenía caso
|
|
16
|
+
para las tablas de Jira Cloud, así que caían al `default` y cada celda terminaba en su
|
|
17
|
+
propia línea: `spec_version` en una, `v1` en la siguiente. Como el regex que lee la
|
|
18
|
+
versión no cruza saltos de línea —a propósito, para no capturar un `v2` de veinte líneas
|
|
19
|
+
más abajo— no encontraba nada, y el link se estampaba en `version: pendiente`. El aviso
|
|
20
|
+
era el peor de todos: uno que **acusa al usuario** de no haber hecho algo que sí hizo.
|
|
21
|
+
Ahora una fila de tabla llega como una fila de markdown. El `ac_hash` **no se mueve** —la
|
|
22
|
+
metadata vive arriba del bloque de criterios—, así que ningún `implements.yaml` ya
|
|
23
|
+
estampado queda atrasado por este cambio: lo único que cambia es que `pendiente` pasa a
|
|
24
|
+
ser la versión que la US declara. Es el hermano del #46: aquel era el campo escrito
|
|
25
|
+
`specversion`, este es el campo escrito en la tabla que el propio molde pide.
|
|
26
|
+
- **La otra mitad del mismo bug: dai tampoco escribía tablas.** `markdownToAdf` mandaba la
|
|
27
|
+
metadata del molde —y el comentario de cobertura de `dai release stamp`, que también es
|
|
28
|
+
una tabla— como párrafos con pipes adentro. Ilegibles, y encima invitaban a rehacerlos
|
|
29
|
+
como tabla de Jira a mano, que es justo lo que del otro lado no se sabía leer. Ahora
|
|
30
|
+
viajan como nodo `table` y el ida y vuelta de `dai edit-us` no destruye la metadata.
|
|
31
|
+
- **`dai release done` avisaba de un problema inexistente.** Miraba `origin/release/X.Y.Z`,
|
|
32
|
+
una ref local que sobrevive desactualizada hasta el próximo prune, en vez de preguntarle
|
|
33
|
+
al remoto. Cuando GitHub ya había borrado la rama al mergear —su comportamiento por
|
|
34
|
+
default— intentaba borrar algo que no estaba y reportaba un ⚠ sobre un estado que era el
|
|
35
|
+
correcto. Ahora pregunta con `git ls-remote --heads`, que es autoritativo, limpia la ref
|
|
36
|
+
fantasma y explica en una línea por qué no hubo nada que borrar. Un aviso falso es caro:
|
|
37
|
+
enseña a ignorar los avisos.
|
|
38
|
+
|
|
39
|
+
### Cambiado
|
|
40
|
+
- **`dai release plan --json` expone `counts.stale`, antes `counts.atrasadas`.** Era una
|
|
41
|
+
clave en español en una salida que lee un script — la única que quedaba — y sigue el
|
|
42
|
+
glosario que ADR-0008 ya había fijado (*atrasado* → *stale*). Si escribiste algo contra
|
|
43
|
+
el `--json` de la 0.15.0 (publicada un día antes), es el único nombre que tenés que
|
|
44
|
+
cambiar.
|
|
45
|
+
|
|
46
|
+
### Interno
|
|
47
|
+
- **Convención de idioma, ahora escrita.** El repo siempre fue código en inglés y
|
|
48
|
+
comentarios en español, pero no estaba en ningún lado: al escribir el ciclo de release se
|
|
49
|
+
rompió sistemáticamente (25% de identificadores en español en lo nuevo, contra ~2%
|
|
50
|
+
preexistente). Se renombraron 209 identificadores al inglés y la regla quedó como sexta
|
|
51
|
+
regla de oro en `CONTRIBUTING.md`, con una excepción deliberada: **`autor` se queda**. No
|
|
52
|
+
es código, es un campo del `implements.yaml` y parte del contrato del método (ADR-0004);
|
|
53
|
+
renombrarlo sería romper un contrato para ganar consistencia interna.
|
|
54
|
+
- **ADR-0008** documenta el relevamiento que falta para poder ejecutar el i18n.
|
|
55
|
+
- **484 tests** (+9 desde la 0.15.0): las tablas ADF en las dos direcciones —incluido el
|
|
56
|
+
ida y vuelta markdown → ADF → markdown, que es el que protege la metadata de `dai
|
|
57
|
+
edit-us`— y la primera cobertura de integración de `dai release done`, que era casi todo
|
|
58
|
+
efecto y no tenía ninguna: el ciclo completo contra un repo con remoto de verdad, el caso
|
|
59
|
+
del forge que se adelantó, y las dos negativas a taguear.
|
|
60
|
+
|
|
61
|
+
## [0.15.0] — 2026-09-09
|
|
62
|
+
|
|
63
|
+
**Un equipo puede tener el link QUÉ↔CÓMO perfecto y seguir sin poder contestar la pregunta
|
|
64
|
+
que hace el negocio: "¿esto ya está en producción?". La trazabilidad llegaba hasta la PR y
|
|
65
|
+
se cortaba justo ahí. Esta versión agrega el último eslabón — la versión desplegada — y con
|
|
66
|
+
él, el ciclo completo para cortarla, cerrarla y contarla.**
|
|
67
|
+
|
|
68
|
+
### Agregado
|
|
69
|
+
- **`dai release plan` — el manifiesto de la versión.** Qué User Stories entran entre el
|
|
70
|
+
último tag y la rama de integración, en qué estado está cada una, y qué entró **sin**
|
|
71
|
+
declarar US. Resuelve las US leyendo los `implements.yaml` **tal como estaban en cada
|
|
72
|
+
commit del rango**, no por el nombre de la rama: el link viaja con el código, así que la
|
|
73
|
+
respuesta sobrevive a que la branch se borre y a que el change se archive — el estado
|
|
74
|
+
normal del repo cuando llegás a cortar, días después del merge. Es el dato del que
|
|
75
|
+
dependen los otros cuatro comandos; `--json` es lo que consume la skill.
|
|
76
|
+
- **`dai release cut <X.Y.Z>` — preparar, sin hablar hacia afuera.** Rama de release, número
|
|
77
|
+
en los archivos que el repo espeja, entrada del CHANGELOG y commit. Ni push, ni tag, ni
|
|
78
|
+
PR: todo lo que pasa ANTES de la firma humana.
|
|
79
|
+
- **`dai release done <X.Y.Z>` — cerrar, después del merge.** Tag anotado, release note en
|
|
80
|
+
el forge, back-merge a integración, borrado de la rama de release y aviso al canal. Existe
|
|
81
|
+
como comando separado porque los dos pasos que más se olvidan cuando la ceremonia se hace
|
|
82
|
+
a mano viven en esta mitad, la que queda después de la firma. Se llama `done` y no
|
|
83
|
+
`finish` por lo mismo que `dai done` cierra el trabajo de una branch: mismo verbo, distinto
|
|
84
|
+
sustantivo.
|
|
85
|
+
- **`dai release stamp <X.Y.Z> --env <ambiente>` — que cada US sepa dónde está.** Deja en
|
|
86
|
+
cada historia del release un comentario con versión, app, ambiente y fecha, y con eso el
|
|
87
|
+
funcional lee el ticket en vez de preguntar. Una US federada en varios repos acumula sola
|
|
88
|
+
su matriz. Es **opcional** y decir que no sale con 0: cuando el comando corre, el tag ya
|
|
89
|
+
existe y la versión está hecha.
|
|
90
|
+
- **`dai release status`** — dónde estás en el ciclo: versión declarada vs último tag, qué
|
|
91
|
+
falta promover, si quedó un back-merge pendiente, qué ramas de release sobrevivieron.
|
|
92
|
+
**`dai release notify --test`** — probar el canal antes de depender de él, porque un
|
|
93
|
+
webhook no se puede validar sin postear y fingir que sí sería justo lo que dai no hace.
|
|
94
|
+
- **Aviso de release a un canal de equipo** (`DAI_NOTIFY`): discord, slack, webex, telegram
|
|
95
|
+
o un `webhook` genérico para Teams, Mattermost o un sistema interno. Apagado por default:
|
|
96
|
+
dai no habla hacia afuera sin que se lo pidan. El endpoint **es la credencial**, así que
|
|
97
|
+
vive en el `.env.dai` y dai muestra el host, nunca la URL — tampoco en los errores.
|
|
98
|
+
- **Skill `/dai-release`** — conduce el ciclo confirmando paso a paso. No recalcula el
|
|
99
|
+
manifiesto: lo pide y lo narra. Si su memoria y el CLI se contradicen, gana el CLI.
|
|
100
|
+
- **[Guía de releases](docs/guias/releases.md)** con el *porqué* y
|
|
101
|
+
**[tutorial del ciclo completo](docs/tutoriales/ciclo-de-release.md)**. Una estrategia de
|
|
102
|
+
branching que el equipo no entiende se abandona en dos sprints, así que la documentación
|
|
103
|
+
entra en la misma versión que los comandos, no después.
|
|
104
|
+
|
|
105
|
+
### Corregido
|
|
106
|
+
- **`dai pr` no podía abrir la PR de una `feature/` en un repo sin User Stories.** Le pasa a
|
|
107
|
+
cualquier repo de tooling o librería interna que use dai para versionar sin gestionar sus
|
|
108
|
+
propias historias — a este, sin ir más lejos, donde el mensaje mandaba a renombrar la
|
|
109
|
+
branch a `chore/`. Lo que distingue un olvido de un repo que no trabaja así es si la
|
|
110
|
+
branch **nombra un ticket**. El gate de CI no se toca.
|
|
111
|
+
- **Un aviso que aparece siempre no avisa nada.** El manifiesto marcaba cada branch sin US
|
|
112
|
+
como "entró trabajo sin link", incluso en repos donde ninguna branch va a declarar una
|
|
113
|
+
jamás. Ahora el hallazgo se reporta solo si el repo trabaja con User Stories — salvo que
|
|
114
|
+
la branch nombre un ticket, que ahí sí es un olvido.
|
|
115
|
+
- **`dai pr` fallaba al actualizar una PR por un motivo que no era suyo.** `gh pr edit`
|
|
116
|
+
resuelve por GraphQL y arrastra campos deprecados del servidor, así que devolvía un error
|
|
117
|
+
sobre *Projects (classic)* cuando lo único que se quería era cambiar el body. Ahora
|
|
118
|
+
reintenta por REST con la misma autenticación; se intenta callado y solo se reporta si el
|
|
119
|
+
plan B también falla.
|
|
120
|
+
|
|
121
|
+
### Interno
|
|
122
|
+
- **475 tests** (+68 desde la 0.14.0): el manifiesto y su resolución por commit, el corte y
|
|
123
|
+
el cierre, el gate de alcance del estampado y su idempotencia por (app, versión,
|
|
124
|
+
ambiente), y el adaptador de canal — incluidos tres tests que fallan si el endpoint se
|
|
125
|
+
filtra en algún mensaje.
|
|
126
|
+
- El adaptador de PM suma `comment(id, markdown)` y `listComments(id)` en los tres backends:
|
|
127
|
+
sin poder leer sus propios comentarios, dai no puede saber qué ya estampó.
|
|
128
|
+
- Esta versión se cortó con los comandos nuevos, y el dogfooding devolvió tres de las
|
|
129
|
+
correcciones de arriba.
|
|
130
|
+
|
|
6
131
|
## [0.14.0] — 2026-09-08
|
|
7
132
|
|
|
8
133
|
**`dai pr` proponía mergear a `main` en un repo donde `main` despliega a producción, y el
|
|
@@ -918,6 +1043,8 @@ ClickUp y Jira Cloud.
|
|
|
918
1043
|
- Tests de las rutas de red (jira/clickup/forge) con `fetch` mockeado. Sin links rotos;
|
|
919
1044
|
`files` de npm sin tests ni secretos.
|
|
920
1045
|
|
|
1046
|
+
[0.15.1]: https://github.com/dforce2055/dai/releases/tag/v0.15.1
|
|
1047
|
+
[0.15.0]: https://github.com/dforce2055/dai/releases/tag/v0.15.0
|
|
921
1048
|
[0.14.0]: https://github.com/dforce2055/dai/releases/tag/v0.14.0
|
|
922
1049
|
[0.13.3]: https://github.com/dforce2055/dai/releases/tag/v0.13.3
|
|
923
1050
|
[0.13.2]: https://github.com/dforce2055/dai/releases/tag/v0.13.2
|
package/CONTRIBUTING.md
CHANGED
|
@@ -41,6 +41,28 @@ git config core.hooksPath .githooks # valida convención de commits + autoría
|
|
|
41
41
|
(Cursor, Copilot, Claude, …). Un agente puede ayudarte, pero el cambio lo firmas
|
|
42
42
|
tú: con tu identidad y sin el trailer del agente. Un check de CI lo bloquea en cada
|
|
43
43
|
PR (ver [`governance/human-authorship.md`](governance/human-authorship.md)).
|
|
44
|
+
6. **Código en inglés, comentarios en español.** Los identificadores —variables,
|
|
45
|
+
funciones, constantes, claves de objeto— van en **inglés**; los comentarios y la
|
|
46
|
+
documentación, en **español**. Y todo lo que sale por una interfaz de máquina
|
|
47
|
+
(`--json`, el payload de un webhook, los códigos de estado internos) va en inglés
|
|
48
|
+
también: es una API, la lee un script, y traducirla después rompe a quien la consume.
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
// El endpoint ES la credencial: quien lo tiene, postea. ← comentario en español
|
|
52
|
+
export function describeTarget(cfg) { ← código en inglés
|
|
53
|
+
const target = new URL(cfg.endpoint).host;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**La excepción, y es una sola:** los nombres de campo del `implements.yaml` son
|
|
57
|
+
parte del contrato del método, no del código. `autor:` se llama así en el schema
|
|
58
|
+
(ADR-0004) y en todos los repos que ya lo usan; renombrarlo sería romper el
|
|
59
|
+
contrato para ganar consistencia interna, que es el peor cambio posible.
|
|
60
|
+
|
|
61
|
+
Lo que **todavía no** cumple esta regla son los **mensajes al usuario**, que hoy son
|
|
62
|
+
literales en español repartidos por todo el CLI. Eso no es un descuido: es el
|
|
63
|
+
refactor que [ADR-0008](docs/adr/0008-estrategia-de-i18n.md) tiene planificado como
|
|
64
|
+
fase 3 (`DAI_LANG` + un catálogo `t(key)` sin dependencias). Mientras tanto, escribí
|
|
65
|
+
los mensajes en español y **no inventes un mecanismo de traducción propio**.
|
|
44
66
|
|
|
45
67
|
## Flujo (la propia metodología)
|
|
46
68
|
|
package/README.md
CHANGED
|
@@ -209,6 +209,7 @@ flowchart TD
|
|
|
209
209
|
| `dai forge review <ref> --from <review.json>` `[--dry-run\|--yes]` | **review inline**: un resumen + un comentario anclado a cada `archivo:línea`, clasificado low/medium/high. **Valida cada posición contra el diff** (descarta lo que el modelo inventó) antes de postear; sin `--yes` muestra el preview y no postea nada. Modo desatendido: `--min-severity`/`--min-confidence`/`--max-comments`. El review sale con `event: COMMENT`, nunca `APPROVE` ([ADR-0016](docs/adr/0016-review-inline.md)) |
|
|
210
210
|
| `dai forge comment <ref> --body-file <f>` · `dai forge pr <ref>` | comentar / leer una PR/MR (GitHub/GitLab) — el fallback simple, sin anclar |
|
|
211
211
|
| `dai ac-hash <us.md>` | calcula el hash de los criterios de aceptación de una US |
|
|
212
|
+
| `dai release plan` · `cut <X.Y.Z>` · `done <X.Y.Z>` · `stamp <X.Y.Z> --env <amb>` · `status` · `notify --test` | **el ciclo de versión** ([ADR-0019](docs/adr/0019-ciclo-de-version-y-aviso-de-release.md)). `plan` arma el **manifiesto**: qué US entran, cuáles quedaron **atrasadas** y qué se coló **sin US** — más el bump que **propone** (la versión la firma una persona: un cambio de default es minor aunque todo sea `fix:`). `cut` prepara (rama, número, entrada del CHANGELOG con el material para repartir, commit) y **no habla hacia afuera**; `done` cierra tras el merge (tag + release note + back-merge + aviso) — los dos pasos que más se olvidan. `stamp` le avisa a **cada US** en qué versión y ambiente salió: muestra el alcance real, es **idempotente** por (app, versión, ambiente) y **es opcional** (decir que no sale con 0). El tag es la versión; `VERSION`/`package.json` son espejos y puede no haber ninguno. Guía: [releases](docs/guias/releases.md) · Tutorial: [ciclo de release](docs/tutoriales/ciclo-de-release.md) |
|
|
212
213
|
| `dai help [<comando>]` · `dai <comando> --help` | ayuda del CLI. Pedir ayuda **nunca ejecuta el comando**: sale por `stdout` y termina con 0. Valen `--help`, `-h` y `dai <comando> help` — las tres formas, en todos los comandos |
|
|
213
214
|
| `dai doctor` · `dai docs <dest>` · `dai version` | diagnóstico del entorno (incluye **version-drift** del scaffold) · copiar la doc (sin los assets del sitio; los links a las capturas apuntan al sitio publicado) · versión (`dai version` avisa si tu repo quedó atrás) |
|
|
214
215
|
|
|
@@ -243,7 +244,7 @@ flowchart TD
|
|
|
243
244
|
> en [`skills/`](skills/).
|
|
244
245
|
|
|
245
246
|
Skills (se invocan en el asistente): `/doc-to-backlog` · `/grill-intent` · `/grill-epic` · `/grill-user-story` · `/link-us` ·
|
|
246
|
-
`/tdd` · `/dai-review`. Config del tracker (`md`\|`jira`\|`clickup`) y tokens: en `.env.dai`
|
|
247
|
+
`/tdd` · `/dai-review` · `/dai-release`. Config del tracker (`md`\|`jira`\|`clickup`) y tokens: en `.env.dai`
|
|
247
248
|
(no versionado; el `.env` del equipo no se toca — [ADR-0017](docs/adr/0017-env-dai.md)) —
|
|
248
249
|
ver [`.env.dai.example`](.env.dai.example). Auth (SSH + tokens): [ADR-0007](docs/adr/0007-modelo-de-autenticacion.md).
|
|
249
250
|
|
|
@@ -279,7 +280,7 @@ mi-repo/
|
|
|
279
280
|
├── CLAUDE.md · Constitución del proyecto (auto-cargada por Claude)
|
|
280
281
|
├── .env.dai · tu tracker (NO versionado, completa el token) + .env.dai.example (plantilla, sí versionada)
|
|
281
282
|
├── .claude/skills/ · Las skills, locales al repo (el equipo las hereda)
|
|
282
|
-
│ └── doc-to-backlog · grill-intent · grill-epic · grill-user-story · link-us · tdd · dai-review
|
|
283
|
+
│ └── doc-to-backlog · grill-intent · grill-epic · grill-user-story · link-us · tdd · dai-review · dai-release
|
|
283
284
|
├── .github/
|
|
284
285
|
│ ├── copilot-instructions.md · La constitución, auto-inyectada en cada chat de Copilot
|
|
285
286
|
│ ├── skills/ · Las mismas skills, en formato Copilot nativo (SKILL.md)
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.15.1
|