@dforce2055/dai 0.8.1 → 0.8.2
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.example +5 -2
- package/CHANGELOG.md +42 -0
- package/VERSION +1 -1
- package/cli/dai.mjs +12 -6
- package/cli/lib/bootstrap.mjs +7 -3
- package/cli/lib/pm-adapter.mjs +4 -1
- package/cli/lib/pm-clickup.mjs +3 -1
- package/cli/lib/pm-jira.mjs +1 -1
- package/cli/lib/pr.mjs +36 -7
- package/cli/lib/tracker-url.mjs +36 -0
- package/docs/PROBAR.md +2 -1
- package/package.json +1 -1
- package/skills/dai-review/SKILL.md +16 -4
- package/templates/pull-request.md +5 -1
package/.env.example
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
# md | jira | clickup
|
|
7
7
|
DAI_PM=md
|
|
8
8
|
|
|
9
|
-
# Plantilla del link al ticket (
|
|
10
|
-
|
|
9
|
+
# OPCIONAL. Plantilla del link al ticket (`dai ls`, `dai pr`, stamp). {id} se reemplaza.
|
|
10
|
+
# Con DAI_PM=jira o =clickup, dai ya deduce el link solo: esto es un OVERRIDE, y solo
|
|
11
|
+
# hace falta si tu tracker vive en otra URL (p. ej. un Jira Server con path propio).
|
|
12
|
+
# Si dai no puede saber el link, avisa y deja la PR sin él — nunca escribe el id pelado.
|
|
13
|
+
# DAI_TRACKER_URL_TEMPLATE=https://jira.miempresa.com/browse/{id}
|
|
11
14
|
|
|
12
15
|
# ── Backend md (local, offline) ──────────────────────────────────────────────
|
|
13
16
|
# Carpeta donde viven las US como <ID>.md (p. ej. ABC-482.md).
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,46 @@
|
|
|
3
3
|
Formato basado en [Keep a Changelog](https://keepachangelog.com/). Versionado semver
|
|
4
4
|
(ver `VERSION`).
|
|
5
5
|
|
|
6
|
+
## [0.8.2] — 2026-07-17
|
|
7
|
+
|
|
8
|
+
**Dos agujeros que destapó el uso real, y que tienen la misma forma: dai hacía algo
|
|
9
|
+
hacia afuera sin que un humano lo viera, o dejaba que otro le pisara lo que había
|
|
10
|
+
escrito. El [Art. 5](docs/MANIFIESTO.md#art-5) no se cumple solo con no clickear
|
|
11
|
+
Approve.**
|
|
12
|
+
|
|
13
|
+
### Arreglado
|
|
14
|
+
- **`dai-review` posteaba el comentario sin mostrártelo.** La skill componía el review y
|
|
15
|
+
lo publicaba de una: el paso 6 decía *"Postear"* y no había gate. Y el comentario sale
|
|
16
|
+
con **tu token y tu nombre** (`GITHUB_TOKEN`/`GITLAB_TOKEN` son tuyos), así que en la
|
|
17
|
+
PR de un compañero figura como si lo hubieras escrito vos. El corte estaba puesto en el
|
|
18
|
+
lugar equivocado: no aprobar sin humano estaba bien, pero publicar un juicio sobre el
|
|
19
|
+
código de otro, firmado por alguien que no lo leyó, es el mismo problema con otro
|
|
20
|
+
disfraz. Ahora la skill **muestra el comentario entero y espera un OK explícito** en
|
|
21
|
+
ese turno; sin "sí", no se postea. Es el tercer corte duro de la skill.
|
|
22
|
+
- **`dai pr` escribía el id de la US disfrazado de link.** Sin `DAI_TRACKER_URL_TEMPLATE`,
|
|
23
|
+
`trackerUrl(id)` devolvía el **id pelado**; como un string es truthy, `composePrBody` lo
|
|
24
|
+
escribía igual y la PR quedaba con `- US: 86abc123` en vez de un enlace, sin un solo
|
|
25
|
+
aviso. Ahora la URL se resuelve por una cadena explícita —template > URL canónica del
|
|
26
|
+
tracker > derivada del backend > `null`— y **si dai no la sabe, avisa y omite la línea
|
|
27
|
+
en vez de mentir** (`lib/tracker-url.mjs`).
|
|
28
|
+
- **El bloque de enlaces de `dai pr` no sobrevivía a un edit.** Iba marcado con un
|
|
29
|
+
comentario suelto, así que cualquier agente que reescribiera *"Enlaces relacionados"*
|
|
30
|
+
se lo llevaba puesto sin dejar rastro — pasó en PRs reales. Y el propio template lo
|
|
31
|
+
invitaba: su hint pedía *"US en el tracker, commit ancla, docs, issues"*, o sea justo la
|
|
32
|
+
sección que `dai pr` acababa de llenar. dai se peleaba consigo mismo y ganaba el que
|
|
33
|
+
corría último. Ahora el bloque va **delimitado** (`<!-- dai:links:start … end -->`),
|
|
34
|
+
se **regenera de forma idempotente**, preserva lo que el humano sumó abajo, y el hint
|
|
35
|
+
del template pide solo lo que dai **no** sabe (docs, issues, PRs relacionadas).
|
|
36
|
+
|
|
37
|
+
### Cambiado
|
|
38
|
+
- **dai deduce el link al tracker solo.** Con `DAI_PM=jira` o `=clickup` ya no hace falta
|
|
39
|
+
`DAI_TRACKER_URL_TEMPLATE`: se deriva de la config (`/browse/<KEY>` y
|
|
40
|
+
`/t/<id>`), y `fetchUS` ahora devuelve la **URL canónica** del tracker — en ClickUp, la
|
|
41
|
+
que trae el `team_id`, que no se puede deducir del id. La variable queda como
|
|
42
|
+
**override** para trackers con URL propia. `dai init` dejó de scaffoldearla: era
|
|
43
|
+
contraproducente, porque el template gana sobre la canónica y le tapaba el `team_id`.
|
|
44
|
+
Los `.env` que ya la tienen siguen andando igual (el override sigue ganando).
|
|
45
|
+
|
|
6
46
|
## [0.8.1] — 2026-07-16
|
|
7
47
|
|
|
8
48
|
**Primera prueba real en Windows con analistas y devs de una empresa: el ciclo completo
|
|
@@ -326,6 +366,8 @@ ClickUp y Jira Cloud.
|
|
|
326
366
|
- Tests de las rutas de red (jira/clickup/forge) con `fetch` mockeado. Sin links rotos;
|
|
327
367
|
`files` de npm sin tests ni secretos.
|
|
328
368
|
|
|
369
|
+
[0.8.2]: https://github.com/dforce2055/dai/releases/tag/v0.8.2
|
|
370
|
+
[0.8.1]: https://github.com/dforce2055/dai/releases/tag/v0.8.1
|
|
329
371
|
[0.8.0]: https://github.com/dforce2055/dai/releases/tag/v0.8.0
|
|
330
372
|
[0.7.0]: https://github.com/dforce2055/dai/releases/tag/v0.7.0
|
|
331
373
|
[0.6.0]: https://github.com/dforce2055/dai/releases/tag/v0.6.0
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.8.
|
|
1
|
+
0.8.2
|
package/cli/dai.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import { loadEnv } from "./lib/env.mjs";
|
|
|
25
25
|
import { getAdapter, coverageStatus, statusLabel } from "./lib/pm-adapter.mjs";
|
|
26
26
|
import { branchUrl, commitUrl, parseRemote, detectForge } from "./lib/forge-url.mjs";
|
|
27
27
|
import { parsePrRef, getPR, postComment } from "./lib/forge-api.mjs";
|
|
28
|
+
import { trackerUrl } from "./lib/tracker-url.mjs";
|
|
28
29
|
import { composePrBody, prTitle, forgeTool } from "./lib/pr.mjs";
|
|
29
30
|
import { dirsEqual } from "./lib/fsutil.mjs";
|
|
30
31
|
import { parseFlags, parseAssistants, isAssistantToken, asList } from "./lib/args.mjs";
|
|
@@ -66,10 +67,9 @@ function runNpmTool(name, args, opts = {}) {
|
|
|
66
67
|
const win = process.platform === "win32";
|
|
67
68
|
return execFileSync(win ? `${name}.cmd` : name, args, { shell: win, ...opts });
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
70
|
+
// La URL de la US: template > canónica del tracker > derivada del backend > null.
|
|
71
|
+
// Nunca el id pelado: ver el porqué en lib/tracker-url.mjs.
|
|
72
|
+
const usUrlFor = (id, liveUrl = null) => trackerUrl(id, { env: process.env, liveUrl });
|
|
73
73
|
|
|
74
74
|
// ── ac-hash ─────────────────────────────────────────────────────────────────
|
|
75
75
|
function cmdAcHash(arg) {
|
|
@@ -88,7 +88,7 @@ function cmdLs(opts) {
|
|
|
88
88
|
for (const im of f.implements || []) {
|
|
89
89
|
if (isPlaceholderId(im.id)) continue; // saltea plantillas sin completar
|
|
90
90
|
rows.push({ change: f.change, repo: f.repo, id: im.id, version: im.version,
|
|
91
|
-
ac_hash: im.ac_hash, link:
|
|
91
|
+
ac_hash: im.ac_hash, link: usUrlFor(im.id) });
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
if (opts.json) { process.stdout.write(JSON.stringify(rows, null, 2) + "\n"); return; }
|
|
@@ -394,8 +394,14 @@ async function cmdPr(opts) {
|
|
|
394
394
|
// Commits de la branch (para precargar "Cambios realizados").
|
|
395
395
|
let commits = [];
|
|
396
396
|
try { commits = git(["log", `${base}..HEAD`, "--pretty=%s"]).split("\n").filter(Boolean); } catch { /* base local ausente */ }
|
|
397
|
+
// La canónica del tracker (live.url) gana sobre la derivada; el template gana sobre todo.
|
|
398
|
+
const usUrl = usUrlFor(id, live?.url);
|
|
399
|
+
if (!usUrl) {
|
|
400
|
+
warn(`no sé la URL de ${id} en el tracker: la PR va a quedar sin link a la US.`);
|
|
401
|
+
warn(`configurá DAI_TRACKER_URL_TEMPLATE en el .env (p. ej. https://tu-tracker/browse/{id}).`);
|
|
402
|
+
}
|
|
397
403
|
const body = composePrBody(readFileSync(tplPath, "utf8"), {
|
|
398
|
-
id, version, ac_hash, status, usUrl
|
|
404
|
+
id, version, ac_hash, status, usUrl, usTitle: live?.title, commits,
|
|
399
405
|
branch, branchUrl: branchUrl(remote, branch), commit, commitUrl: commitUrl(remote, commit),
|
|
400
406
|
});
|
|
401
407
|
const title = prTitle(opts, id, live?.title);
|
package/cli/lib/bootstrap.mjs
CHANGED
|
@@ -92,10 +92,15 @@ export function skillToCursor(md) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
// Contenido del .env según el backend de PM elegido (tokens vacíos, a completar).
|
|
95
|
+
//
|
|
96
|
+
// Sin DAI_TRACKER_URL_TEMPLATE a propósito: con jira/clickup, dai deduce el link solo
|
|
97
|
+
// (lib/tracker-url.mjs). Scaffoldearlo era peor que no ponerlo — el template GANA sobre
|
|
98
|
+
// la URL canónica que devuelve el tracker, así que el `/t/{id}` que escribíamos acá
|
|
99
|
+
// tapaba la de ClickUp con team_id. Queda como override manual para trackers raros.
|
|
95
100
|
export function envFor(pm) {
|
|
96
101
|
const head = "# Config de dai — completá lo que falte. NUNCA commitees tokens (.env está gitignored).\n";
|
|
97
102
|
if (pm === "clickup") {
|
|
98
|
-
return head + "DAI_PM=clickup\nDAI_CLICKUP_TOKEN=\nDAI_CLICKUP_LIST_ID=\
|
|
103
|
+
return head + "DAI_PM=clickup\nDAI_CLICKUP_TOKEN=\nDAI_CLICKUP_LIST_ID=\n";
|
|
99
104
|
}
|
|
100
105
|
if (pm === "jira") {
|
|
101
106
|
return head +
|
|
@@ -107,8 +112,7 @@ export function envFor(pm) {
|
|
|
107
112
|
"DAI_JIRA_PROJECT=\n" +
|
|
108
113
|
"DAI_JIRA_ISSUETYPE=Story\n" +
|
|
109
114
|
"# Campos propios que tu Jira exige al crear. Si el archivo no existe, se ignora.\n" +
|
|
110
|
-
"DAI_JIRA_FIELDS_FILE=.dai/jira-fields.json\n"
|
|
111
|
-
"DAI_TRACKER_URL_TEMPLATE=\n";
|
|
115
|
+
"DAI_JIRA_FIELDS_FILE=.dai/jira-fields.json\n";
|
|
112
116
|
}
|
|
113
117
|
return head + "DAI_PM=md\nDAI_MD_US_DIR=.dai/us\n";
|
|
114
118
|
}
|
package/cli/lib/pm-adapter.mjs
CHANGED
|
@@ -7,9 +7,12 @@
|
|
|
7
7
|
// clickup — REST v2 (pm-clickup.mjs)
|
|
8
8
|
//
|
|
9
9
|
// Interfaz (fetchUS/stamp pueden ser sync o async — el CLI siempre await-ea):
|
|
10
|
-
// fetchUS(id) → { id, title, spec_version, ac_hash } | null
|
|
10
|
+
// fetchUS(id) → { id, title, spec_version, ac_hash, url } | null
|
|
11
11
|
// stamp(id, record) → destino donde quedó la cobertura
|
|
12
12
|
// kind → nombre del backend
|
|
13
|
+
//
|
|
14
|
+
// `url` es la URL web canónica de la US según el tracker (opcional: null si el backend
|
|
15
|
+
// no la sabe, como md). El CLI la prefiere sobre la derivada — ver tracker-url.mjs.
|
|
13
16
|
|
|
14
17
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
15
18
|
import { join, dirname } from "node:path";
|
package/cli/lib/pm-clickup.mjs
CHANGED
|
@@ -29,7 +29,9 @@ export function clickupAdapter(env) {
|
|
|
29
29
|
const res = await fetch(clickupTaskUrl(id), { headers: clickupAuthHeaders(env) });
|
|
30
30
|
if (res.status === 404) return null;
|
|
31
31
|
if (!res.ok) throw new Error(`clickup ${res.status}: ${await res.text()}`);
|
|
32
|
-
|
|
32
|
+
const j = await res.json();
|
|
33
|
+
// `url` es la canónica (/t/<team_id>/<id>): la sabe ClickUp, no la deducimos.
|
|
34
|
+
return { id, ...parseUS(clickupTaskToText(j)), url: j.url || null };
|
|
33
35
|
},
|
|
34
36
|
async stamp(id, record) {
|
|
35
37
|
const res = await fetch(clickupCommentUrl(id), {
|
package/cli/lib/pm-jira.mjs
CHANGED
|
@@ -143,7 +143,7 @@ export function jiraAdapter(env) {
|
|
|
143
143
|
const res = await daiFetch(jiraIssueUrl(base, id), { headers: jiraAuthHeaders(env) });
|
|
144
144
|
if (res.status === 404) return null;
|
|
145
145
|
if (!res.ok) throw new Error(`jira ${res.status}: ${await res.text()}`);
|
|
146
|
-
return { id, ...parseUS(jiraIssueToText(await res.json())) };
|
|
146
|
+
return { id, ...parseUS(jiraIssueToText(await res.json())), url: `${trim(base)}/browse/${id}` };
|
|
147
147
|
},
|
|
148
148
|
async stamp(id, record) {
|
|
149
149
|
const res = await daiFetch(jiraCommentUrl(base, id), {
|
package/cli/lib/pr.mjs
CHANGED
|
@@ -29,15 +29,44 @@ export function composePrBody(template, d) {
|
|
|
29
29
|
if (d.commits && d.commits.length) {
|
|
30
30
|
b = replaceSection(b, "Cambios realizados", d.commits.map((c) => `- [x] ${c}`).join("\n"));
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
return upsertLinksBlock(b, d);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── Bloque de enlaces ────────────────────────────────────────────────────────
|
|
36
|
+
// Delimitado y regenerable a propósito. Con el comentario suelto de antes, cualquier
|
|
37
|
+
// agente que reescribiera "Enlaces relacionados" se lo llevaba puesto sin dejar rastro
|
|
38
|
+
// (pasó en PRs reales). Con marcadores, el bloque se detecta, se preserva y se
|
|
39
|
+
// regenera — y quien edite el body ve que es de dai y que se pisa solo.
|
|
40
|
+
export const LINKS_START = "<!-- dai:links:start · generado por `dai pr` — no editar a mano -->";
|
|
41
|
+
export const LINKS_END = "<!-- dai:links:end -->";
|
|
42
|
+
|
|
43
|
+
// Los links que dai sabe. Sin URL no inventa la línea: prefiere no decir nada.
|
|
44
|
+
export function renderLinks(d) {
|
|
45
|
+
const links = [LINKS_START];
|
|
46
|
+
if (d.usUrl) links.push(`- US \`${d.id}\`: ${d.usUrl}`);
|
|
35
47
|
if (d.branchUrl) links.push(`- branch \`${d.branch}\`: ${d.branchUrl}`);
|
|
36
48
|
if (d.commitUrl) links.push(`- commit \`${(d.commit || "").slice(0, 8)}\`: ${d.commitUrl}`);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
links.push(LINKS_END);
|
|
50
|
+
return links.join("\n");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Inserta o reemplaza el bloque de dai. Idempotente: correrlo N veces da lo mismo.
|
|
54
|
+
export function upsertLinksBlock(body, d) {
|
|
55
|
+
const block = renderLinks(d);
|
|
56
|
+
// 1. ¿Ya está el bloque delimitado? Se reemplaza entero (regenerar, no duplicar).
|
|
57
|
+
const delimited = /<!--\s*dai:links:start[\s\S]*?dai:links:end\s*-->/i;
|
|
58
|
+
if (delimited.test(body)) return body.replace(delimited, block);
|
|
59
|
+
// 2. ¿Está la sección del template? El bloque va debajo del heading, PRESERVANDO el
|
|
60
|
+
// hint HTML si lo hay: es la guía para quien edite (y es invisible al renderizar).
|
|
61
|
+
// dai suma, no borra — borrar el texto de otro es justo lo que estamos arreglando.
|
|
62
|
+
// (el `\s*` tolera la línea en blanco entre el heading y el hint; como solo matchea
|
|
63
|
+
// espacios, no puede saltar a la sección siguiente para buscarse un comentario)
|
|
64
|
+
const heading = /(^|\n)(##[^\n]*Enlaces relacionados[^\n]*\n)(\s*<!--[\s\S]*?-->[ \t]*\n)?/i;
|
|
65
|
+
if (heading.test(body)) {
|
|
66
|
+
return body.replace(heading, (m, pre, h, hint) => `${pre}${h}${hint || ""}\n${block}\n`);
|
|
67
|
+
}
|
|
68
|
+
// 3. Ni bloque ni sección: se apéndea con su propio heading.
|
|
69
|
+
return `${body.replace(/\s*$/, "")}\n\n## Enlaces relacionados\n\n${block}\n`;
|
|
41
70
|
}
|
|
42
71
|
|
|
43
72
|
// Título del PR: el pasado a mano, o "<ID>: <título de la US>", o solo el ID.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// dai · la URL de la US en el tracker. Puro, sync, sin red (ADR-0007: config por .env).
|
|
2
|
+
//
|
|
3
|
+
// Cadena de resolución, del más específico al más general:
|
|
4
|
+
// 1. DAI_TRACKER_URL_TEMPLATE — override explícito del usuario: siempre gana.
|
|
5
|
+
// 2. La URL que devolvió el tracker en `fetchUS` — la canónica (ClickUp la trae con
|
|
6
|
+
// el team_id; la derivada no puede saberlo). Solo existe si hubo red.
|
|
7
|
+
// 3. Derivada del backend + su config — determinística y offline.
|
|
8
|
+
// 4. `null` — dai NO sabe la URL.
|
|
9
|
+
//
|
|
10
|
+
// El paso 4 es el que importa. Antes devolvíamos el `id` pelado, y como un string es
|
|
11
|
+
// truthy, quien consumía esto lo escribía igual — un id disfrazado de enlace, sin un
|
|
12
|
+
// solo aviso. Preferimos no decir nada antes que mentir: quien llama decide si omite
|
|
13
|
+
// la línea o avisa.
|
|
14
|
+
|
|
15
|
+
// URL web de la US deducida del backend, sin salir a la red.
|
|
16
|
+
export function deriveTrackerUrl(id, env = {}) {
|
|
17
|
+
if (!id) return null;
|
|
18
|
+
const kind = String(env.DAI_PM || "md").toLowerCase();
|
|
19
|
+
if (kind === "clickup") {
|
|
20
|
+
// /t/<id> redirige a la canónica /t/<team_id>/<id>. Sin token no sabemos el team.
|
|
21
|
+
return `https://app.clickup.com/t/${encodeURIComponent(id)}`;
|
|
22
|
+
}
|
|
23
|
+
if (kind === "jira") {
|
|
24
|
+
const base = String(env.DAI_JIRA_BASE_URL || "").replace(/\/+$/, "");
|
|
25
|
+
return base ? `${base}/browse/${encodeURIComponent(id)}` : null;
|
|
26
|
+
}
|
|
27
|
+
return null; // md: la US es un archivo local, no tiene URL web
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// La URL final, o null si no hay forma de saberla. `liveUrl` es la que trajo fetchUS.
|
|
31
|
+
export function trackerUrl(id, { env = {}, liveUrl = null } = {}) {
|
|
32
|
+
if (!id) return null;
|
|
33
|
+
const tpl = env.DAI_TRACKER_URL_TEMPLATE;
|
|
34
|
+
if (tpl) return String(tpl).replace(/\{id\}/g, id);
|
|
35
|
+
return liveUrl || deriveTrackerUrl(id, env);
|
|
36
|
+
}
|
package/docs/PROBAR.md
CHANGED
|
@@ -65,8 +65,9 @@ Si esto anda, el flujo está bien. Pasa al tracker real.
|
|
|
65
65
|
cat > .env <<'EOF'
|
|
66
66
|
DAI_PM=clickup
|
|
67
67
|
DAI_CLICKUP_TOKEN=pk_XXXXXXXX
|
|
68
|
-
DAI_TRACKER_URL_TEMPLATE=https://app.clickup.com/t/{id}
|
|
69
68
|
EOF
|
|
69
|
+
# El link a la tarea lo deduce dai solo. Solo si tu tracker vive en otra URL:
|
|
70
|
+
# DAI_TRACKER_URL_TEMPLATE=https://mi-tracker/t/{id}
|
|
70
71
|
dai doctor # confirma DAI_PM=clickup y el token
|
|
71
72
|
|
|
72
73
|
dai link-us 86cxyz # trae la US de ClickUp → branch + implements.yaml
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dforce2055/dai",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Metodología de desarrollo asistido por IA — CLI de acciones deterministas (trazabilidad QUÉ↔CÓMO).",
|
|
5
5
|
"repository": { "type": "git", "url": "git+https://github.com/dforce2055/dai.git" },
|
|
6
6
|
"homepage": "https://dforce2055.github.io/dai/",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dai-review
|
|
3
|
-
description: "Revisa una Pull/Merge Request de un repo remoto (GitHub o GitLab) de forma consciente de la metodología dai, y deja un comentario estándar en español con errores y mejoras. Corre `dai check` (¿la US está atrasada?), valida el Definition of Done, hace el review de código (correctitud + calidad), compone el comentario estándar y lo postea — vía el MCP del forge si está disponible, o vía `dai forge comment` (token) si no. Invocar como /dai-review <URL-de-la-PR o número>. Usar en el paso 6 de SCRUM-CON-IA (code review), antes de que un partner humano firme."
|
|
3
|
+
description: "Revisa una Pull/Merge Request de un repo remoto (GitHub o GitLab) de forma consciente de la metodología dai, y deja un comentario estándar en español con errores y mejoras. Corre `dai check` (¿la US está atrasada?), valida el Definition of Done, hace el review de código (correctitud + calidad), compone el comentario estándar, TE LO MUESTRA Y ESPERA TU OK, y recién entonces lo postea — vía el MCP del forge si está disponible, o vía `dai forge comment` (token) si no. Nunca postea sin aprobación explícita: sale con tu nombre y tu token. Invocar como /dai-review <URL-de-la-PR o número>. Usar en el paso 6 de SCRUM-CON-IA (code review), antes de que un partner humano firme."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# dai-review — review de PR consciente de la metodología
|
|
@@ -39,7 +39,11 @@ Mismo comentario estándar, dos formas de dejarlo (elige la disponible, en este
|
|
|
39
39
|
- 🟡 **Mejoras** de calidad (reuso, simplicidad, eficiencia).
|
|
40
40
|
- ✅ Lo que está **bien** (refuerza lo bueno).
|
|
41
41
|
5. **Componer el comentario estándar** (ver formato abajo).
|
|
42
|
-
6. **
|
|
42
|
+
6. **Mostrarlo y esperar el OK.** Imprimí el comentario **entero**, tal cual va a salir,
|
|
43
|
+
y preguntá: _"¿lo posteo así, lo edito, o lo descarto?"_ **Frená ahí.** Si te piden
|
|
44
|
+
cambios, aplicalos y volvé a mostrarlo. No hay atajo: sin un "sí" explícito en este
|
|
45
|
+
turno, no se postea. Un "sí" de una PR anterior no cuenta para esta.
|
|
46
|
+
7. **Postear** por MCP o `dai forge comment`. Confirmar el link al comentario.
|
|
43
47
|
|
|
44
48
|
## El comentario estándar
|
|
45
49
|
|
|
@@ -64,12 +68,20 @@ Es el mismo que emite `renderReviewComment` del CLI — respeta esta forma:
|
|
|
64
68
|
_Revisión asistida por dai. La aprobación la firma un humano (Art. 5 del manifiesto)._
|
|
65
69
|
```
|
|
66
70
|
|
|
67
|
-
##
|
|
71
|
+
## Tres cortes duros
|
|
68
72
|
|
|
69
73
|
1. **No aprobar.** La skill **comenta**, no firma la aprobación. Eso es de un humano.
|
|
70
|
-
2. **
|
|
74
|
+
2. **No postear sin OK.** El comentario sale **con el token del humano y con su nombre**
|
|
75
|
+
(`GITHUB_TOKEN`/`GITLAB_TOKEN` son suyos): en la PR de un compañero figura como si lo
|
|
76
|
+
hubiera escrito él. Publicar un juicio sobre el código de otro, firmado por alguien
|
|
77
|
+
que no lo leyó, es tan grave como aprobar sin mirar. El paso 6 no es opcional.
|
|
78
|
+
3. **Hallazgos concretos.** Nada de "mejorar la calidad" en abstracto: archivo, línea,
|
|
71
79
|
y el porqué. Si no es accionable, no va.
|
|
72
80
|
|
|
81
|
+
> **Por qué el corte 2 existe:** esta skill posteaba directo. El Art. 5 estaba bien
|
|
82
|
+
> leído en la letra —no clickeaba Approve— y mal puesto en la práctica: te dejaba
|
|
83
|
+
> firmar en público un review que nunca viste. Que salga bueno era suerte, no diseño.
|
|
84
|
+
|
|
73
85
|
## Relación con el modelo
|
|
74
86
|
|
|
75
87
|
- Es el paso 6 de [`SCRUM-CON-IA.md`](../../docs/SCRUM-CON-IA.md).
|
|
@@ -42,7 +42,11 @@
|
|
|
42
42
|
|
|
43
43
|
## Enlaces relacionados
|
|
44
44
|
|
|
45
|
-
<!--
|
|
45
|
+
<!--
|
|
46
|
+
La US, la branch y el commit ancla los precarga `dai pr` en el bloque `dai:links`
|
|
47
|
+
de abajo: NO los escribas a mano ni reescribas ese bloque (se regenera y te lo pisa).
|
|
48
|
+
Acá abajo sumá solo lo que dai no sabe: docs, issues, PRs relacionadas, dependencias.
|
|
49
|
+
-->
|
|
46
50
|
|
|
47
51
|
---
|
|
48
52
|
|