@dforce2055/dai 0.13.3 → 0.15.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/.env.dai.example CHANGED
@@ -12,6 +12,37 @@ DAI_PM=md
12
12
  # Si dai no puede saber el link, avisa y deja la PR sin él — nunca escribe el id pelado.
13
13
  # DAI_TRACKER_URL_TEMPLATE=https://jira.miempresa.com/browse/{id}
14
14
 
15
+ # ── Flujo de branches (dai pr · dai done) ────────────────────────────────────
16
+ # Las DOS ramas de vida larga del repo. No se configura "la base" de las PR: la base sale
17
+ # del TIPO de branch, y con eso dai deja de adivinar.
18
+ # feature/ · fix/ → PR contra DAI_BRANCH_DEV
19
+ # release/ · hotfix/ → PR contra DAI_BRANCH_PROD, con confirmación explícita
20
+ #
21
+ # Rama que INTEGRA el desarrollo (la que se despliega a test). Sin declarar, dai usa la
22
+ # rama default del remoto (origin/HEAD) y avisa que la está adivinando.
23
+ # DAI_BRANCH_DEV=testing
24
+
25
+ # Rama que DESPLIEGA A PRODUCCIÓN. `dai pr` la marca en el preview y pide confirmación
26
+ # antes de publicar; con --yes hace falta --to-prod. Sin declarar, dai no marca ninguna
27
+ # rama como producción: no adivina cuál es.
28
+ # DAI_BRANCH_PROD=main
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
+
15
46
  # ── Backend md (local, offline) ──────────────────────────────────────────────
16
47
  # Carpeta donde viven las US como <ID>.md (p. ej. ABC-482.md).
17
48
  DAI_MD_US_DIR=.dai/us
