@dforce2055/dai 0.1.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 (81) hide show
  1. package/.env.example +30 -0
  2. package/CHANGELOG.md +46 -0
  3. package/CODE_OF_CONDUCT.md +37 -0
  4. package/CONTRIBUTING.md +66 -0
  5. package/LICENSE +674 -0
  6. package/README.md +288 -0
  7. package/SECURITY.md +37 -0
  8. package/VERSION +1 -0
  9. package/cli/dai.mjs +692 -0
  10. package/cli/lib/ac-hash.mjs +74 -0
  11. package/cli/lib/args.mjs +23 -0
  12. package/cli/lib/bootstrap.mjs +74 -0
  13. package/cli/lib/env.mjs +23 -0
  14. package/cli/lib/forge-api.mjs +96 -0
  15. package/cli/lib/forge-url.mjs +61 -0
  16. package/cli/lib/fsutil.mjs +24 -0
  17. package/cli/lib/implements.mjs +94 -0
  18. package/cli/lib/link-us.mjs +59 -0
  19. package/cli/lib/pm-adapter.mjs +59 -0
  20. package/cli/lib/pm-clickup.mjs +54 -0
  21. package/cli/lib/pm-jira.mjs +123 -0
  22. package/cli/lib/pr.mjs +53 -0
  23. package/cli/lib/us.mjs +36 -0
  24. package/docs/EJEMPLO-END-TO-END.md +330 -0
  25. package/docs/MANIFIESTO.md +114 -0
  26. package/docs/METODOLOGIA.md +254 -0
  27. package/docs/PROBAR.md +91 -0
  28. package/docs/SCRUM-CON-IA.md +190 -0
  29. package/docs/adr/0001-contrato-ac-hash.md +86 -0
  30. package/docs/adr/0002-agnostico-del-asistente.md +87 -0
  31. package/docs/adr/0003-deteccion-y-estampado-son-comandos.md +73 -0
  32. package/docs/adr/0004-ubicacion-y-schema-implements.md +94 -0
  33. package/docs/adr/0005-superficie-comandos-y-stamp.md +65 -0
  34. package/docs/adr/0006-distribucion-y-licencia.md +59 -0
  35. package/docs/adr/0007-modelo-de-autenticacion.md +63 -0
  36. package/docs/adr/README.md +19 -0
  37. package/docs/detalle/01-refinamiento.md +33 -0
  38. package/docs/detalle/02-planning.md +27 -0
  39. package/docs/detalle/03-ramas.md +32 -0
  40. package/docs/detalle/04-tdd.md +35 -0
  41. package/docs/detalle/05-smoke.md +32 -0
  42. package/docs/detalle/06-code-review.md +34 -0
  43. package/docs/detalle/07-merge-trazabilidad.md +33 -0
  44. package/docs/detalle/08-daily.md +29 -0
  45. package/docs/detalle/09-review.md +25 -0
  46. package/docs/detalle/10-retro.md +27 -0
  47. package/docs/detalle/README.md +20 -0
  48. package/docs/glosario.md +79 -0
  49. package/docs/guias/dev.md +66 -0
  50. package/docs/guias/lead.md +53 -0
  51. package/docs/guias/po.md +50 -0
  52. package/governance/branch-naming.md +36 -0
  53. package/governance/ci-rules.md +57 -0
  54. package/governance/commit-convention.md +76 -0
  55. package/index.html +479 -0
  56. package/install.sh +19 -0
  57. package/manifest.yaml +76 -0
  58. package/package.json +55 -0
  59. package/skills/dai-review/SKILL.md +78 -0
  60. package/skills/doc-to-backlog/SKILL.md +70 -0
  61. package/skills/doc-to-backlog/templates/backlog-candidato.md +49 -0
  62. package/skills/grill-epic/SKILL.md +76 -0
  63. package/skills/grill-intent/SKILL.md +43 -0
  64. package/skills/grill-intent/templates/intent.md +36 -0
  65. package/skills/grill-user-story/SKILL.md +76 -0
  66. package/skills/grill-user-story/templates/user-story.md +61 -0
  67. package/skills/link-us/SKILL.md +42 -0
  68. package/skills/link-us/templates/implements.yaml +16 -0
  69. package/skills/tdd/SKILL.md +109 -0
  70. package/skills/tdd/deep-modules.md +33 -0
  71. package/skills/tdd/interface-design.md +31 -0
  72. package/skills/tdd/mocking.md +59 -0
  73. package/skills/tdd/refactoring.md +10 -0
  74. package/skills/tdd/tests.md +61 -0
  75. package/templates/adr.md +43 -0
  76. package/templates/commit-msg +48 -0
  77. package/templates/definition-of-done.md +50 -0
  78. package/templates/definition-of-ready.md +51 -0
  79. package/templates/epica.md +62 -0
  80. package/templates/formato-us.md +129 -0
  81. package/templates/pull-request.md +62 -0
