@dforce2055/dai 0.8.2 → 0.10.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.
Files changed (55) hide show
  1. package/{.env.example → .env.dai.example} +3 -3
  2. package/CHANGELOG.md +81 -0
  3. package/README.md +41 -29
  4. package/VERSION +1 -1
  5. package/cli/dai.mjs +175 -44
  6. package/cli/lib/bootstrap.mjs +42 -7
  7. package/cli/lib/env.mjs +12 -0
  8. package/cli/lib/forge-api.mjs +89 -2
  9. package/cli/lib/pm-clickup.mjs +2 -2
  10. package/cli/lib/pm-jira.mjs +2 -2
  11. package/cli/lib/review-findings.mjs +195 -0
  12. package/cli/lib/skills-source.mjs +8 -0
  13. package/docs/EJEMPLO-END-TO-END.md +52 -43
  14. package/docs/MANIFIESTO.md +2 -2
  15. package/docs/METODOLOGIA.md +20 -15
  16. package/docs/PROBAR.md +13 -14
  17. package/docs/SCRUM-CON-IA.md +10 -10
  18. package/docs/adr/0003-deteccion-y-estampado-son-comandos.md +1 -1
  19. package/docs/adr/0006-distribucion-y-licencia.md +1 -1
  20. package/docs/adr/0013-skills-externas-install-from.md +11 -4
  21. package/docs/adr/0015-jira-corporativo.md +1 -1
  22. package/docs/adr/0016-review-inline.md +128 -0
  23. package/docs/adr/0017-env-dai.md +64 -0
  24. package/docs/adr/README.md +2 -0
  25. package/docs/detalle/01-refinamiento.md +6 -5
  26. package/docs/detalle/03-ramas.md +2 -2
  27. package/docs/detalle/04-tdd.md +15 -9
  28. package/docs/detalle/06-code-review.md +8 -5
  29. package/docs/detalle/08-daily.md +1 -1
  30. package/docs/detalle/README.md +1 -1
  31. package/docs/glosario.md +2 -2
  32. package/docs/guias/dev.md +10 -7
  33. package/docs/guias/index.md +12 -0
  34. package/docs/guias/lead.md +1 -1
  35. package/docs/guias/po.md +13 -7
  36. package/docs/index.md +35 -0
  37. package/docs/public/favicon.svg +12 -0
  38. package/docs/public/logo-link.svg +12 -0
  39. package/docs/public/logo.svg +12 -0
  40. package/docs/public/tutoriales/clickup-1-settings.png +0 -0
  41. package/docs/public/tutoriales/clickup-2-api.png +0 -0
  42. package/docs/public/tutoriales/clickup-3-generate-copy.png +0 -0
  43. package/docs/public/tutoriales/jira-1-avatar.png +0 -0
  44. package/docs/public/tutoriales/jira-2-seguridad-tokens.png +0 -0
  45. package/docs/public/tutoriales/jira-3-crear-token.png +0 -0
  46. package/docs/public/tutoriales/jira-4-nombre-vencimiento.png +0 -0
  47. package/docs/public/tutoriales/jira-5-copiar.png +0 -0
  48. package/docs/tutoriales/claves-ssh.md +93 -0
  49. package/docs/tutoriales/configurar-git.md +53 -0
  50. package/docs/tutoriales/index.md +18 -0
  51. package/docs/tutoriales/instalar-glab.md +74 -0
  52. package/docs/tutoriales/token-clickup.md +73 -0
  53. package/docs/tutoriales/token-jira.md +74 -0
  54. package/package.json +9 -3
  55. package/skills/dai-review/SKILL.md +96 -42
@@ -15,7 +15,7 @@ const trim = (b) => String(b || "").replace(/\/+$/, "");
15
15
  // error de config más común: `dai init` deja DAI_JIRA_PROJECT vacío y quien lo completa
16
16
  // suele pegar la épica que tiene a mano. Jira responde un 400 que no lo explica.