package/CHANGELOG.md CHANGED
@@ -3,6 +3,137 @@
3
3
  Formato basado en [Keep a Changelog](https://keepachangelog.com/). Versionado semver
4
4
  (ver `VERSION`).
5
5
 
6
+ ## [0.15.0] — 2026-09-09
7
+
8
+ **Un equipo puede tener el link QUÉ↔CÓMO perfecto y seguir sin poder contestar la pregunta
9
+ que hace el negocio: "¿esto ya está en producción?". La trazabilidad llegaba hasta la PR y
10
+ se cortaba justo ahí. Esta versión agrega el último eslabón — la versión desplegada — y con
11
+ él, el ciclo completo para cortarla, cerrarla y contarla.**
12
+
13
+ ### Agregado
14
+ - **`dai release plan` — el manifiesto de la versión.** Qué User Stories entran entre el
15
+ último tag y la rama de integración, en qué estado está cada una, y qué entró **sin**
16
+ declarar US. Resuelve las US leyendo los `implements.yaml` **tal como estaban en cada
17
+ commit del rango**, no por el nombre de la rama: el link viaja con el código, así que la
18
+ respuesta sobrevive a que la branch se borre y a que el change se archive — el estado
19
+ normal del repo cuando llegás a cortar, días después del merge. Es el dato del que
20
+ dependen los otros cuatro comandos; `--json` es lo que consume la skill.
21
+ - **`dai release cut <X.Y.Z>` — preparar, sin hablar hacia afuera.** Rama de release, número
22
+ en los archivos que el repo espeja, entrada del CHANGELOG y commit. Ni push, ni tag, ni
23
+ PR: todo lo que pasa ANTES de la firma humana.
24
+ - **`dai release done <X.Y.Z>` — cerrar, después del merge.** Tag anotado, release note en
25
+ el forge, back-merge a integración, borrado de la rama de release y aviso al canal. Existe
26
+ como comando separado porque los dos pasos que más se olvidan cuando la ceremonia se hace
27
+ a mano viven en esta mitad, la que queda después de la firma. Se llama `done` y no
28
+ `finish` por lo mismo que `dai done` cierra el trabajo de una branch: mismo verbo, distinto
29
+ sustantivo.
30
+ - **`dai release stamp <X.Y.Z> --env <ambiente>` — que cada US sepa dónde está.** Deja en
31
+ cada historia del release un comentario con versión, app, ambiente y fecha, y con eso el
32
+ funcional lee el ticket en vez de preguntar. Una US federada en varios repos acumula sola
33
+ su matriz. Es **opcional** y decir que no sale con 0: cuando el comando corre, el tag ya
34
+ existe y la versión está hecha.
35
+ - **`dai release status`** — dónde estás en el ciclo: versión declarada vs último tag, qué
36
+ falta promover, si quedó un back-merge pendiente, qué ramas de release sobrevivieron.
37
+ **`dai release notify --test`** — probar el canal antes de depender de él, porque un
38
+ webhook no se puede validar sin postear y fingir que sí sería justo lo que dai no hace.
39
+ - **Aviso de release a un canal de equipo** (`DAI_NOTIFY`): discord, slack, webex, telegram
40
+ o un `webhook` genérico para Teams, Mattermost o un sistema interno. Apagado por default:
41
+ dai no habla hacia afuera sin que se lo pidan. El endpoint **es la credencial**, así que
42
+ vive en el `.env.dai` y dai muestra el host, nunca la URL — tampoco en los errores.
43
+ - **Skill `/dai-release`** — conduce el ciclo confirmando paso a paso. No recalcula el
44
+ manifiesto: lo pide y lo narra. Si su memoria y el CLI se contradicen, gana el CLI.
45
+ - **[Guía de releases](docs/guias/releases.md)** con el *porqué* y
46
+ **[tutorial del ciclo completo](docs/tutoriales/ciclo-de-release.md)**. Una estrategia de
47
+ branching que el equipo no entiende se abandona en dos sprints, así que la documentación
48
+ entra en la misma versión que los comandos, no después.
49
+
50
+ ### Corregido
51
+ - **`dai pr` no podía abrir la PR de una `feature/` en un repo sin User Stories.** Le pasa a
52
+ cualquier repo de tooling o librería interna que use dai para versionar sin gestionar sus
53
+ propias historias — a este, sin ir más lejos, donde el mensaje mandaba a renombrar la
54
+ branch a `chore/`. Lo que distingue un olvido de un repo que no trabaja así es si la
55
+ branch **nombra un ticket**. El gate de CI no se toca.
56
+ - **Un aviso que aparece siempre no avisa nada.** El manifiesto marcaba cada branch sin US
57
+ como "entró trabajo sin link", incluso en repos donde ninguna branch va a declarar una
58
+ jamás. Ahora el hallazgo se reporta solo si el repo trabaja con User Stories — salvo que
59
+ la branch nombre un ticket, que ahí sí es un olvido.
60
+ - **`dai pr` fallaba al actualizar una PR por un motivo que no era suyo.** `gh pr edit`
61
+ resuelve por GraphQL y arrastra campos deprecados del servidor, así que devolvía un error
62
+ sobre *Projects (classic)* cuando lo único que se quería era cambiar el body. Ahora
63
+ reintenta por REST con la misma autenticación; se intenta callado y solo se reporta si el
64
+ plan B también falla.
65
+
66
+ ### Interno
67
+ - **475 tests** (+68 desde la 0.14.0): el manifiesto y su resolución por commit, el corte y
68
+ el cierre, el gate de alcance del estampado y su idempotencia por (app, versión,
69
+ ambiente), y el adaptador de canal — incluidos tres tests que fallan si el endpoint se
70
+ filtra en algún mensaje.
71
+ - El adaptador de PM suma `comment(id, markdown)` y `listComments(id)` en los tres backends:
72
+ sin poder leer sus propios comentarios, dai no puede saber qué ya estampó.
73
+ - Esta versión se cortó con los comandos nuevos, y el dogfooding devolvió tres de las
74
+ correcciones de arriba.
75
+
76
+ ## [0.14.0] — 2026-09-08
77
+
78
+ **`dai pr` proponía mergear a `main` en un repo donde `main` despliega a producción, y el
79
+ preview no lo destacaba de ninguna forma. Tirando de ese hilo apareció que el problema no
80
+ era el default: era pedirle a alguien que configure "la base", cuando la base no es una
81
+ constante — es consecuencia del tipo de branch. Y de yapa, el hallazgo más caro de la
82
+ versión: `dai <comando> --help` no imprimía ayuda, ejecutaba el comando.**
83
+
84
+ ### Cambiado
85
+ - **La base de una PR sale del mapa de ramas del repo, no de un `main` fijo.** Se declaran
86
+ las **dos ramas de vida larga** en el `.env.dai` —`DAI_BRANCH_DEV` (la que integra) y
87
+ `DAI_BRANCH_PROD` (la que despliega a producción)— y `dai pr` deriva la base del **tipo de
88
+ branch**: `feature/` y `fix/` integran, `release/` y `hotfix/` van contra producción.
89
+ `--base` gana siempre. Sin nada declarado, dai cae a la rama default del remoto
90
+ (`origin/HEAD`) **y avisa que la está adivinando** — antes decía `main` sin más.
91
+ `dai done` usa el mismo mapa (tenía el mismo `main` hardcodeado) y `dai doctor` lo reporta.
92
+ El preview ahora dice **de dónde salió** la base, que era la mitad que faltaba
93
+ ([#46](https://github.com/dforce2055/dai/issues/46)).
94
+ - **Apuntarle a producción pide confirmación explícita.** Si la base es `DAI_BRANCH_PROD`,
95
+ el preview la marca `⚠️ DESPLIEGA A PRODUCCIÓN` y hay que **escribir el nombre de la rama**
96
+ para seguir; con `--yes` hace falta `--to-prod`. Sin la variable declarada dai no marca
97
+ ninguna rama como producción: no adivina cuál es, y un gate inventado sobre una suposición
98
+ es peor que no tenerlo.
99
+ - **`dai <comando> --help` imprime ayuda en vez de ejecutar el comando.** Vale para todos:
100
+ `dai help`, `dai --help`, `dai -h`, `dai help <cmd>`, `dai <cmd> --help`, `dai <cmd> -h` y
101
+ `dai <cmd> help`. Siempre por `stdout` y siempre con código 0; un comando desconocido sigue
102
+ saliendo por `stderr` con código ≠ 0, que es lo que deja `dai foo --help` usable dentro de
103
+ un script. Cada comando tiene ayuda propia (qué hace, uso, opciones, ejemplo).
104
+
105
+ ### Corregido
106
+ - **`dai pr` dejaba una MR con el diff al día y la descripción vieja.** Con una MR ya abierta
107
+ pusheaba la branch, fallaba al crear porque la MR existía, y la única señal era el comando
108
+ crudo del forge. Quedaba una MR que **miente sobre lo que contiene**, que es peor que un
109
+ error porque parece que salió bien. Ahora la detecta **antes** de pushear
110
+ (`gh pr list --head` / `glab mr list --source-branch`), el preview dice
111
+ `── Pull Request a ACTUALIZAR (#12) ──` y actualiza título y descripción. Si la detección no
112
+ pudo correr y el forge responde *"already exists"*, la busca y la actualiza igual; si tampoco
113
+ puede, lo dice con todas las letras: *"NO toqué su descripción: quedó la vieja"*. También
114
+ avisa si la MR abierta apunta a otra base que la pedida ([#46](https://github.com/dforce2055/dai/issues/46)).
115
+ - **`dai link-us` estampaba `version: v1` en una US que declaraba `v4`.** El regex del
116
+ `spec_version` exigía separador y la US lo escribía pegado (`specversion`) — y estaba
117
+ **duplicado en dos módulos**, que es exactamente por qué se podía arreglar en uno y seguir
118
+ roto en el otro. Ahora vive en un solo lugar y tolera `spec_version`, `spec version`,
119
+ `spec-version` y `specversion`. Sin `spec_version` declarado **no se inventa un `v1`**: queda
120
+ `pendiente` con aviso, porque ese número se publica en el cuerpo de la PR y se estampa en el
121
+ tracker como si fuera un dato. `dai check` además avisa cuando el número del link no coincide
122
+ con el de la US viva ([#46](https://github.com/dforce2055/dai/issues/46)).
123
+ - **`dai pr` no podía abrir la PR de un repo sin US.** En un repo que no se trackea a sí mismo
124
+ con User Stories —el de dai, sin ir más lejos— una branch `fix/` sin ID moría pidiendo un
125
+ link que no puede existir, y aconsejaba renombrarla a `chore/`, que para un fix es el consejo
126
+ equivocado. Ahora, si la branch no exige link **y** el repo no declara ninguna US, la PR sale
127
+ "Sin US" con el motivo. Una `feature/` sin link sigue fallando: ahí falta de verdad.
128
+ - **`.env.dai` no estaba en el `.gitignore` de este repo**, aunque `dai init` lo agrega en todos
129
+ los que scaffoldea. Faltaba justo en el que se publica en npm.
130
+
131
+ ### Interno
132
+ - **408 tests** (+41): el mapa de ramas y la derivación por tipo de branch, la detección y
133
+ actualización de una PR existente en los dos forges, las variantes del `spec_version`, y la
134
+ convención de ayuda — con un test que recorre los `case` del dispatcher y **falla si alguno se
135
+ agrega sin ayuda**, para que la convención no dependa de acordarse.
136
+
6
137
  ## [0.13.3] — 2026-09-02
7
138
 
8
139
  **Una PR de dai se abría diciendo, en el mismo párrafo, dos cosas que no encajaban: que el
@@ -857,6 +988,8 @@ ClickUp y Jira Cloud.
857
988
  - Tests de las rutas de red (jira/clickup/forge) con `fetch` mockeado. Sin links rotos;
858
989
  `files` de npm sin tests ni secretos.
859
990
 
991
+ [0.15.0]: https://github.com/dforce2055/dai/releases/tag/v0.15.0
992
+ [0.14.0]: https://github.com/dforce2055/dai/releases/tag/v0.14.0
860
993
  [0.13.3]: https://github.com/dforce2055/dai/releases/tag/v0.13.3
861
994
  [0.13.2]: https://github.com/dforce2055/dai/releases/tag/v0.13.2
862
995
  [0.13.1]: https://github.com/dforce2055/dai/releases/tag/v0.13.1
package/README.md CHANGED
@@ -202,13 +202,15 @@ flowchart TD
202
202
  | `dai link-us <ID> --resync` | re-estampa el `ac_hash` contra la US viva (tras un ⚠️ de check) |
203
203
  | `dai check` | compara tu código vs la US viva → ✅ al día / ⚠️ atrasado (exit code = gate de PR) |
204
204
  | `dai ls [--json]` | lista las US que implementa el repo + su link al tracker |
205
- | `dai pr [--assignee u] [--base b] [--draft] [--yes] [--us ID] [--title t] [--description t\|--description-file f] [--changes t\|--changes-file f]` · alias **`dai mr`** | crea TU PR/MR precargada: pregunta la branch base (default `main`), muestra el texto y confirma antes de publicar. Detecta el forge (GitHub→PR con `gh` · GitLab→MR con `glab`); `mr` es el mismo comando, más natural en GitLab. **La US la resuelve la branch** (la nombra `dai link-us`): si hay varias vivas y ninguna coincide, **pregunta** en vez de elegir por vos (sin TTY falla pidiendo `--us <ID>`), y una branch `chore/`/`docs/` sale **sin US** en lugar de heredar la de otro. **La descripción la escribís vos (o tu agente) con `--description`**: dai llena la US, los commits y los links, pero no inventa el propósito de un cambio — si "Descripción" o "Cambios realizados" quedarían con el molde del template, con `--yes` o sin TTY **no publica** y te dice qué falta |
205
+ | `dai pr [--assignee u] [--base b] [--draft] [--yes] [--us ID] [--title t] [--description t\|--description-file f] [--changes t\|--changes-file f]` · alias **`dai mr`** | crea **o actualiza** TU PR/MR precargada: propone la branch base **según el tipo de rama** (`feature/`/`fix/` `DAI_BRANCH_DEV` · `release/`/`hotfix/` → `DAI_BRANCH_PROD`; `--base` gana, y si no hay nada declarado cae a la rama default del remoto **avisando que adivina**), muestra el texto y confirma antes de publicar. Contra la rama de producción pide una **confirmación explícita** (escribir el nombre de la rama; con `--yes` hace falta `--to-prod`). Si la branch **ya tiene una PR/MR abierta**, actualiza su título y su descripción en lugar de fallar dejando el diff al día y el body viejo. Detecta el forge (GitHub→PR con `gh` · GitLab→MR con `glab`); `mr` es el mismo comando, más natural en GitLab. **La US la resuelve la branch** (la nombra `dai link-us`): si hay varias vivas y ninguna coincide, **pregunta** en vez de elegir por vos (sin TTY falla pidiendo `--us <ID>`), y una branch `chore/`/`docs/` sale **sin US** en lugar de heredar la de otro. **La descripción la escribís vos (o tu agente) con `--description`**: dai llena la US, los commits y los links, pero no inventa el propósito de un cambio — si "Descripción" o "Cambios realizados" quedarían con el molde del template, con `--yes` o sin TTY **no publica** y te dice qué falta |
206
206
  | `dai stamp` | estampa la cobertura inversa en el tracker (branch + commit-ancla) |
207
- | `dai done [--base main] [--force]` | cierra la US: vuelve a la base, `fetch --prune` + `pull`, y borra la branch local **si está mergeada** (chequeo estricto; `--force` la borra igual). Redes: no estar en la base, sin cambios sueltos, sin commits sin pushear |
207
+ | `dai done [--base b] [--force]` | cierra la US: vuelve a la base (se resuelve igual que en `dai pr`), `fetch --prune` + `pull`, y borra la branch local **si está mergeada** (chequeo estricto; `--force` la borra igual). Redes: no estar en la base, sin cambios sueltos, sin commits sin pushear |
208
208
  | `dai archive [<change>] [--skip-specs]` | **funde los delta specs del change en las specs canónicas** (`openspec/specs/`) y lo archiva. Lo corre el **aprobador** de la PR (gate de aprobación, [ADR-0011](docs/adr/0011-archive-gate-de-aprobacion.md)); detecta el change activo o le pasás el nombre. Envuelve `openspec archive` |
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) |
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 |
212
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) |
213
215
 
214
216
  > **🆕 Mantené tu repo al día — `dai sync`.** Las skills, la constitución y los templates son un
@@ -242,7 +244,7 @@ flowchart TD
242
244
  > en [`skills/`](skills/).
243
245
 
244
246
  Skills (se invocan en el asistente): `/doc-to-backlog` · `/grill-intent` · `/grill-epic` · `/grill-user-story` · `/link-us` ·
245
- `/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`
246
248
  (no versionado; el `.env` del equipo no se toca — [ADR-0017](docs/adr/0017-env-dai.md)) —
247
249
  ver [`.env.dai.example`](.env.dai.example). Auth (SSH + tokens): [ADR-0007](docs/adr/0007-modelo-de-autenticacion.md).
248
250
 
@@ -278,7 +280,7 @@ mi-repo/
278
280
  ├── CLAUDE.md · Constitución del proyecto (auto-cargada por Claude)
279
281
  ├── .env.dai · tu tracker (NO versionado, completa el token) + .env.dai.example (plantilla, sí versionada)
280
282
  ├── .claude/skills/ · Las skills, locales al repo (el equipo las hereda)
281
- │ └── 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
282
284
  ├── .github/
283
285
  │ ├── copilot-instructions.md · La constitución, auto-inyectada en cada chat de Copilot
284
286
  │ ├── skills/ · Las mismas skills, en formato Copilot nativo (SKILL.md)
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.13.3
1
+ 0.15.0