@@ -0,0 +1,123 @@
1
+ // dai · backend Jira del adaptador de PM (REST v3 / Jira Cloud, cara CLI).
2
+ // Auth: Basic (email + api_token) — token scopeado, no contraseña (ADR-0007).
3
+ // Config: DAI_JIRA_BASE_URL, DAI_JIRA_EMAIL, DAI_JIRA_TOKEN.
4
+ //
5
+ // Jira Cloud usa ADF (Atlassian Document Format, JSON) para descripción y comentarios,
6
+ // no markdown ni string. Así que: al LEER convertimos ADF → markdown (para que el
7
+ // hasher encuentre "## Criterios de aceptación"); al ESCRIBIR el stamp, componemos ADF.
8
+
9
+ import { parseUS } from "./us.mjs";
10
+
11
+ const trim = (b) => String(b || "").replace(/\/+$/, "");
12
+
13
+ export function jiraIssueUrl(base, id) {
14
+ return `${trim(base)}/rest/api/3/issue/${encodeURIComponent(id)}?fields=summary,description`;
15
+ }
16
+ export function jiraCommentUrl(base, id) {
17
+ return `${trim(base)}/rest/api/3/issue/${encodeURIComponent(id)}/comment`;
18
+ }
19
+ export function jiraAuthHeaders(env) {
20
+ const cred = Buffer.from(`${env.DAI_JIRA_EMAIL || ""}:${env.DAI_JIRA_TOKEN || ""}`).toString("base64");
21
+ return { Authorization: `Basic ${cred}`, Accept: "application/json", "Content-Type": "application/json" };
22
+ }
23
+
24
+ // ── ADF → markdown (para leer la descripción) ────────────────────────────────
25
+ export function adfToMarkdown(node) {
26
+ if (node == null) return "";
27
+ if (Array.isArray(node)) return node.map(adfToMarkdown).join("");
28
+ switch (node.type) {
29
+ case "doc": return (node.content || []).map(adfToMarkdown).join("\n");
30
+ case "heading": return "#".repeat(node.attrs?.level || 1) + " " + (node.content || []).map(adfToMarkdown).join("") + "\n";
31
+ case "paragraph": return (node.content || []).map(adfToMarkdown).join("").trim() + "\n";
32
+ case "bulletList":
33
+ case "orderedList": return (node.content || []).map(adfToMarkdown).join("");
34
+ case "listItem": return "- " + (node.content || []).map(adfToMarkdown).join("").trim() + "\n";
35
+ case "text": return node.text || "";
36
+ case "hardBreak": return "\n";
37
+ default: return (node.content || []).map(adfToMarkdown).join("");
38
+ }
39
+ }
40
+
41
+ // ── markdown → ADF (para CREAR el issue: la descripción va en ADF) ────────────
42
+ // Parser de bloques: headings, párrafos y bullets. Suficiente para el formato de US.
43
+ export function markdownToAdf(md) {
44
+ const clean = (s) => s.replace(/[*_`]+/g, "").trim();
45
+ const content = [];
46
+ let para = [], bullets = null;
47
+ const flushPara = () => { if (para.length) { const t = clean(para.join(" ")); if (t) content.push({ type: "paragraph", content: [{ type: "text", text: t }] }); para = []; } };
48
+ const flushBullets = () => { if (bullets) { if (bullets.length) content.push({ type: "bulletList", content: bullets }); bullets = null; } };
49
+ for (const raw of String(md || "").split(/\r?\n/)) {
50
+ const line = raw.replace(/\s+$/, "");
51
+ const h = line.match(/^(#{1,6})\s+(.*)$/);
52
+ const b = line.match(/^\s*[-*+]\s+(?:\[[ xX]\]\s+)?(.*)$/);
53
+ if (h) { flushPara(); flushBullets(); const t = clean(h[2]); if (t) content.push({ type: "heading", attrs: { level: h[1].length }, content: [{ type: "text", text: t }] }); }
54
+ else if (b) { flushPara(); if (!bullets) bullets = []; const t = clean(b[1]); if (t) bullets.push({ type: "listItem", content: [{ type: "paragraph", content: [{ type: "text", text: t }] }] }); }
55
+ else if (line.trim() === "") { flushPara(); flushBullets(); }
56
+ else { flushBullets(); para.push(line.trim()); }
57
+ }
58
+ flushPara(); flushBullets();
59
+ if (content.length === 0) content.push({ type: "paragraph", content: [{ type: "text", text: " " }] });
60
+ return { type: "doc", version: 1, content };
61
+ }
62
+
63
+ // Arma el texto de la US: summary (campo) + descripción (ADF→md o string).
64
+ export function jiraIssueToText(json) {
65
+ const f = json.fields || {};
66
+ const desc = typeof f.description === "string" ? f.description
67
+ : f.description ? adfToMarkdown(f.description) : "";
68
+ return `# ${f.summary || json.key || ""}\n\n${desc}`;
69
+ }
70
+
71
+ // ── cobertura → ADF (para escribir el comentario del stamp) ───────────────────
72
+ const STATUS = { "al-dia": "✅ al día", atrasado: "⚠️ atrasado", "sin-us": "❓ sin US" };
73
+ const txt = (s) => ({ type: "text", text: String(s) });
74
+ const link = (s, href) => ({ type: "text", text: String(s), marks: [{ type: "link", attrs: { href } }] });
75
+
76
+ export function renderCoverageAdf(id, r) {
77
+ const content = [
78
+ { type: "heading", attrs: { level: 4 }, content: [txt(`Cobertura de ${id} — generado por dai stamp`)] },
79
+ { type: "paragraph", content: [txt(`${r.repo} / ${r.change} @ ${r.version} (${r.ac_hash}) — ${STATUS[r.status] || r.status}`)] },
80
+ ];
81
+ const items = [];
82
+ if (r.branchUrl) items.push({ type: "listItem", content: [{ type: "paragraph", content: [txt("branch: "), link(r.branch, r.branchUrl)] }] });
83
+ if (r.commitUrl) items.push({ type: "listItem", content: [{ type: "paragraph", content: [txt("commit: "), link((r.commit || "").slice(0, 8), r.commitUrl), txt(" (ancla durable)")] }] });
84
+ if (items.length) content.push({ type: "bulletList", content: items });
85
+ return { type: "doc", version: 1, content };
86
+ }
87
+
88
+ export function jiraAdapter(env) {
89
+ const base = env.DAI_JIRA_BASE_URL;
90
+ if (!base) throw new Error("falta DAI_JIRA_BASE_URL en el .env (backend jira).");
91
+ return {
92
+ kind: "jira",
93
+ async fetchUS(id) {
94
+ const res = await fetch(jiraIssueUrl(base, id), { headers: jiraAuthHeaders(env) });
95
+ if (res.status === 404) return null;
96
+ if (!res.ok) throw new Error(`jira ${res.status}: ${await res.text()}`);
97
+ return { id, ...parseUS(jiraIssueToText(await res.json())) };
98
+ },
99
+ async stamp(id, record) {
100
+ const res = await fetch(jiraCommentUrl(base, id), {
101
+ method: "POST", headers: jiraAuthHeaders(env),
102
+ body: JSON.stringify({ body: renderCoverageAdf(id, record) }),
103
+ });
104
+ if (!res.ok) throw new Error(`jira ${res.status}: ${await res.text()}`);
105
+ return `${trim(base)}/browse/${id}`;
106
+ },
107
+ async createUS({ title, descriptionMarkdown }) {
108
+ const project = env.DAI_JIRA_PROJECT;
109
+ const issuetype = env.DAI_JIRA_ISSUETYPE || "Story";
110
+ if (!project) throw new Error("falta DAI_JIRA_PROJECT en el .env (la clave del proyecto donde crear el issue).");
111
+ const res = await fetch(`${trim(base)}/rest/api/3/issue`, {
112
+ method: "POST", headers: jiraAuthHeaders(env),
113
+ body: JSON.stringify({ fields: {
114
+ project: { key: project }, issuetype: { name: issuetype },
115
+ summary: title, description: markdownToAdf(descriptionMarkdown),
116
+ } }),
117
+ });
118
+ if (!res.ok) throw new Error(`jira ${res.status}: ${await res.text()}`);
119
+ const j = await res.json();
120
+ return { id: j.key, url: `${trim(base)}/browse/${j.key}` };
121
+ },
122
+ };
123
+ }
package/cli/lib/pr.mjs ADDED
@@ -0,0 +1,53 @@
1
+ // dai · componer el cuerpo de una Pull/Merge Request desde el template (ADR-0005).
2
+ // Parte pura y testeable: rellena el template con los datos del link + git + check.
3
+ // Los efectos (git push, gh/glab create) viven en dai.mjs.
4
+
5
+ const EMOJI = { "al-dia": "✅ al día", atrasado: "⚠️ atrasado", "sin-us": "❓ sin US" };
6
+
7
+ // Reemplaza el cuerpo de una sección (## Heading … hasta el próximo ## o el final)
8
+ // por content. Tolerante: si no encuentra la sección, devuelve el body igual.
9
+ // Sin flag `m`: `$` = fin de string (no fin de línea), así no corta antes de tiempo.
10
+ export function replaceSection(body, heading, content) {
11
+ const esc = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12
+ const re = new RegExp(`(^|\\n)(##[ \\t]*${esc}[ \\t]*\\n)([\\s\\S]*?)(\\n##\\s|$)`, "i");
13
+ if (!re.test(body)) return body;
14
+ return body.replace(re, (m, pre, h, _b, tail) => `${pre}${h}\n${content}\n${tail}`);
15
+ }
16
+
17
+ // Rellena el template del PR con los datos precargados. Tolerante: reemplaza los
18
+ // placeholders que encuentra y deja el resto para que el humano lo edite.
19
+ export function composePrBody(template, d) {
20
+ let b = template;
21
+ b = b.replace(/`ABC-###`/g, `\`${d.id}\``);
22
+ b = b.replace(/@ `vX`/g, `@ \`${d.version}\``);
23
+ b = b.replace(/`<hash>`/g, `\`${d.ac_hash}\``);
24
+ b = b.replace(/verificado con `dai check` ✅/g, `verificado con \`dai check\`: ${EMOJI[d.status] || d.status}`);
25
+
26
+ // Descripción: default desde la US (el humano lo pule).
27
+ if (d.usTitle) b = replaceSection(b, "Descripción", `Implementa la US **${d.usTitle}** (\`${d.id}\`). Ver los criterios de aceptación en el tracker.`);
28
+ // Cambios realizados: de los commits de la branch (como gh pr create --fill).
29
+ if (d.commits && d.commits.length) {
30
+ b = replaceSection(b, "Cambios realizados", d.commits.map((c) => `- [x] ${c}`).join("\n"));
31
+ }
32
+ // Bloque de enlaces (lo agrega dai; el humano completa el resto del template).
33
+ const links = ["", "<!-- Enlaces precargados por `dai pr` -->"];
34
+ if (d.usUrl) links.push(`- US: ${d.usUrl}`);
35
+ if (d.branchUrl) links.push(`- branch \`${d.branch}\`: ${d.branchUrl}`);
36
+ if (d.commitUrl) links.push(`- commit \`${(d.commit || "").slice(0, 8)}\`: ${d.commitUrl}`);
37
+ return b.replace(/(##\s*Enlaces relacionados\s*\n)(<!--[\s\S]*?-->)?/i,
38
+ (m, h) => `${h}${links.join("\n")}\n`) === b
39
+ ? b + "\n" + links.join("\n") + "\n" // si no había sección Enlaces, apéndela
40
+ : b.replace(/(##\s*Enlaces relacionados\s*\n)(<!--[\s\S]*?-->)?/i, (m, h) => `${h}${links.join("\n")}\n`);
41
+ }
42
+
43
+ // Título del PR: el pasado a mano, o "<ID>: <título de la US>", o solo el ID.
44
+ export function prTitle(opts, id, usTitle) {
45
+ if (opts.title) return opts.title;
46
+ if (usTitle) return `${id}: ${usTitle}`;
47
+ return id;
48
+ }
49
+
50
+ // Herramienta de CLI del forge según el host del remoto.
51
+ export function forgeTool(forge) {
52
+ return forge === "gitlab" ? "glab" : "gh";
53
+ }
package/cli/lib/us.mjs ADDED
@@ -0,0 +1,36 @@
1
+ // dai · primitivos de US y cobertura, compartidos por todos los backends de PM.
2
+ // Vive aparte de pm-adapter.mjs para que jira/clickup lo importen sin ciclos.
3
+
4
+ import { acHash } from "./ac-hash.mjs";
5
+ import { extractTitle } from "./link-us.mjs";
6
+
7
+ // Parseo puro de una US (markdown o texto) → identidad + hash vivo.
8
+ export function parseUS(raw) {
9
+ const title = extractTitle(raw);
10
+ const m = raw.match(/spec[_ ]version[^\n]*?\b(v\d+)\b/i);
11
+ return { title, spec_version: m ? m[1] : null, ac_hash: acHash(raw) };
12
+ }
13
+
14
+ // Compara el hash estampado (implements.yaml) con el hash vivo de la US.
15
+ export function coverageStatus(stampedHash, liveHash) {
16
+ if (liveHash == null) return "sin-us";
17
+ return stampedHash === liveHash ? "al-dia" : "atrasado";
18
+ }
19
+
20
+ const STATUS_LABEL = { "al-dia": "✅ al día", atrasado: "⚠️ atrasado", "sin-us": "❓ sin US" };
21
+ export const statusLabel = (s) => STATUS_LABEL[s] || s;
22
+
23
+ // Render de la cobertura como markdown (lo que un backend "estampa").
24
+ export function renderCoverage(id, r) {
25
+ const lines = [
26
+ `# Cobertura de ${id} · generado por dai stamp`,
27
+ "",
28
+ "| repo | change | versión | ac_hash | estado |",
29
+ "|------|--------|---------|---------|--------|",
30
+ `| ${r.repo} | ${r.change} | ${r.version} | ${r.ac_hash} | ${statusLabel(r.status)} |`,
31
+ "",
32
+ ];
33
+ if (r.branchUrl) lines.push(`- branch: ${r.branch} → ${r.branchUrl}`);
34
+ if (r.commitUrl) lines.push(`- commit: ${r.commit} → ${r.commitUrl} (ancla durable)`);
35
+ return lines.join("\n") + "\n";
36
+ }
@@ -0,0 +1,330 @@
1
+ # Ejemplo end-to-end — El "golden path"
2
+
3
+ > **Para qué sirve.** Es la pieza que la gente copia. Una User Story real viajando
4
+ > de *idea vaga* a *desplegada*, mostrando **el artefacto concreto que produce cada
5
+ > uno de los 10 pasos** de [`SCRUM-CON-IA.md`](SCRUM-CON-IA.md). Si tienes que
6
+ > mostrar la metodología en una sola lectura, es esta.
7
+ >
8
+ > Dominio del ejemplo: **un carrito de compras** (checkout). Los nombres
9
+ > (`ABC-482`, `frontend`) son ilustrativos.
10
+
11
+ ## El mapa: qué artefacto sale de cada paso
12
+
13
+ | Paso | Skill / evento | Artefacto que produce |
14
+ |---|---|---|
15
+ | 1 Refinamiento | `grill-intent` → `grill-user-story` | `intent.md` + la **US** (`ABC-482`) |
16
+ | 2 Planning | `opsx:propose` | `proposal.md` + `design.md` + `tasks.md` + `specs/` |
17
+ | 3 Rama | `link-us` | branch + `implements.yaml` |
18
+ | 4 TDD | `tdd` | test (RED) → código (GREEN) |
19
+ | 5 Smoke | skill de smoke | escenario end-to-end verde |
20
+ | 6 Code review | review propio del dev → `dai pr` → partner (con `/dai-review`) | PR (código + spec) + aprobación |
21
+ | 7 Merge | `dai stamp` | cobertura estampada en el tracker |
22
+ | 8 Daily | — (humano) | sincronización verbal |
23
+ | 9 Review/Demo | criterios Gherkin | US aceptada por el PO |
24
+ | 10 Retro | — (humano) | 1–2 mejoras |
25
+
26
+ ---
27
+
28
+ ## Paso 1 — Refinamiento: de la idea vaga a la US testeable
29
+
30
+ ### 1a · Cómo empieza (el ticket vago)
31
+
32
+ El PO crea un ticket en el gestor. Nace con **identidad** (`ABC-482`) y poco más:
33
+
34
+ ```
35
+ ABC-482 · "Arreglar lo del carrito que a veces deja comprar de más"
36
+ ```
37
+
38
+ Impresentable para implementar. Acá entra la IA.
39
+
40
+ ### 1b · Gate 0 — `grill-intent` desafía el problema
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
68
+ construcción** ([`formato-us.md`](../templates/formato-us.md)) y la publica en el gestor:
69
+
70
+ ```markdown
71
+ # 🔗 Metadata de trazabilidad
72
+ | Campo | Valor |
73
+ |--------------|------------------------------|
74
+ | ID | ABC-482 |
75
+ | spec_version | v1 |
76
+ | Autor | J. Pérez (PO) |
77
+ | Estado | pulida |
78
+ | Repos esperados | frontend |
79
+
80
+ # Finalizar la compra del carrito
81
+
82
+ ## Historia
83
+ Como **comprador**
84
+ quiero **finalizar la compra de mi carrito**
85
+ para **recibir los productos que elijo sin sorpresas**.
86
+
87
+ ## Casos de uso
88
+ - **Happy path** — el comprador finaliza un carrito con productos con stock → se crea la orden.
89
+ - **Alternativo** — intenta finalizar con el carrito vacío → el sistema lo frena.
90
+ - **Excepción** — un producto del carrito no tiene stock → se rechaza indicando cuál.
91
+
92
+ ## 🔗 Criterios de aceptación
93
+ - [ ] **AC-1** —
94
+ - **Dado** un carrito con productos que tienen stock
95
+ - **Cuando** el comprador finaliza la compra
96
+ - **Entonces** se crea la orden y el carrito queda vacío
97
+ - [ ] **AC-2** —
98
+ - **Dado** un carrito vacío
99
+ - **Cuando** se intenta finalizar la compra
100
+ - **Entonces** el sistema lo rechaza y NO crea ninguna orden
101
+ - [ ] **AC-3** —
102
+ - **Dado** un carrito con un producto sin stock
103
+ - **Cuando** se intenta finalizar la compra
104
+ - **Entonces** se rechaza indicando qué producto no tiene stock
105
+
106
+ ## Fuera de scope
107
+ - El pago (pasarela, tarjetas) es otra US.
108
+
109
+ ## Reglas de negocio
110
+ - Una orden creada descuenta el stock de cada producto.
111
+ ```
112
+
113
+ > Nota que la US **no dice** tablas, endpoints ni framework — solo el QUÉ. Y cada
114
+ > AC es un test en potencia ([Art. 3](./MANIFIESTO.md#art-3) del manifiesto).
115
+
116
+ ---
117
+
118
+ ## Paso 2 — Planning: `opsx:propose` deriva el CÓMO
119
+
120
+ El dev corre `opsx:explore` → `opsx:propose` sobre la US. OpenSpec genera el design
121
+ y las tareas. El dev valida y ajusta:
122
+
123
+ ```markdown
124
+ # design.md (extracto)
125
+ ## Enfoque
126
+ Guard de validación en el caso de uso `FinalizarCompra`. El carrito se valida ANTES
127
+ de crear la orden: no vacío, y todos sus productos con stock.
128
+
129
+ ## Reglas de la transición
130
+ carrito con stock ──finalizar──► orden creada, carrito vacío (permitido)
131
+ carrito vacío ──finalizar──► ✗ CarritoVacioError
132
+ producto sin stock ──finalizar──► ✗ SinStockError(producto)
133
+ ```
134
+
135
+ ```markdown
136
+ # tasks.md (extracto)
137
+ - [ ] T1. Test: finalizar carrito con stock → orden creada, carrito vacío (AC-1)
138
+ - [ ] T2. Test: finalizar carrito vacío → CarritoVacioError, sin orden (AC-2)
139
+ - [ ] T3. Test: producto sin stock → SinStockError con el producto (AC-3)
140
+ - [ ] T4. Guard de validación en FinalizarCompra
141
+ - [ ] T5. Smoke end-to-end del flujo
142
+ ```
143
+
144
+ > Las tareas **nacen del cómo**, definidas por quien va a implementar — no bajadas
145
+ > desde arriba (Art. 1).
146
+
147
+ ---
148
+
149
+ ## Paso 3 — Rama: `link-us` ata el código al QUÉ
150
+
151
+ ```bash
152
+ $ dai link-us ABC-482 --us us.md --change finalizar-compra
153
+ ✓ branch: feature/ABC-482-finalizar-la-compra-del-carrito
154
+ ✓ archivo: openspec/changes/finalizar-compra/implements.yaml (ac_hash 7f3a9c2e)
155
+ ```
156
+
157
+ ```yaml
158
+ # implements.yaml — el ÚNICO link autorado a mano (schema ADR-0004)
159
+ change: finalizar-compra
160
+ repo: frontend
161
+
162
+ implements:
163
+ - id: ABC-482
164
+ version: v1
165
+ ac_hash: 7f3a9c2e # lo calculó `dai ac-hash` sobre los criterios de la US v1
166
+
167
+ introduces:
168
+ - guard-carrito-vacio
169
+
170
+ autor: D. Force (dev)
171
+ ```
172
+
173
+ > El key `ABC-482` **no se tipeó**: salió del argumento. La rama y el link son
174
+ > correctos por construcción (Art. 8, Art. 9).
175
+
176
+ ---
177
+
178
+ ## Paso 4 — TDD: un test a la vez (RED → GREEN)
179
+
180
+ Vertical slice del AC-2 (el guard del carrito vacío). **Primero el test (RED):**
181
+
182
+ ```typescript
183
+ test("un carrito vacío no se puede finalizar", async () => {
184
+ const carrito = await nuevoCarrito({ items: [] });
185
+
186
+ const accion = finalizarCompra(carrito.id);
187
+
188
+ await expect(accion).rejects.toThrow(CarritoVacioError);
189
+ expect(await ordenesDe(carrito.id)).toHaveLength(0); // NO se creó ninguna orden
190
+ });
191
+ // ▶ FALLA: finalizarCompra todavía no valida el carrito.
192
+ ```
193
+
194
+ **Después el código mínimo (GREEN):**
195
+
196
+ ```typescript
197
+ export async function finalizarCompra(id: CarritoId) {
198
+ const carrito = await repo.obtener(id);
199
+ if (carrito.items.length === 0) throw new CarritoVacioError(id);
200
+ const sinStock = carrito.items.filter((i) => !hayStock(i));
201
+ if (sinStock.length > 0) throw new SinStockError(sinStock);
202
+ const orden = await crearOrden(carrito);
203
+ return repo.vaciar(carrito, orden);
204
+ }
205
+ // ▶ VERDE. Repetir el ciclo para AC-1 y AC-3.
206
+ ```
207
+
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`).
210
+
211
+ ---
212
+
213
+ ## Paso 5 — Smoke: el flujo entero, verde
214
+
215
+ ```
216
+ $ smoke checkout
217
+ ✓ carrito con stock → orden creada, carrito vacío
218
+ ✓ carrito vacío → rechazado, sin orden
219
+ ✓ producto sin stock → rechazado indicando el producto
220
+ SMOKE OK (3/3)
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Paso 6 — Code review: el dev primero, después un partner
226
+
227
+ **Primero el dev revisa la implementación de la IA** — minucioso y con criterio
228
+ (correctitud, casos borde, seguridad, calidad). El dev es responsable del código, no la IA
229
+ (anti vibe-coding). Ajusta y **commitea** lo que haga falta.
230
+
231
+ Con el smoke verde y **todo commiteado** (lo que quede suelto no entra en la PR), crea la
232
+ PR con `dai pr` — precargada con la US, el estado del check y los links (los **dos activos**:
233
+ código + spec trazable) — y la asigna a un partner:
234
+
235
+ ```
236
+ $ dai check
237
+ ✅ ABC-482 al día (v1)
238
+ $ dai pr --assignee mgomez
239
+ ✓ PR #123 creada → …/pull/123 (base: main · US: ABC-482 @ v1 · dai check ✅)
240
+ ```
241
+
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:
244
+
245
+ ```
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.
251
+
252
+ 👤 M. Gómez (partner): de acuerdo con el DomainError. Aprobado tras el ajuste.
253
+ ```
254
+
255
+ > 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.
257
+
258
+ ---
259
+
260
+ ## Paso 7 — Merge: la trazabilidad se estampa sola
261
+
262
+ Al mergear, se corre `dai stamp` (el dev, o el CI si está automatizado — ADR-0003).
263
+ Lee el `implements.yaml` y **estampa la cobertura inversa** en el ticket `ABC-482`,
264
+ con links a la implementación:
265
+
266
+ ```
267
+ ABC-482 · implementado por (lo estampó dai stamp)
268
+ ┌──────────┬───────────────────┬─────────┬──────────┬───────────┐
269
+ │ repo │ change │ versión │ ac_hash │ estado │
270
+ ├──────────┼───────────────────┼─────────┼──────────┼───────────┤
271
+ │ frontend │ finalizar-compra │ v1 │ 7f3a9c2e │ ✅ al día │
272
+ └──────────┴───────────────────┴─────────┴──────────┴───────────┘
273
+ branch → …/tree/feature/ABC-482-finalizar-la-compra-del-carrito
274
+ commit → …/commit/abc123 (ancla durable)
275
+ ```
276
+
277
+ > El estado se **deriva**, no se reporta (Art. 10). El link (branch + commit) hace
278
+ > que el ticket sea un router hacia la implementación real (§2.5).
279
+
280
+ ---
281
+
282
+ ## Paso 8 — Daily *(humano, a propósito)*
283
+
284
+ > *"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).
286
+
287
+ ## Paso 9 — Review / Demo
288
+
289
+ El PO valida contra los **mismos criterios que ya eran tests**. Finaliza un carrito,
290
+ prueba con uno vacío, prueba con un producto sin stock, ve los rechazos. AC-1, AC-2,
291
+ AC-3 verdes → **US aceptada**. Cero sorpresas: si el QUÉ hubiera cambiado, el
292
+ `@version` lo habría gritado antes.
293
+
294
+ ## Paso 10 — Retro *(humano)*
295
+
296
+ > *"El Gate 0 nos ahorró rediseñar el stock, que no hacía falta. Mejora para el
297
+ > próximo sprint: sumar el smoke al pipeline y no correrlo a mano."* La matriz de
298
+ > trazabilidad aportó el dato; la decisión la tomó el equipo (Art. 6).
299
+
300
+ ---
301
+
302
+ ## Epílogo — Y cuando el QUÉ cambia (el `@version` gritando solo)
303
+
304
+ Dos sprints después, el PO agrega un criterio a `ABC-482` (ahora exige **avisar al
305
+ comprador qué productos quedaron sin stock, sin cancelar el resto del carrito**).
306
+ Sube la US a **v2** → cambia el `ac_hash`.
307
+
308
+ ```
309
+ ABC-482 · implementado por
310
+ ┌──────────┬───────────────────┬─────────┬──────────┬────────────────────────────┐
311
+ │ repo │ change │ versión │ ac_hash │ estado │
312
+ ├──────────┼───────────────────┼─────────┼──────────┼────────────────────────────┤
313
+ │ frontend │ finalizar-compra │ v1 │ 7f3a9c2e │ ⚠️ ATRASADO (la US es v2) │
314
+ └──────────┴───────────────────┴─────────┴──────────┴────────────────────────────┘
315
+ ```
316
+
317
+ Nadie le avisó al dev: `dai check` lo marcó solo al re-derivar el hash de la US viva
318
+ y compararlo con el estampado (Art. 11). El dev abre una nueva iteración —mismo
319
+ `ABC-482`, ahora contra v2— y el ciclo vuelve a empezar desde el paso 3.
320
+
321
+ ---
322
+
323
+ ## Qué demuestra este recorrido
324
+
325
+ - **Los 10 pasos son tu Scrum de siempre** — solo que en cada uno hay una skill.
326
+ - **El link nunca se escribió dos veces**: `implements.yaml` una vez, la cobertura
327
+ se derivó.
328
+ - **Nada llegó a producción sin ser testeable y trazable** (Arts. 3, 9, 10).
329
+ - **Los rituales humanos siguieron siendo humanos** (Art. 6).
330
+ - Y cuando el negocio cambió, **el desajuste se hizo visible solo** (Art. 11).
@@ -0,0 +1,114 @@
1
+ # Manifiesto — Desarrollo Asistido por IA
2
+
3
+ > **Qué es esto.** La *constitución* de la metodología: los principios inmutables
4
+ > contra los que se mide toda decisión. Cuando `grill-intent` desafía un problema,
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
7
+ > memoria no gobierna nada.
8
+ >
9
+ > **Cómo se usa.** Es el input de constitución que las skills asumen (junto con
10
+ > `openspec/project.md` y `CLAUDE.md`). No se negocia por conveniencia de un
11
+ > sprint. Se cambia por decisión explícita del equipo, registrada como ADR.
12
+
13
+ ---
14
+
15
+ ## Los cuatro valores
16
+
17
+ 1. **El QUÉ y el CÓMO son cosas distintas, con dueños distintos.**
18
+ 2. **La IA asiste; la persona decide.**
19
+ 3. **Nada existe si no se puede testear ni trazar.**
20
+ 4. **La ceremonia se agrega cuando duele, no antes.**
21
+
22
+ Todo lo que sigue son artículos que hacen operativos esos cuatro valores.
23
+
24
+ ---
25
+
26
+ ## I. Sobre el QUÉ y el CÓMO
27
+
28
+ <a id="art-1"></a>**Art. 1 — Separación de responsabilidades.**
29
+ El *funcional* define **qué** hay que hacer y **por qué**. El *técnico* define
30
+ **cómo**. Nadie invade el terreno del otro: la US no dice endpoints ni tablas; el
31
+ design no re-discute el valor de negocio.
32
+
33
+ <a id="art-2"></a>**Art. 2 — El QUÉ es tool-agnóstico.**
34
+ El requerimiento vive con una identidad y una forma mínima que **no dependen de la
35
+ herramienta de abajo** (OpenSpec, Swagger, YAML, Markdown). Cambiar la herramienta
36
+ técnica no rompe el link.
37
+
38
+ <a id="art-3"></a>**Art. 3 — Testeable o no existe.**
39
+ Un criterio de aceptación que no se puede volver un test, no es un criterio. *"El
40
+ usuario tiene una buena experiencia"* se rechaza. *"Un carrito vacío no se puede
41
+ finalizar"* se acepta. Si no es verificable, no entra.
42
+
43
+ ## II. Sobre la IA y el humano (HITL)
44
+
45
+ <a id="art-4"></a>**Art. 4 — La IA saca a preguntas, no inventa.**
46
+ El QUÉ se produce **por interrogación** (`grill-*`), no por generación. La IA no
47
+ adivina requerimientos: presiona hasta que la persona los explicita. Un QUÉ que la
48
+ IA "completó sola" es una alucinación con formato lindo.
49
+
50
+ <a id="art-5"></a>**Art. 5 — La persona firma.**
51
+ Toda decisión irreversible o de negocio —aceptar una US, aprobar un PR, descartar
52
+ un problema— la toma y la firma un humano. La IA propone; nunca autoriza.
53
+
54
+ <a id="art-6"></a>**Art. 6 — Los rituales de coordinación son humanos.**
55
+ El *daily* y la *retro* se hacen a mano, a propósito. Son donde el equipo se
56
+ apropia del proceso y lo entiende. La IA puede darles datos; no los reemplaza.
57
+
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
60
+ bien definida, pasa por un design, y se construye con tests. La disciplina no es
61
+ opcional: es lo que separa este método de "pedirle cosas a un chat".
62
+
63
+ ## III. Sobre la trazabilidad
64
+
65
+ <a id="art-8"></a>**Art. 8 — Identidad estable.**
66
+ Todo QUÉ nace con un ID único, independiente del path y del formato. Es lo único
67
+ que lo hace linkeable. No se inventa un esquema nuevo: es el ticket del gestor (o
68
+ el change en el nivel más chico).
69
+
70
+ <a id="art-9"></a>**Art. 9 — El link se autora una sola vez.**
71
+ El CÓMO declara `implements: <id>@<version>`. Es el **único** link escrito a mano.
72
+ La dirección inversa (quién implementó qué) **siempre se deriva, nunca se escribe**.
73
+ Escribirlo en los dos lados es firmar la desincronización.
74
+
75
+ <a id="art-10"></a>**Art. 10 — El estado se deriva, no se reporta.**
76
+ La matriz de trazabilidad —quién implementó qué, contra qué versión, quién quedó
77
+ atrasado— no la mantiene ninguna persona. La calcula la máquina a partir de los
78
+ links. Si alguien la actualiza a mano, algo está mal diseñado.
79
+
80
+ <a id="art-11"></a>**Art. 11 — El versionado avisa solo.**
81
+ El `@version` (número legible + hash de criterios) hace que un cambio del QUÉ marque
82
+ solo a los CÓMO atrasados. Nadie avisa a nadie: el link versionado lo grita. El
83
+ número comunica; el hash detecta.
84
+
85
+ <a id="art-12"></a>**Art. 12 — Capacidad entera, detalle on-demand.**
86
+ El link es a nivel de capacidad/US entera, no criterio-por-criterio. Si el QUÉ sabe
87
+ *quién* lo implementó, el detalle fino se resuelve leyendo el repo por ID. El índice
88
+ central es un **router, no un almacén**.
89
+
90
+ ## IV. Sobre la escala
91
+
92
+ <a id="art-13"></a>**Art. 13 — Un protocolo, varios niveles de ceremonia.**
93
+ No hay una metodología para equipos chicos y otra para grandes. Hay **un** protocolo
94
+ invariante (Arts. 1–12) y un dial de ceremonia que sube o baja según la escala. El
95
+ que aprende el nivel chico ya sabe el grande.
96
+
97
+ <a id="art-14"></a>**Art. 14 — No adelantar complejidad.**
98
+ Cada capa de plomería (tracker externo, CI que estampa, matriz de ambientes) se
99
+ agrega **cuando duele, no antes**. Empezar con la maquinaria completa "por las
100
+ dudas" es tan malo como no tener método.
101
+
102
+ <a id="art-15"></a>**Art. 15 — Los roles se colapsan, el link no.**
103
+ Cuando una misma persona es autor del QUÉ y del CÓMO, los gates se aligeran (un
104
+ auto-check honesto en vez de la firma de otro), pero el link `implements` **sigue
105
+ existiendo**. La ceremonia se achica; la trazabilidad no se negocia.
106
+
107
+ ---
108
+
109
+ ## Cómo se enmienda
110
+
111
+ Estos artículos se cambian solo por **decisión explícita del equipo**, registrada
112
+ como un ADR con fecha y motivo. Ningún sprint, deadline ni "esta vez es distinto"
113
+ alcanza para saltárselos en silencio. Si un artículo estorba seguido, esa es la
114
+ señal de que hay que debatirlo y enmendarlo — no de ignorarlo.