17
17
  export function assertProjectKey(key) {
18
- if (!key) throw new Error("falta DAI_JIRA_PROJECT en el .env (la clave del proyecto donde crear el issue).");
18
+ if (!key) throw new Error("falta DAI_JIRA_PROJECT en el .env.dai (la clave del proyecto donde crear el issue).");
19
19
  const k = String(key).trim();
20
20
  const m = k.match(/^([A-Za-z][A-Za-z0-9_]*)-\d+$/);
21
21
  if (m) {
@@ -136,7 +136,7 @@ export function createHint(status, body = "") {
136
136
 
137
137
  export function jiraAdapter(env) {
138
138
  const base = env.DAI_JIRA_BASE_URL;
139
- if (!base) throw new Error("falta DAI_JIRA_BASE_URL en el .env (backend jira).");
139
+ if (!base) throw new Error("falta DAI_JIRA_BASE_URL en el .env.dai (backend jira).");
140
140
  return {
141
141
  kind: "jira",
142
142
  async fetchUS(id) {
@@ -0,0 +1,195 @@
1
+ // dai · hallazgos de un review inline: parseo, validación contra el diff, filtros y render.
2
+ // Puro y sin red (ADR-0002: lo mecánico en el CLI, el criterio en la skill).
3
+ //
4
+ // El contrato con la skill es un `review.json`. La skill lo escribe, el humano lo lee y
5
+ // lo edita, y el CLI lo valida y lo postea. Ese archivo ES la puerta humana: es
6
+ // diff-eable, editable a mano y auditable, y no ata el flujo a ningún asistente.
7
+
8
+ export const SEVERITIES = ["low", "medium", "high"];
9
+ const SEV = {
10
+ low: { emoji: "🔵", label: "Low" },
11
+ medium: { emoji: "🟡", label: "Medium" },
12
+ high: { emoji: "🔴", label: "High" },
13
+ };
14
+ const rank = (s) => SEVERITIES.indexOf(s);
15
+
16
+ // ── Parseo del review.json ───────────────────────────────────────────────────
17
+ // Errores accionables: un LLM se equivoca, y "Unexpected token" no le dice dónde.
18
+
19
+ function fail(msg) {
20
+ throw new Error(`review.json inválido: ${msg}`);
21
+ }
22
+
23
+ export function parseFindings(text) {
24
+ let j;
25
+ try {
26
+ j = typeof text === "string" ? JSON.parse(text) : text;
27
+ } catch (e) {
28
+ fail(`no es JSON válido (${e.message}).`);
29
+ }
30
+ if (!j || typeof j !== "object" || Array.isArray(j)) fail("la raíz tiene que ser un objeto.");
31
+ if (!Array.isArray(j.findings)) fail("falta el array 'findings' (puede ir vacío, pero tiene que estar).");
32
+
33
+ const findings = j.findings.map((f, i) => {
34
+ const at = `findings[${i}]`;
35
+ if (!f || typeof f !== "object") fail(`${at}: tiene que ser un objeto.`);
36
+ if (typeof f.path !== "string" || !f.path.trim()) fail(`${at}: falta 'path' (ruta del archivo, relativa a la raíz del repo).`);
37
+ if (!Number.isInteger(f.line) || f.line < 1) fail(`${at} (${f.path}): 'line' tiene que ser un entero ≥ 1, vino ${JSON.stringify(f.line)}.`);
38
+ if (typeof f.body !== "string" || !f.body.trim()) fail(`${at} (${f.path}:${f.line}): falta 'body' (el hallazgo, en prosa).`);
39
+ const side = f.side === undefined ? "RIGHT" : String(f.side).toUpperCase();
40
+ if (side !== "RIGHT" && side !== "LEFT") fail(`${at} (${f.path}:${f.line}): 'side' tiene que ser RIGHT o LEFT, vino ${JSON.stringify(f.side)}.`);
41
+ if (!SEVERITIES.includes(f.severity)) fail(`${at} (${f.path}:${f.line}): 'severity' tiene que ser ${SEVERITIES.join(" | ")}, vino ${JSON.stringify(f.severity)}.`);
42
+ const confidence = f.confidence === undefined ? 1 : f.confidence;
43
+ if (typeof confidence !== "number" || confidence < 0 || confidence > 1) {
44
+ fail(`${at} (${f.path}:${f.line}): 'confidence' tiene que ser un número entre 0 y 1, vino ${JSON.stringify(f.confidence)}.`);
45
+ }
46
+ return { path: f.path.trim(), line: f.line, side, severity: f.severity, confidence, body: f.body.trim() };
47
+ });
48
+
49
+ return {
50
+ us: j.us ?? null,
51
+ version: j.version ?? null,
52
+ checkStatus: j.checkStatus ?? null,
53
+ dod: j.dod ?? null,
54
+ summary: typeof j.summary === "string" ? j.summary.trim() : "",
55
+ good: Array.isArray(j.good) ? j.good.filter((g) => typeof g === "string" && g.trim()) : [],
56
+ findings,
57
+ };
58
+ }
59
+
60
+ // ── Posiciones válidas según el diff ─────────────────────────────────────────
61
+ // El anti-alucinación. Un LLM inventa números de línea con una facilidad pasmosa, y el
62
+ // forge responde 422 sin decir cuál falló. Como el diff ya lo tenemos local, se verifica
63
+ // acá antes de salir a la red.
64
+ //
65
+ // Devuelve: Map<path, { right: Set<line>, left: Set<line> }>. Las líneas de contexto
66
+ // cuentan de los dos lados: el forge acepta comentarlas, son parte del hunk.
67
+
68
+ export function diffPositions(diff) {
69
+ const files = new Map();
70
+ let cur = null, newLine = 0, oldLine = 0;
71
+
72
+ for (const raw of String(diff ?? "").split(/\r?\n/)) {
73
+ // Ojo el orden: '+++ ' y '--- ' empiezan con '+' y '-'. Van ANTES que las de contenido.
74
+ if (raw.startsWith("+++ ")) {
75
+ const p = raw.slice(4).trim().replace(/\t.*$/, "");
76
+ if (p === "/dev/null") { cur = null; continue; } // archivo borrado: no hay lado derecho
77
+ cur = { right: new Set(), left: new Set() };
78
+ files.set(stripDiffPrefix(p), cur);
79
+ continue;
80
+ }
81
+ if (raw.startsWith("--- ")) continue;
82
+ if (raw.startsWith("@@")) {
83
+ const m = raw.match(/^@@+ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
84
+ if (m) { oldLine = Number(m[1]); newLine = Number(m[2]); }
85
+ continue;
86
+ }
87
+ if (!cur) continue;
88
+ if (raw.startsWith("+")) cur.right.add(newLine++);
89
+ else if (raw.startsWith("-")) cur.left.add(oldLine++);
90
+ else if (raw.startsWith(" ")) { cur.right.add(newLine++); cur.left.add(oldLine++); }
91
+ // "", "index …", "diff --git …", "similarity …": se ignoran
92
+ }
93
+ return files;
94
+ }
95
+
96
+ // "b/src/x.ts" → "src/x.ts". git usa a//b/ como prefijos del diff.
97
+ function stripDiffPrefix(p) {
98
+ return p.replace(/^[ab]\//, "");
99
+ }
100
+
101
+ // ── Validación ───────────────────────────────────────────────────────────────
102
+ // Un hallazgo que no apunta al diff NO se postea. Se reporta, no se descarta en silencio.
103
+
104
+ export function validateFindings(findings, positions) {
105
+ const valid = [], rejected = [];
106
+ for (const f of findings) {
107
+ const pos = positions.get(f.path);
108
+ if (!pos) {
109
+ rejected.push({ finding: f, reason: "el archivo no aparece en el diff" });
110
+ continue;
111
+ }
112
+ const lines = f.side === "LEFT" ? pos.left : pos.right;
113
+ if (!lines.has(f.line)) {
114
+ rejected.push({ finding: f, reason: `la línea ${f.line} no es parte del diff (lado ${f.side})` });
115
+ continue;
116
+ }
117
+ valid.push(f);
118
+ }
119
+ return { valid, rejected };
120
+ }
121
+
122
+ // ── Filtros ──────────────────────────────────────────────────────────────────
123
+ // Lo que habilita el modo desatendido con red: por debajo del umbral, el hallazgo no se
124
+ // postea pero se lista en el resumen. Nada se cae en silencio.
125
+
126
+ export function filterFindings(findings, { minSeverity = "low", minConfidence = 0, maxComments = Infinity } = {}) {
127
+ if (!SEVERITIES.includes(minSeverity)) throw new Error(`--min-severity: valores ${SEVERITIES.join(" | ")}, vino '${minSeverity}'.`);
128
+ const kept = [], suppressed = [];
129
+ // Ordenado por severidad y después por confianza: si hay tope, se cortan los menos graves.
130
+ const sorted = [...findings].sort((a, b) => rank(b.severity) - rank(a.severity) || b.confidence - a.confidence);
131
+ for (const f of sorted) {
132
+ if (rank(f.severity) < rank(minSeverity)) { suppressed.push({ finding: f, reason: `severidad ${f.severity} < ${minSeverity}` }); continue; }
133
+ if (f.confidence < minConfidence) { suppressed.push({ finding: f, reason: `confianza ${f.confidence} < ${minConfidence}` }); continue; }
134
+ if (kept.length >= maxComments) { suppressed.push({ finding: f, reason: `tope de --max-comments (${maxComments})` }); continue; }
135
+ kept.push(f);
136
+ }
137
+ return { kept, suppressed };
138
+ }
139
+
140
+ // ── Render ───────────────────────────────────────────────────────────────────
141
+
142
+ // El cuerpo de un comentario inline. Lleva marca de dai a propósito: el comentario se
143
+ // postea con el token del humano, así que el forge lo atribuye a él SIN badge de bot. Sin
144
+ // esta línea, el compañero ve un juicio sobre su código firmado por una persona, sin
145
+ // forma de saber que lo escribió una máquina.
146
+ export function renderFindingBody(f) {
147
+ const { emoji, label } = SEV[f.severity];
148
+ return `${emoji} **${label}** — ${f.body}\n\n<sub>🤖 <code>dai-review</code> · revisión asistida por IA, revisada y firmada por un humano</sub>`;
149
+ }
150
+
151
+ const bullets = (arr) => (arr.length ? arr.map((x) => `- ${x}`).join("\n") : "- — ninguno —");
152
+
153
+ function detailsList(title, items) {
154
+ if (!items.length) return null;
155
+ const rows = items.map(({ finding: f, reason }) => `- \`${f.path}:${f.line}\` (${f.severity}) — ${reason}\n > ${f.body.split("\n")[0]}`);
156
+ return `<details>\n<summary>${title} (${items.length})</summary>\n\n${rows.join("\n")}\n\n</details>`;
157
+ }
158
+
159
+ // El cuerpo del review (el "Pull request overview"). Mismo encabezado de metodología que
160
+ // renderReviewComment — los reviews del equipo se siguen leyendo igual.
161
+ export function renderReviewSummary(r, { kept = [], suppressed = [], rejected = [] } = {}) {
162
+ const head = r.us
163
+ ? `**US:** \`${r.us}\`${r.version ? ` @ ${r.version}` : ""} · \`dai check\`: ${r.checkStatus || "?"}`
164
+ : "**US:** _(este PR no declara implementar una US)_";
165
+
166
+ const counts = SEVERITIES.slice().reverse()
167
+ .map((s) => ({ s, n: kept.filter((f) => f.severity === s).length }))
168
+ .filter(({ n }) => n > 0)
169
+ .map(({ s, n }) => `${n} ${SEV[s].emoji} ${SEV[s].label}`);
170
+
171
+ const hallazgos = kept.length
172
+ ? `Dejé **${kept.length}** ${kept.length === 1 ? "comentario" : "comentarios"} en línea: ${counts.join(" · ")}.`
173
+ : "Sin comentarios en línea: no encontré nada concreto que marcar.";
174
+
175
+ return [
176
+ "## 🤖 dai-review",
177
+ "",
178
+ head,
179
+ r.dod ? `**Definition of Done:** ${r.dod}` : null,
180
+ "",
181
+ r.summary || null,
182
+ r.summary ? "" : null,
183
+ "### Hallazgos",
184
+ hallazgos,
185
+ "",
186
+ "### ✅ Lo que está bien",
187
+ bullets(r.good),
188
+ "",
189
+ detailsList("Suprimidos por el filtro", suppressed),
190
+ detailsList("Descartados: no apuntan al diff", rejected),
191
+ "",
192
+ "---",
193
+ "_Revisión asistida por dai. La aprobación la firma un humano (Art. 5 del manifiesto)._",
194
+ ].filter((l) => l !== null).join("\n").replace(/\n{3,}/g, "\n\n") + "\n";
195
+ }
@@ -15,6 +15,14 @@ export function parseSource(src) {
15
15
  const hash = raw.lastIndexOf("#");
16
16
  if (hash > 0) { ref = raw.slice(hash + 1) || null; loc = raw.slice(0, hash); }
17
17
 
18
+ // Paquete npm: `npm:@scope/pkg[@version]`. dai hace `npm pack` a un temp, respetando el
19
+ // `.npmrc` del repo (así resuelve registries privados con scope). La versión va en el
20
+ // propio spec (`@1.2.3`), no como ref con '#'.
21
+ if (loc.startsWith("npm:")) {
22
+ const spec = loc.slice(4).trim();
23
+ if (!spec) throw new Error("fuente npm vacía (usá npm:@scope/paquete)");
24
+ return { type: "npm", location: spec, ref: null };
25
+ }
18
26
  // Path local explícito (./ ../ / ~/).
19
27
  if (/^(\.\.?\/|\/|~\/)/.test(loc)) return { type: "path", location: loc, ref };
20
28
  // git por sintaxis de URL (https, ssh, scp git@host:…).
@@ -12,10 +12,10 @@
12
12
 
13
13
  | Paso | Skill / evento | Artefacto que produce |
14
14
  |---|---|---|
15
- | 1 Refinamiento | `grill-intent` → `grill-user-story` | `intent.md` + la **US** (`ABC-482`) |
15
+ | 1 Refinamiento | `grill-user-story` → `grill-intent` (Gate 0) | la **US** (`ABC-482`) + `intent.md` |
16
16
  | 2 Planning | `opsx:propose` | `proposal.md` + `design.md` + `tasks.md` + `specs/` |
17
17
  | 3 Rama | `link-us` | branch + `implements.yaml` |
18
- | 4 TDD | `tdd` | test (RED) → código (GREEN) |
18
+ | 4 Implementación | `opsx:apply` (con TDD) | test (RED) → código (GREEN), por el agente |
19
19
  | 5 Smoke | skill de smoke | escenario end-to-end verde |
20
20
  | 6 Code review | review propio del dev → `dai pr` → partner (con `/dai-review`) | PR (código + spec) + aprobación |
21
21
  | 7 Merge | `dai stamp` | cobertura estampada en el tracker |
@@ -37,34 +37,9 @@ ABC-482 · "Arreglar lo del carrito que a veces deja comprar de más"
37
37
 
38
38
  Impresentable para implementar. Acá entra la IA.
39
39
 
40
- ### 1b · Gate 0 — `grill-intent` desafía el problema
40
+ ### 1b · `grill-user-story` produce la US
41
41
 
42
- La skill no acepta el ticket: interroga el *problema* detrás (contra el
43
- [`MANIFIESTO.md`](MANIFIESTO.md)).
44
-
45
- ```
46
- IA: ¿Qué duele HOY, concretamente? "Comprar de más" es un síntoma.
47
- PO: Un cliente finalizó la compra de un producto que ya no tenía stock, y quedó
48
- una orden que no podemos cumplir. También pasa que finalizan con el carrito
49
- vacío y se genera una orden fantasma.
50
- IA: ¿Quién siente el dolor? "El sistema" no es un usuario.
51
- PO: El comprador (recibe un error después de comprar) y depósito, que recibe
52
- órdenes que no puede despachar.
53
- IA: ¿Y si no lo hacemos? (costo de no actuar)
54
- PO: Órdenes que se cancelan a mano, clientes enojados. Pasó bastante el último mes.
55
- IA: La solución implícita es "validar el carrito al finalizar". ¿Es el salto correcto,
56
- o el problema real es que el stock que muestra el carrito no es confiable?
57
- PO: No, validar al finalizar es lo correcto: el stock ya existe, falta chequearlo
58
- en el momento de la compra.
59
-
60
- Veredicto: a-spec ✅ (problema real, usuario concreto, costo claro, sin solution-lock)
61
- ```
62
-
63
- → produce `openspec/intents/20260703-checkout-invalido/intent.md` con ese veredicto.
64
-
65
- ### 1c · `grill-user-story` produce la US
66
-
67
- Con el problema validado, la skill interroga hasta que la US es **testeable por
42
+ Acá entra la IA: `grill-user-story` interroga al PO hasta que la US es **testeable por
68
43
  construcción** ([`formato-us.md`](../templates/formato-us.md)) y la publica en el gestor:
69
44
 
70
45
  ```markdown
@@ -113,6 +88,33 @@ para **recibir los productos que elijo sin sorpresas**.
113
88
  > Nota que la US **no dice** tablas, endpoints ni framework — solo el QUÉ. Y cada
114
89
  > AC es un test en potencia ([Art. 3](./MANIFIESTO.md#art-3) del manifiesto).
115
90
 
91
+ ### 1c · Gate 0 — `grill-intent` desafía el problema
92
+
93
+ Con la US ya formada, **antes** de invertir en el spec, el Gate 0 cuestiona el *problema*
94
+ detrás (contra el [`MANIFIESTO.md`](MANIFIESTO.md)):
95
+
96
+ ```
97
+ IA: ¿Qué duele HOY, concretamente? "Comprar de más" es un síntoma.
98
+ PO: Un cliente finalizó la compra de un producto que ya no tenía stock, y quedó
99
+ una orden que no podemos cumplir. También pasa que finalizan con el carrito
100
+ vacío y se genera una orden fantasma.
101
+ IA: ¿Quién siente el dolor? "El sistema" no es un usuario.
102
+ PO: El comprador (recibe un error después de comprar) y depósito, que recibe
103
+ órdenes que no puede despachar.
104
+ IA: ¿Y si no lo hacemos? (costo de no actuar)
105
+ PO: Órdenes que se cancelan a mano, clientes enojados. Pasó bastante el último mes.
106
+ IA: La solución implícita es "validar el carrito al finalizar". ¿Es el salto correcto,
107
+ o el problema real es que el stock que muestra el carrito no es confiable?
108
+ PO: No, validar al finalizar es lo correcto: el stock ya existe, falta chequearlo
109
+ en el momento de la compra.
110
+
111
+ Veredicto: a-spec ✅ (problema real, usuario concreto, costo claro, sin solution-lock)
112
+ ```
113
+
114
+ → produce `openspec/intents/20260703-checkout-invalido/intent.md` con ese veredicto. Si
115
+ hubiera dado *reframe* o *don't build*, la US volvería al PO **antes** de gastar un solo
116
+ artefacto de spec — ese es el punto del Gate 0.
117
+
116
118
  ---
117
119
 
118
120
  ## Paso 2 — Planning: `opsx:propose` deriva el CÓMO
@@ -175,9 +177,10 @@ autor: D. Force (dev)
175
177
 
176
178
  ---
177
179
 
178
- ## Paso 4 — TDD: un test a la vez (RED → GREEN)
180
+ ## Paso 4 — Implementación: el agente construye con TDD (`/opsx:apply`)
179
181
 
180
- Vertical slice del AC-2 (el guard del carrito vacío). **Primero el test (RED):**
182
+ `/opsx:apply` implementa las tareas del change, un test a la vez. Vertical slice del AC-2
183
+ (el guard del carrito vacío). **Primero el test (RED):**
181
184
 
182
185
  ```typescript
183
186
  test("un carrito vacío no se puede finalizar", async () => {
@@ -205,8 +208,9 @@ export async function finalizarCompra(id: CarritoId) {
205
208
  // ▶ VERDE. Repetir el ciclo para AC-1 y AC-3.
206
209
  ```
207
210
 
208
- > El test verifica por la **interfaz pública** (`finalizarCompra`, `ordenesDe`), no
209
- > espía lo interno. Sobrevive a un refactor (Art. 7 + skill `tdd`).
211
+ > El test (que escribió el agente) verifica por la **interfaz pública**
212
+ > (`finalizarCompra`, `ordenesDe`), no espía lo interno. Sobrevive a un refactor
213
+ > (Art. 7 + skill `tdd`). El dev revisa cada slice: es responsable del código.
210
214
 
211
215
  ---
212
216
 
@@ -239,21 +243,26 @@ $ dai pr --assignee mgomez
239
243
  ✓ PR #123 creada → …/pull/123 (base: main · US: ABC-482 @ v1 · dai check ✅)
240
244
  ```
241
245
 
242
- El **partner** revisa la PR. Se apoya en la skill `/dai-review` para un primer pase con
243
- comentario estándar, y **firma** aprobación o rechazo:
246
+ El **partner** revisa la PR. Se apoya en la skill `/dai-review` para un primer pase: deja
247
+ un **review inline** (resumen + un comentario anclado por línea, low/medium/high). La skill
248
+ le muestra el preview y **espera su OK antes de postear**; después el partner **firma**
249
+ aprobación o rechazo:
244
250
 
245
251
  ```
246
- 🤖 /dai-review (primer pase, ayuda al partner)
247
- · AC-2 cubierto y verificado (no se crea orden con carrito vacío).
248
- · Sugerencia: CarritoVacioError y SinStockError deberían extender un DomainError
249
- común, como el resto del módulo.
250
- · Sin problemas de correctitud.
252
+ 🤖 /dai-review resumen
253
+ US: ABC-482 @ v1 · dai check: al día · DoD: 5/5
254
+ 1 comentario en línea: 1 🔵 Low.
255
+
256
+ 🔵 Low src/checkout/errors.ts:12
257
+ CarritoVacioError y SinStockError podrían extender un DomainError común,
258
+ como el resto del módulo.
251
259
 
252
- 👤 M. Gómez (partner): de acuerdo con el DomainError. Aprobado tras el ajuste.
260
+ 👤 M. Gómez (partner): reviso el preview, lo posteo, y apruebo tras el ajuste.
253
261
  ```
254
262
 
255
263
  > El dev revisa su propio código; un partner distinto revisa la PR y **firma** (Art. 5).
256
- > La skill `/dai-review` le saca el ruido al partner, pero la aprobación la firma la persona.
264
+ > El review sale con el nombre y el token del partner (nunca `APPROVE` automático): la
265
+ > skill asiste, la persona firma.
257
266
 
258
267
  ---
259
268
 
@@ -282,7 +291,7 @@ ABC-482 · implementado por (lo estampó dai stamp)
282
291
  ## Paso 8 — Daily *(humano, a propósito)*
283
292
 
284
293
  > *"Ayer cerré ABC-482, la validación del checkout con el guard de carrito. Hoy
285
- > agarro ABC-490. Sin trabas."* — La IA no genera esto; el equipo se sincroniza (Art. 6).
294
+ > tomo ABC-490. Sin trabas."* — La IA no genera esto; el equipo se sincroniza (Art. 6).
286
295
 
287
296
  ## Paso 9 — Review / Demo
288
297
 
@@ -3,7 +3,7 @@
3
3
  > **Qué es esto.** La *constitución* de la metodología: los principios inmutables
4
4
  > contra los que se mide toda decisión. Cuando `grill-intent` desafía un problema,
5
5
  > lo hace contra estos artículos. Cuando dudas si algo "va con el método", la
6
- > respuesta está acá. Es corto a propósito — un manifiesto que no se puede citar de
6
+ > respuesta está aquí. Es corto a propósito — un manifiesto que no se puede citar de
7
7
  > memoria no gobierna nada.
8
8
  >
9
9
  > **Cómo se usa.** Es el input de constitución que las skills asumen (junto con
@@ -56,7 +56,7 @@ El *daily* y la *retro* se hacen a mano, a propósito. Son donde el equipo se
56
56
  apropia del proceso y lo entiende. La IA puede darles datos; no los reemplaza.
57
57
 
58
58
  <a id="art-7"></a>**Art. 7 — No vibe coding.**
59
- No se improvisa código sobre una idea vaga. Toda implementación arranca de una US
59
+ No se improvisa código sobre una idea vaga. Toda implementación parte de una US
60
60
  bien definida, pasa por un design, y se construye con tests. La disciplina no es
61
61
  opcional: es lo que separa este método de "pedirle cosas a un chat".
62
62
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **Fuente de verdad única.** Este documento es el maestro. Los dos HTML
4
4
  > (`desarrollo-asistido-por-ia.html` federado y `-equipos-compactos.html`) son
5
- > **vistas de presentación** derivadas de acá — si algo contradice a este `.md`,
5
+ > **vistas de presentación** derivadas de aquí — si algo contradice a este `.md`,
6
6
  > gana este `.md`. Versionado por git para que no driftee.
7
7
 
8
8
  Una sola metodología para dos escalas opuestas:
@@ -57,9 +57,9 @@ identidad el día que se crea el ticket/change.
57
57
  ### 2.2 Formato linkeable garantizado
58
58
 
59
59
  El QUÉ se produce con una forma mínima **testeable**: `id · spec_version · autor ·
60
- criterios de aceptación en Gherkin`. Es exactamente lo que aseguran las skills
61
- `grill-intent` → `grill-user-story` (ver `formato-us.md`). Sin esa forma, no hay
62
- a qué linkear.
60
+ criterios de aceptación en Gherkin`. Es exactamente lo que asegura la skill
61
+ `grill-user-story` (y después el Gate 0 de `grill-intent` desafía el problema; ver
62
+ `formato-us.md`). Sin esa forma, no hay a qué linkear.
63
63
 
64
64
  ### 2.3 El link se autora una sola vez, del lado del CÓMO
65
65
 
@@ -122,8 +122,10 @@ federación.**
122
122
 
123
123
  El CÓMO se construye con **test primero**, en *vertical slices* (un test → una
124
124
  implementación → repetir), verificando por la **interfaz pública**, no espiando lo
125
- interno. Un buen test lee como una spec y sobrevive a un refactor. Ver
126
- `skills/tdd/`.
125
+ interno. Un buen test lee como una spec y sobrevive a un refactor. **Quien lo ejecuta
126
+ es el agente**, dentro de `/opsx:apply` (la disciplina la encapsula la skill `tdd`); el
127
+ dev decide qué comportamientos importa testear y es responsable de revisar el resultado
128
+ ([Art. 7](MANIFIESTO.md#art-7)). Ver `skills/tdd/`.
127
129
 
128
130
  ---
129
131
 
@@ -134,7 +136,7 @@ duele, no antes.**
134
136
 
135
137
  | | **N1 · Solo / 1 repo** | **N2 · Equipo compacto** | **N3 · Federado** |
136
138
  |---|---|---|---|
137
- | Caso típico | equipo chico arrancando, un dev | equipo chico | organización grande |
139
+ | Caso típico | equipo chico empezando, un dev | equipo chico | organización grande |
138
140
  | El QUÉ vive en | `proposal.md` de OpenSpec | ClickUp (US) → el change la referencia | Jira (`ABC-###`, hub) |
139
141
  | El link vive en | la carpeta del change (co-localizado) | `implements.yaml` en el repo | `implements.yaml` versionado |
140
142
  | Inversa la genera | un comando local | comando / CI liviano | CI estampa cobertura + CD reporta ambiente |
@@ -178,13 +180,13 @@ El artefacto no desaparece; se aligera la ceremonia alrededor.
178
180
 
179
181
  ```
180
182
  ① Nace la idea → ticket vago en el PM (o intención suelta en N1)
181
- Gate 0: ¿problema OK? → /grill-intent → veredicto: a-spec / reframe / descartar
182
- ③ Se pule el QUÉ → /grill-user-story → US testeable (formato-us.md),
183
+ Se pule el QUÉ → /grill-user-story US testeable (formato-us.md),
183
184
  publicada en Jira/ClickUp (o .md si no hay MCP)
185
+ ③ Gate 0: ¿problema OK? → /grill-intent → veredicto: a-spec / reframe / descartar
184
186
  ④ Se abre el CÓMO → /link-us ABC-### → branch + implements.yaml ligados al ID
185
187
  ⑤ Se arma el change → opsx:explore → opsx:propose (proposal/design/tasks + specs)
186
- ⑥ Se implementa → TDD (red green refactor), vertical slices
187
- ⑦ Se promueve → opsx:apply → opsx:archive; el CI estampa cobertura en el PM
188
+ ⑥ Se implementa → opsx:apply → el agente aplica las tareas con TDD (red→green→refactor)
189
+ ⑦ Se promueve → opsx:archive; el CI estampa cobertura en el PM
188
190
  ⑧ Se despliega → el CD reporta a qué ambiente (dev/test/pre/prod) fue la versión
189
191
  ```
190
192
 
@@ -203,11 +205,14 @@ El artefacto no desaparece; se aligera la ceremonia alrededor.
203
205
 
204
206
  | Skill | Lado | Qué hace |
205
207
  |---|---|---|
206
- | `grill-intent` | QUÉ | Gate 0: desafía el *problema* antes de escribir spec. Veredicto: a-spec / reframe / descartar. |
208
+ | `doc-to-backlog` | QUÉ | Un documento (PDF/Word) backlog candidato de épicas + US para priorizar. |
209
+ | `grill-epic` | QUÉ | Algo grande → una épica partida en varias US. |
207
210
  | `grill-user-story` | QUÉ | Interroga hasta producir una US testeable (INVEST + Gherkin). Publica en Jira/ClickUp o deja `.md`. |
211
+ | `grill-intent` | QUÉ | Gate 0: con la US ya formada, desafía el *problema* antes de escribir spec. Veredicto: a-spec / reframe / descartar. |
208
212
  | `link-us` | CÓMO | Crea branch + `implements.yaml` desde el ID del PM. El link, correcto por construcción. |
209
- | `tdd` | CÓMO | Red-green-refactor en vertical slices, tests por interfaz pública. |
210
- | `opsx:*` | CÓMO | OpenSpec: explore propose apply archive. Lo provee OpenSpec. |
213
+ | `opsx:*` | CÓMO | OpenSpec: explore propose apply (el agente implementa con TDD) → archive. Lo provee OpenSpec. |
214
+ | `tdd` | CÓMO | La disciplina red-green-refactor (vertical slices, tests por interfaz pública) que aplica `opsx:apply`. |
215
+ | `dai-review` | CÓMO | Review inline de una PR/MR ajena: resumen + un comentario por línea, con gate humano de OK antes de postear. |
211
216
 
212
217
  El **adaptador de PM** es un seam único: las skills del QUÉ publican en Jira **o**
213
218
  ClickUp **o** dejan un `.md` según qué MCP/token haya. Es la **misma** skill con
@@ -228,7 +233,7 @@ de N3 queda **una** decisión sin resolver; el resto se cerró al construir la h
228
233
 
229
234
  **Ya resueltas** (al construir dai):
230
235
  - **Adaptador de PM configurable** → `getAdapter` (backends `md`/`jira`/`clickup`, token del
231
- `.env`); modelo de auth en [ADR-0007](adr/0007-modelo-de-autenticacion.md).
236
+ `.env.dai` no versionado — [ADR-0017](adr/0017-env-dai.md)); modelo de auth en [ADR-0007](adr/0007-modelo-de-autenticacion.md).
232
237
  - **Formato de `implements.yaml`** → congelado en [ADR-0004](adr/0004-ubicacion-y-schema-implements.md)
233
238
  (schema + ubicación + descubrimiento por glob).
234
239
  - **Escritura multi-repo en el tracker** → `dai stamp` deja un **comentario por repo** (no se
package/docs/PROBAR.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Probar dai
2
2
 
3
- No hace falta publicar en npm para probarlo. Se prueba local. Recomendado: hacer la
4
- **Fase 0** (backend `md`, sin credenciales) para validar el flujo, y después la
5
- **Fase 1** (ClickUp real).
3
+ La forma más rápida de conocer dai: instala el CLI y corre el ciclo completo en tu
4
+ máquina. Empieza **sin credenciales** (backend `md`) para ver el flujo entero en un par de
5
+ minutos, y después pruébalo **contra tu tracker real** (ClickUp o Jira).
6
6
 
7
7
  > Esto es la guía para **usar** dai por primera vez. Para verlo funcionando sobre una
8
8
  > US real (narrado), mira [`EJEMPLO-END-TO-END.md`](EJEMPLO-END-TO-END.md).
@@ -10,19 +10,17 @@ No hace falta publicar en npm para probarlo. Se prueba local. Recomendado: hacer
10
10
  ## Instalar el CLI
11
11
 
12
12
  ```bash
13
- npm i -g @dforce2055/dai # desde npm
14
- # — o, si clonaste el repo —
15
- git clone https://github.com/dforce2055/dai && cd dai && npm link
16
-
13
+ npm i -g @dforce2055/dai
17
14
  dai --version
18
15
  ```
19
16
 
20
- ## Fase 0 — sin credenciales (backend `md`)
17
+ ## Paso 1 — sin credenciales (backend `md`)
21
18
 
22
- Valida el loop completo `link-us → check → stamp` sin depender de red ni tokens.
19
+ Recorre el loop completo `link-us → check → stamp` sin red ni tokens: todo local. Perfecto
20
+ para ver cómo funciona antes de conectar nada.
23
21
 
24
22
  ```bash
25
- # 1. Repo de prueba + bootstrap (dai init deja el .env; elige "md" cuando pregunte)
23
+ # 1. Repo de prueba + bootstrap (dai init deja el .env.dai; elige "md" cuando pregunte)
26
24
  mkdir /tmp/dai-test && cd /tmp/dai-test
27
25
  git init && git commit --allow-empty -m init
28
26
  git remote add origin git@github.com:TU-USUARIO/dai-test.git # para los links de branch/commit
@@ -48,9 +46,9 @@ dai check # ⚠️ ATRASADO (exit 1) → sugiere: dai li
48
46
  dai stamp # con md, deja .dai/us/<slug>.coverage.md
49
47
  ```
50
48
 
51
- Si esto anda, el flujo está bien. Pasa al tracker real.
49
+ Con esto ya viste el ciclo entero. Ahora conéctalo a tu tracker real.
52
50
 
53
- ## Fase 1ClickUp real
51
+ ## Paso 2contra tu tracker real (ClickUp)
54
52
 
55
53
  **Preparar ClickUp:**
56
54
 
@@ -62,7 +60,7 @@ Si esto anda, el flujo está bien. Pasa al tracker real.
62
60
  **Config y flujo:**
63
61
 
64
62
  ```bash
65
- cat > .env <<'EOF'
63
+ cat > .env.dai <<'EOF'
66
64
  DAI_PM=clickup
67
65
  DAI_CLICKUP_TOKEN=pk_XXXXXXXX
68
66
  EOF
@@ -79,7 +77,8 @@ dai check # ⚠️ ATRASADO (lo detectó solo)
79
77
  dai stamp # deja un COMENTARIO en la tarea con la cobertura
80
78
  ```
81
79
 
82
- > El `.env` está gitignored: el token no se commitea (ADR-0007).
80
+ > El `.env.dai` **no se versiona**: el token no se commitea, y el `.env` del equipo no se
81
+ > toca ([ADR-0017](adr/0017-env-dai.md); modelo de auth en [ADR-0007](adr/0007-modelo-de-autenticacion.md)).
83
82
 
84
83
  ## Troubleshooting
85
84
 
@@ -49,9 +49,9 @@
49
49
  |---|---|
50
50
  | **Clásico** | El PO parte épicas en Historias de Usuario y les pone criterios de aceptación. |
51
51
  | **Dolor** | US vagas, no testeables ("el usuario quiere un botón"). El malentendido se descubre tarde, ya implementado. |
52
- | **Con IA** | `grill-intent` (Gate 0: ¿es el problema correcto? veredicto a-spec / reframe / descartar) y luego `grill-user-story` **interrogan** al PO hasta que la US es testeable por construcción (INVEST + Gherkin). La IA no *escribe* la US: la *saca a preguntas*. La US nace con ID estable y criterios hasheables. |
52
+ | **Con IA** | `grill-user-story` **interroga** al PO hasta que la US es testeable por construcción (INVEST + Gherkin); y después el Gate 0 de `grill-intent` desafía el *problema* detrás (veredicto: a-spec / reframe / descartar). La IA no *escribe* la US: la *saca a preguntas*. La US nace con ID estable y criterios hasheables. |
53
53
  | **Humano (HITL)** | El PO responde y decide. La IA no inventa requerimientos: los pule. |
54
- | **Herramienta** | `grill-intent`, `grill-user-story`, `formato-us.md` |
54
+ | **Herramienta** | `grill-user-story`, `grill-intent`, `formato-us.md` |
55
55
  | **Detalle →** | [`detalle/01-refinamiento.md`](detalle/01-refinamiento.md) |
56
56
 
57
57
  #### 2. Sprint Planning: comprometer US y derivar tareas
@@ -76,19 +76,19 @@
76
76
  | **Clásico** | El dev crea una rama para trabajar la US. |
77
77
  | **Dolor** | Nombres inconsistentes, ramas que no se sabe a qué US pertenecen → trazabilidad rota desde el commit uno. |
78
78
  | **Con IA** | `link-us ABC-###` crea la rama **desde el ID de la US, sin tipearlo a mano**, y genera el `implements.yaml`. La rama *es* el link: correcto por construcción. |
79
- | **Humano (HITL)** | El dev elige qué US agarra. |
79
+ | **Humano (HITL)** | El dev elige qué US toma. |
80
80
  | **Herramienta** | `link-us` |
81
81
  | **Detalle →** | [`detalle/03-ramas.md`](detalle/03-ramas.md) |
82
82
 
83
- #### 4. Implementación con TDD
83
+ #### 4. Implementación (`/opsx:apply`, con TDD)
84
84
 
85
85
  | | |
86
86
  |---|---|
87
87
  | **Clásico** | El dev codea. Idealmente con tests. |
88
88
  | **Dolor** | Se codea primero y se testea "si queda tiempo" (nunca queda). Vibe coding. |
89
- | **Con IA** | La skill `tdd` fuerza test→código en *vertical slices* (un test → una implementación → repetir). La IA escribe el test como spec ejecutable **antes** del código, verificando por la interfaz pública. Anti vibe-coding real. |
90
- | **Humano (HITL)** | El dev decide qué comportamientos importa testear y revisa cada slice. |
91
- | **Herramienta** | `tdd` |
89
+ | **Con IA** | El **agente implementa** con `/opsx:apply`: aplica las tareas de `opsx:propose` en *vertical slices* (un test → el código mínimo → repetir), escribiendo el test como spec ejecutable **antes** del código y verificando por la interfaz pública (la disciplina TDD la encapsula la skill `tdd`). Anti vibe-coding real. |
90
+ | **Humano (HITL)** | El dev decide qué comportamientos importa testear, valida cada slice y **es responsable del código** (no la IA) — lo revisa en el paso 6. |
91
+ | **Herramienta** | `opsx:apply` (con la disciplina de `tdd`) |
92
92
  | **Detalle →** | [`detalle/04-tdd.md`](detalle/04-tdd.md) |
93
93
 
94
94
  #### 5. Smoke test de la US
@@ -108,9 +108,9 @@
108
108
  |---|---|
109
109
  | **Clásico** | Un compañero revisa el PR/MR antes de mergear. |
110
110
  | **Dolor** | Depende de que el partner tenga tiempo y ganas; reviews superficiales que dejan pasar lo importante. |
111
- | **Con IA** | La IA hace el **primer pase** (correctitud + estándares del repo) antes del humano. El partner revisa lo que importa, no el ruido. |
111
+ | **Con IA** | La IA hace el **primer pase**: un **review inline** (corre `dai check` + valida el DoD, y deja un resumen + **un comentario por línea**, low/medium/high). Muestra el preview y **espera el OK del partner antes de postear**; el partner revisa lo que importa, no el ruido. |
112
112
  | **Humano (HITL)** | El partner aprueba o rechaza. La IA sugiere; la persona decide y firma. |
113
- | **Herramienta** | `dai-review` |
113
+ | **Herramienta** | `dai-review` (`dai forge review`) |
114
114
  | **Detalle →** | [`detalle/06-code-review.md`](detalle/06-code-review.md) |
115
115
 
116
116
  #### 7. Merge + trazabilidad automática
@@ -170,7 +170,7 @@
170
170
  | 1 Refinamiento | ●●● | La US testeable es la base de todo. |
171
171
  | 2 Planning | ●● | El design y las tareas se derivan de la US. |
172
172
  | 3 Rama | ●●● | El link correcto por construcción. |
173
- | 4 TDD | ●●● | El corazón del anti vibe-coding. |
173
+ | 4 Implementación | ●●● | El agente construye con TDD; el corazón del anti vibe-coding. |
174
174
  | 5 Smoke | ●● | Cierre verificable de la US. |
175
175
  | 6 Code review | ●● | Primer pase automático, humano decide. |
176
176
  | 7 Merge + trazabilidad | ●●● | La matriz se deriva sola. |
@@ -49,7 +49,7 @@ Pasar de distribuido a automático es mover la invocación, no reescribir nada.
49
49
 
50
50
  ## Consecuencias
51
51
 
52
- - ✅ **No hace falta ningún CI para empezar.** La metodología arranca con un dev
52
+ - ✅ **No hace falta ningún CI para empezar.** La metodología empieza con un dev
53
53
  tipeando `dai check` / `dai stamp`. El [Art. 14](../MANIFIESTO.md#art-14) queda respetado.
54
54
  - ✅ **Rampa de adopción continua:** manual → git-hook → CI, siempre el mismo comando.
55
55
  - ✅ **Honra el [Art. 10](../MANIFIESTO.md#art-10):** el humano *dispara* la derivación; no *escribe a mano* el
@@ -20,7 +20,7 @@ más fuerte de eso es el **copyleft**: quien distribuya un `dai` modificado debe
20
20
  publicar sus cambios bajo la misma licencia. La GPL convierte "las mejoras vuelven"
21
21
  en cláusula, no en deseo.
22
22
 
23
- El downside típico de la GPL **no aplica** acá: `dai` es un **CLI que se ejecuta**,
23
+ El downside típico de la GPL **no aplica** aquí: `dai` es un **CLI que se ejecuta**,
24
24
  no una librería que se embebe. Usar `dai` sobre tu código —aunque sea propietario y
25
25
  comercial— **no genera ninguna obligación**; el copyleft solo se activa si alguien
26
26
  **forkea y redistribuye** una versión modificada. Y *libre ≠ gratis*: se puede