@dforce2055/dai 0.11.0 → 0.13.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 +147 -0
- package/README.md +1 -1
- package/VERSION +1 -1
- package/cli/dai.mjs +136 -39
- package/cli/lib/bootstrap.mjs +43 -2
- package/cli/lib/branch-scope.mjs +61 -5
- package/cli/lib/link-us.mjs +5 -2
- package/cli/lib/pr.mjs +21 -1
- package/docs/EJEMPLO-END-TO-END.md +1 -1
- package/docs/glosario.md +1 -1
- package/docs/guias/dev.md +11 -1
- package/docs/public/tutoriales/funcional-1-skills-usuario.png +0 -0
- package/docs/public/tutoriales/funcional-2-copilot-signin.png +0 -0
- package/docs/public/tutoriales/funcional-3-carpeta-configurada.png +0 -0
- package/docs/public/tutoriales/funcional-4-doctor.png +0 -0
- package/docs/public/tutoriales/funcional-5-publish-parent.png +0 -0
- package/docs/tutoriales/index.md +9 -0
- package/docs/tutoriales/setup-dev.md +616 -0
- package/docs/tutoriales/setup-funcional.md +480 -0
- package/package.json +1 -1
- package/skills/link-us/SKILL.md +1 -1
- package/skills/link-us/templates/implements.yaml +4 -2
- package/templates/definition-of-done.md +6 -1
package/cli/lib/branch-scope.mjs
CHANGED
|
@@ -56,17 +56,22 @@ export function trackerKeysIn(branch) {
|
|
|
56
56
|
// chore/, docs/, ci/… → nunca
|
|
57
57
|
// fix/ y el resto → solo si el nombre trae un ID con pinta de key de tracker
|
|
58
58
|
// `main`/`develop`/sin branch → no (no es una branch de trabajo).
|
|
59
|
+
//
|
|
60
|
+
// `kind` distingue POR QUÉ no se exige, que no es lo mismo para todos los comandos:
|
|
61
|
+
// una `chore/` está exenta POR TIPO (el repo declara que ahí no hay producto), mientras
|
|
62
|
+
// que `mi-branch` simplemente no dice nada. `dai pr` usa esa diferencia para no colgarle
|
|
63
|
+
// la US viva del repo a una PR de archivado (issue #31).
|
|
59
64
|
export function requiresLink(branch) {
|
|
60
65
|
const t = branchType(branch);
|
|
61
|
-
if (ALWAYS.has(t)) return { required: true, reason: `'${t}/' es trabajo de producto: requiere US` };
|
|
62
|
-
if (EXEMPT.has(t)) return { required: false, reason: `'${t}/' está exenta de US (governance/branch-naming.md)` };
|
|
63
|
-
if (t === "") return { required: false, reason: "sin prefijo tipo/, no es una branch de trabajo: sin gate de link" };
|
|
66
|
+
if (ALWAYS.has(t)) return { required: true, kind: "always", reason: `'${t}/' es trabajo de producto: requiere US` };
|
|
67
|
+
if (EXEMPT.has(t)) return { required: false, kind: "exempt", reason: `'${t}/' está exenta de US (governance/branch-naming.md)` };
|
|
68
|
+
if (t === "") return { required: false, kind: "untyped", reason: "sin prefijo tipo/, no es una branch de trabajo: sin gate de link" };
|
|
64
69
|
// Tipo desconocido (fix/, spike/, lo que el repo use): si nombró un ID, lo tomamos
|
|
65
70
|
// como intención de implementar una US y se lo exigimos. Si no, no inventamos.
|
|
66
71
|
const keyish = trackerKeysIn(branch).length > 0;
|
|
67
72
|
return keyish
|
|
68
|
-
? { required: true, reason: `'${t}/' con un ID en el nombre: se toma como trabajo de producto` }
|
|
69
|
-
: { required: false, reason: `'${t}/' sin ID en el nombre: no exige US (governance/branch-naming.md)` };
|
|
73
|
+
? { required: true, kind: "always", reason: `'${t}/' con un ID en el nombre: se toma como trabajo de producto` }
|
|
74
|
+
: { required: false, kind: "untyped", reason: `'${t}/' sin ID en el nombre: no exige US (governance/branch-naming.md)` };
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
// Aplana los implements descubiertos a filas { path, change, repo, id, version, ac_hash },
|
|
@@ -142,3 +147,54 @@ export function stampScope({ branch, rows, allRows = rows, ids = [], all = false
|
|
|
142
147
|
reason: `hay ${hit.length > 1 ? hit.length : rows.length} US vivas y la branch '${branch}' no dice cuál`,
|
|
143
148
|
};
|
|
144
149
|
}
|
|
150
|
+
|
|
151
|
+
// La decisión de `dai pr`: ¿con qué US se titula y se linkea ESTA PR?
|
|
152
|
+
//
|
|
153
|
+
// { mode, target, candidates, reason, missing }
|
|
154
|
+
//
|
|
155
|
+
// mode "explicit" → el id que pidió el usuario (dai pr --us ABC-482)
|
|
156
|
+
// mode "branch" → la branch nombra una US viva del repo: esa
|
|
157
|
+
// mode "only" → hay una sola US viva y la branch no está exenta: esa
|
|
158
|
+
// mode "exempt" → branch exenta por tipo (chore/, docs/…) que no nombra US: PR SIN US
|
|
159
|
+
// mode "ambiguous" → varias candidatas y ninguna pista: NO elige, pregunta
|
|
160
|
+
// mode "none" → la branch pide US y el repo no tiene ninguna viva
|
|
161
|
+
//
|
|
162
|
+
// Es la misma pregunta que resuelve stampScope, con dos diferencias que importan
|
|
163
|
+
// (issues #31, #32, #33 — antes `dai pr` recorría TODOS los implements.yaml del repo,
|
|
164
|
+
// archivados incluidos, y se quedaba con el último):
|
|
165
|
+
//
|
|
166
|
+
// 1. Una PR implementa UNA US, no cuatro: no hay modo "all", y el resultado es un
|
|
167
|
+
// único `target`.
|
|
168
|
+
// 2. Una branch exenta NO hereda la US viva del repo. Una PR de `chore/archive-specs`
|
|
169
|
+
// titulada con la historia de un compañero es peor que una sin título lindo: la
|
|
170
|
+
// lista de PRs es la única superficie donde el equipo lee la trazabilidad.
|
|
171
|
+
//
|
|
172
|
+
// `rows` viene del discover SIN archivados; `allRows` incluye los archivados y solo se
|
|
173
|
+
// usa para resolver un id explícito (reabrir la PR de un change ya archivado) y para
|
|
174
|
+
// listar qué US conoce el repo cuando el id pedido no está.
|
|
175
|
+
export function prScope({ branch, rows, allRows = rows, ids = [] }) {
|
|
176
|
+
if (ids.length) {
|
|
177
|
+
const want = String(ids[0]).toLowerCase();
|
|
178
|
+
const target = allRows.find((r) => String(r.id).toLowerCase() === want) || null;
|
|
179
|
+
// `missing` conserva la grafía del usuario: devolverle su ABC-404 en minúsculas lo
|
|
180
|
+
// manda a dudar del case en vez de del id.
|
|
181
|
+
return { mode: "explicit", target, candidates: allRows, missing: target ? [] : [ids[0]], reason: `pediste --us ${ids[0]}` };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const hit = matchBranchToImplements(branch, rows);
|
|
185
|
+
if (hit.length === 1) {
|
|
186
|
+
return { mode: "branch", target: hit[0], candidates: rows, reason: `la branch '${branch}' nombra ${hit[0].id}` };
|
|
187
|
+
}
|
|
188
|
+
if (hit.length > 1) {
|
|
189
|
+
return { mode: "ambiguous", target: null, candidates: hit, reason: `la branch '${branch}' nombra ${hit.length} US vivas` };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// La branch no nombra ninguna US viva.
|
|
193
|
+
const req = requiresLink(branch);
|
|
194
|
+
if (req.kind === "exempt") {
|
|
195
|
+
return { mode: "exempt", target: null, candidates: rows, reason: `${req.reason} y su nombre no nombra ninguna US` };
|
|
196
|
+
}
|
|
197
|
+
if (rows.length === 0) return { mode: "none", target: null, candidates: [], reason: "no hay implements.yaml vivo en el repo" };
|
|
198
|
+
if (rows.length === 1) return { mode: "only", target: rows[0], candidates: rows, reason: "es la única US viva del repo" };
|
|
199
|
+
return { mode: "ambiguous", target: null, candidates: rows, reason: `hay ${rows.length} US vivas y la branch '${branch}' no dice cuál` };
|
|
200
|
+
}
|
package/cli/lib/link-us.mjs
CHANGED
|
@@ -41,7 +41,9 @@ export function extractTitle(md) {
|
|
|
41
41
|
|
|
42
42
|
// Render del implements.yaml (schema ADR-0004).
|
|
43
43
|
export function renderImplementsYaml({ change, repo, id, version = "v1", ac_hash, autor }) {
|
|
44
|
-
return `# Link QUÉ↔CÓMO · scaffoldeado por dai link-us. El ÚNICO link
|
|
44
|
+
return `# Link QUÉ↔CÓMO · scaffoldeado por dai link-us. El ÚNICO link AUTORADO del método:
|
|
45
|
+
# lo escribe alguien, no se deriva. Todo lo de abajo ya está resuelto salvo 'introduces',
|
|
46
|
+
# que se completa AL TERMINAR de implementar — recién ahí se sabe qué se introdujo.
|
|
45
47
|
# Schema: docs/adr/0004-ubicacion-y-schema-implements.md
|
|
46
48
|
change: ${change}
|
|
47
49
|
repo: ${repo}
|
|
@@ -52,7 +54,8 @@ implements:
|
|
|
52
54
|
ac_hash: ${ac_hash}
|
|
53
55
|
|
|
54
56
|
introduces:
|
|
55
|
-
- <capacidad-tecnica> #
|
|
57
|
+
- <capacidad-tecnica> # al cerrar la implementación: capacidades técnicas nuevas
|
|
58
|
+
# (o borrá el bloque si no hay ninguna)
|
|
56
59
|
|
|
57
60
|
autor: ${autor}
|
|
58
61
|
`;
|
package/cli/lib/pr.mjs
CHANGED
|
@@ -18,6 +18,7 @@ export function replaceSection(body, heading, content) {
|
|
|
18
18
|
// placeholders que encuentra y deja el resto para que el humano lo edite.
|
|
19
19
|
export function composePrBody(template, d) {
|
|
20
20
|
let b = template;
|
|
21
|
+
if (!d.id) return composePrBodyNoUs(b, d);
|
|
21
22
|
b = b.replace(/`ABC-###`/g, `\`${d.id}\``);
|
|
22
23
|
b = b.replace(/@ `vX`/g, `@ \`${d.version}\``);
|
|
23
24
|
b = b.replace(/`<hash>`/g, `\`${d.ac_hash}\``);
|
|
@@ -32,6 +33,21 @@ export function composePrBody(template, d) {
|
|
|
32
33
|
return upsertLinksBlock(b, d);
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
// PR de una branch exenta (chore/, docs/, release/…): no implementa una US y no se le
|
|
37
|
+
// exige link. Lo que NO puede pasar es que salga con la US de otro ni con el placeholder
|
|
38
|
+
// `ABC-###` del template — las dos cosas pasaron en repos reales (issues #31, #33).
|
|
39
|
+
// Se dice explícitamente que no hay US, y por qué.
|
|
40
|
+
function composePrBodyNoUs(template, d) {
|
|
41
|
+
let b = replaceSection(template, "🔗 Implementa",
|
|
42
|
+
`- **Sin US:** esta PR no implementa una User Story.\n` +
|
|
43
|
+
(d.noUsReason ? `- **Motivo:** ${d.noUsReason}.\n` : "") +
|
|
44
|
+
`- No se le exige link (\`governance/branch-naming.md\`).`);
|
|
45
|
+
if (d.commits && d.commits.length) {
|
|
46
|
+
b = replaceSection(b, "Cambios realizados", d.commits.map((c) => `- [x] ${c}`).join("\n"));
|
|
47
|
+
}
|
|
48
|
+
return upsertLinksBlock(b, d);
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
// ── Bloque de enlaces ────────────────────────────────────────────────────────
|
|
36
52
|
// Delimitado y regenerable a propósito. Con el comentario suelto de antes, cualquier
|
|
37
53
|
// agente que reescribiera "Enlaces relacionados" se lo llevaba puesto sin dejar rastro
|
|
@@ -70,8 +86,12 @@ export function upsertLinksBlock(body, d) {
|
|
|
70
86
|
}
|
|
71
87
|
|
|
72
88
|
// Título del PR: el pasado a mano, o "<ID>: <título de la US>", o solo el ID.
|
|
73
|
-
|
|
89
|
+
// Sin US (branch exenta) cae al `fallback` — el subject del último commit: describe
|
|
90
|
+
// lo que hay adentro. Inventar un título con la US de otro es el bug de los issues
|
|
91
|
+
// #31/#33, y el título es lo ÚNICO que se ve en la lista de PRs.
|
|
92
|
+
export function prTitle(opts, id, usTitle, fallback = "") {
|
|
74
93
|
if (opts.title) return opts.title;
|
|
94
|
+
if (!id) return fallback;
|
|
75
95
|
if (usTitle) return `${id}: ${usTitle}`;
|
|
76
96
|
return id;
|
|
77
97
|
}
|
|
@@ -157,7 +157,7 @@ $ dai link-us ABC-482 --us us.md --change finalizar-compra
|
|
|
157
157
|
```
|
|
158
158
|
|
|
159
159
|
```yaml
|
|
160
|
-
# implements.yaml — el ÚNICO link
|
|
160
|
+
# implements.yaml — el ÚNICO link AUTORADO: se escribe, no se deriva (schema ADR-0004)
|
|
161
161
|
change: finalizar-compra
|
|
162
162
|
repo: frontend
|
|
163
163
|
|
package/docs/glosario.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
| Término | Qué es |
|
|
21
21
|
|---|---|
|
|
22
22
|
| **Link (QUÉ↔CÓMO)** | La relación entre un requerimiento y su implementación. |
|
|
23
|
-
| **`implements`** | La declaración `implements: <id>@<version>` en el código. El **único** link autorado
|
|
23
|
+
| **`implements`** | La declaración `implements: <id>@<version>` en el código. El **único** link autorado (se escribe, no se deriva): lo scaffoldea `dai link-us` y se cierra al terminar de implementar. |
|
|
24
24
|
| **`implements.yaml`** | El archivo, en el change del repo, que contiene ese link. Lo genera `link-us`. Ejemplo lleno + árbol de dónde vive entre los artefactos de OpenSpec: [ADR-0004](adr/0004-ubicacion-y-schema-implements.md). |
|
|
25
25
|
| **Trazabilidad inversa / cobertura** | El mapa "quién implementó este QUÉ". **Se genera, nunca se escribe.** |
|
|
26
26
|
| **Índice / router** | La tabla central que dice qué ID vive en qué repos. Es un router, **no un almacén**: no guarda el detalle. |
|
package/docs/guias/dev.md
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
- El **CÓMO**: diseño técnico, modelo de datos, arquitectura de la solución.
|
|
9
9
|
- Las **tareas técnicas** (las derivas tú, desde la US, con OpenSpec).
|
|
10
|
-
- El **link** (`implements.yaml`): es el **único** que se autora
|
|
10
|
+
- El **link** (`implements.yaml`): es el **único** que se autora, no se deriva ([Art. 9](../MANIFIESTO.md#art-9)).
|
|
11
|
+
`dai link-us` lo scaffoldea con el `id`/`version`/`ac_hash` ya resueltos; lo único que queda
|
|
12
|
+
abierto es `introduces`, y se cierra **al terminar de implementar** —normalmente lo completa
|
|
13
|
+
el agente que implementó, y tú lo revisas en la PR—.
|
|
11
14
|
- El **código** y su **spec técnica**.
|
|
12
15
|
|
|
13
16
|
## Lo que NO tocas
|
|
@@ -88,3 +91,10 @@ que está junto a tu `implements.yaml`.
|
|
|
88
91
|
- `dai check` · `dai pr` · `dai stamp` · `dai done` (limpieza, opcional)
|
|
89
92
|
- `dai update-us` — empuja al tracker una US que refinaste implementando
|
|
90
93
|
- `definition-of-done.md`
|
|
94
|
+
|
|
95
|
+
> **Ojo con el nombre de los comandos `opsx`.** Acá se escriben en la forma de Claude Code
|
|
96
|
+
> (`/opsx:apply`). En **Copilot y Cursor** los mismos comandos van **con guion**:
|
|
97
|
+
> `/opsx-apply`. No es un alias: es el nombre del archivo que genera OpenSpec para cada
|
|
98
|
+
> asistente. Con la forma equivocada el agente no encuentra el comando, no carga el workflow
|
|
99
|
+
> y se pone a improvisar sin avisar. `dai init` te dice cuál te toca.
|
|
100
|
+
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/tutoriales/index.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Guías de **setup operativo** — lo que haces una vez por máquina para trabajar con dai.
|
|
4
4
|
|
|
5
|
+
## De cero a publicar
|
|
6
|
+
|
|
7
|
+
- [**Setup para analistas funcionales y PMs (Windows)**](./setup-funcional) — el camino
|
|
8
|
+
completo sin git ni repositorio: Node, dai, las skills en Copilot, el token de Jira y una
|
|
9
|
+
épica + US de prueba publicadas de verdad.
|
|
10
|
+
- [**Setup para desarrolladores (Windows)**](./setup-dev) — el otro lado: Node, dai, git + SSH
|
|
11
|
+
+ `glab`, las skills en Copilot, OpenSpec y el ciclo completo sobre una US real
|
|
12
|
+
(`link-us` → `check` → `mr` → `stamp`).
|
|
13
|
+
|
|
5
14
|
## Preparar el entorno
|
|
6
15
|
|
|
7
16
|
- [**Configurar git**](./configurar-git) — tu identidad (nombre + correo) para que los
|