@dforce2055/dai 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/VERSION +1 -1
- package/cli/dai.mjs +44 -28
- package/cli/lib/args.mjs +6 -0
- package/cli/lib/bootstrap.mjs +68 -2
- package/cli/lib/env.mjs +6 -0
- package/docs/adr/0010-versionado-y-upgrade.md +122 -0
- package/docs/adr/README.md +1 -0
- package/package.json +1 -2
- package/templates/formato-us.md +41 -0
- package/index.html +0 -481
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,53 @@
|
|
|
3
3
|
Formato basado en [Keep a Changelog](https://keepachangelog.com/). Versionado semver
|
|
4
4
|
(ver `VERSION`).
|
|
5
5
|
|
|
6
|
+
## [0.3.1] — 2026-07-10
|
|
7
|
+
|
|
8
|
+
Pulido de la experiencia de `dai init` y `dai link-us`, y un ejemplo de US listo para probar.
|
|
9
|
+
|
|
10
|
+
### Corregido
|
|
11
|
+
- **`dai init` · `.env.example`**: ahora refleja el `--pm` elegido — incluye todas las claves del
|
|
12
|
+
tracker (con el `..._TOKEN`), en vez del template genérico `md`. Antes, al elegir jira/clickup,
|
|
13
|
+
el `.env.example` quedaba con `DAI_PM=md` y sin el token.
|
|
14
|
+
- **`dai init` · `--for` con espacio**: mensaje claro cuando `--for claude, cursor` (con espacio) hace
|
|
15
|
+
que la shell parta la lista y un token de asistente caiga como `<repo>` ("no existe el directorio: cursor").
|
|
16
|
+
- **`dai link-us` · `dai ac-hash`**: mensaje accionable cuando la US no tiene sección
|
|
17
|
+
'Criterios de aceptación' — sugiere agregarla o correr `/grill-user-story <ID>`.
|
|
18
|
+
|
|
19
|
+
### Agregado
|
|
20
|
+
- **`templates/formato-us.md`**: ejemplo de US copy-paste al final (con criterios testeables) para
|
|
21
|
+
probar el flujo en 30 segundos, con o sin tracker (`dai ac-hash` / `dai link-us --us … --dry-run`).
|
|
22
|
+
|
|
23
|
+
### Interno
|
|
24
|
+
- **105 tests** (+1 desde 0.3.0: `isAssistantToken`).
|
|
25
|
+
|
|
26
|
+
## [0.3.0] — 2026-07-08
|
|
27
|
+
|
|
28
|
+
`dai init` ahora es **aditivo**: no pisa la configuración de un repo funcional. Más
|
|
29
|
+
robustez en el parser de `.env` y en `doctor`, y nuevas buenas prácticas agnósticas en
|
|
30
|
+
la constitución.
|
|
31
|
+
|
|
32
|
+
### Cambiado
|
|
33
|
+
- **`dai init` es aditivo y no destructivo** sobre un repo con config existente:
|
|
34
|
+
- `.env` / `.env.example`: mergea solo las claves de dai que faltan (no reescribe lo del proyecto).
|
|
35
|
+
- `CLAUDE.md` / `copilot-instructions.md`: inserta la constitución como bloque delimitado
|
|
36
|
+
(`<!-- dai:start/end -->`), idempotente — conserva la constitución previa del proyecto.
|
|
37
|
+
- `.gitignore`: reconcilia para versionar skills y comandos (`.claude/skills/`, `.claude/commands/`),
|
|
38
|
+
dejando fuera solo lo personal (`settings.local.json`); quita ignores "broad" (`.claude/`, `CLAUDE.md`)
|
|
39
|
+
que los escondían.
|
|
40
|
+
- **Constitución**: nuevas reglas agnósticas (verificar el comportamiento ≠ que compile, la IA confirma
|
|
41
|
+
antes de construir, docs vivas) + sección "Buenas prácticas (agnósticas)". Tono "tú" neutro.
|
|
42
|
+
|
|
43
|
+
### Corregido
|
|
44
|
+
- **Parser de `.env`** (`env.mjs`): recorta el comentario inline en valores sin comillas — un token con
|
|
45
|
+
`# ...` al lado ya no rompe el header `Authorization` (error de ByteString).
|
|
46
|
+
- **`dai doctor`**: enumera solo directorios; ya no lista `.DS_Store` como skill.
|
|
47
|
+
|
|
48
|
+
### Interno
|
|
49
|
+
- **104 tests** (+11 desde 0.2.0: `mergeEnv`, `upsertBlock`, `reconcileGitignore`, parser de `env.mjs`).
|
|
50
|
+
- El sitio (`index.html`, `onboarding.html`) sale del paquete npm (`files[]`) — es capa visual del repo/web.
|
|
51
|
+
- Nueva página de onboarding del dev (`onboarding.html`) en el sitio de GitHub Pages.
|
|
52
|
+
|
|
6
53
|
## [0.2.0] — 2026-07-08
|
|
7
54
|
|
|
8
55
|
Soporte para **Cursor** como asistente y un `--for` combinable. Incluye la **primera
|
|
@@ -75,6 +122,8 @@ ClickUp y Jira Cloud.
|
|
|
75
122
|
- Tests de las rutas de red (jira/clickup/forge) con `fetch` mockeado. Sin links rotos;
|
|
76
123
|
`files` de npm sin tests ni secretos.
|
|
77
124
|
|
|
125
|
+
[0.3.1]: https://github.com/dforce2055/dai/releases/tag/v0.3.1
|
|
126
|
+
[0.3.0]: https://github.com/dforce2055/dai/releases/tag/v0.3.0
|
|
78
127
|
[0.2.0]: https://github.com/dforce2055/dai/releases/tag/v0.2.0
|
|
79
128
|
[0.1.1]: https://github.com/dforce2055/dai/releases/tag/v0.1.1
|
|
80
129
|
[0.1.0]: https://github.com/dforce2055/dai/releases/tag/v0.1.0
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.3.1
|
package/cli/dai.mjs
CHANGED
|
@@ -27,8 +27,8 @@ import { branchUrl, commitUrl, parseRemote, detectForge } from "./lib/forge-url.
|
|
|
27
27
|
import { parsePrRef, getPR, postComment } from "./lib/forge-api.mjs";
|
|
28
28
|
import { composePrBody, prTitle, forgeTool } from "./lib/pr.mjs";
|
|
29
29
|
import { dirsEqual } from "./lib/fsutil.mjs";
|
|
30
|
-
import { parseFlags, parseAssistants } from "./lib/args.mjs";
|
|
31
|
-
import { skillToPrompt, skillToCursor, constitution, constitutionCursorRule, envFor } from "./lib/bootstrap.mjs";
|
|
30
|
+
import { parseFlags, parseAssistants, isAssistantToken } from "./lib/args.mjs";
|
|
31
|
+
import { skillToPrompt, skillToCursor, constitution, constitutionCursorRule, envFor, mergeEnv, upsertBlock, reconcileGitignore } from "./lib/bootstrap.mjs";
|
|
32
32
|
|
|
33
33
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
34
34
|
|
|
@@ -59,7 +59,7 @@ function trackerUrl(id) {
|
|
|
59
59
|
function cmdAcHash(arg) {
|
|
60
60
|
const md = arg ? readFileSync(arg, "utf8") : readFileSync(0, "utf8");
|
|
61
61
|
const h = acHash(md);
|
|
62
|
-
if (h == null) fail("la US no tiene bloque de 'Criterios de aceptación'.", 2);
|
|
62
|
+
if (h == null) fail("la US no tiene un bloque 'Criterios de aceptación' (criterios testeables bajo '## Criterios de aceptación').", 2);
|
|
63
63
|
process.stdout.write(h + "\n");
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -98,7 +98,7 @@ async function cmdLinkUs(key, opts) {
|
|
|
98
98
|
// Fuente local: un .md con la US.
|
|
99
99
|
const md = readFileSync(opts.us, "utf8");
|
|
100
100
|
hash = acHash(md);
|
|
101
|
-
if (hash == null) fail(
|
|
101
|
+
if (hash == null) fail(`la US en ${opts.us} no tiene una sección 'Criterios de aceptación' con criterios testeables → sin ac_hash.\n Agregá los criterios bajo '## Criterios de aceptación', o corré /grill-user-story para pulir la US.`, 2);
|
|
102
102
|
title = opts.title || extractTitle(md);
|
|
103
103
|
} else {
|
|
104
104
|
// Fuente tracker: traer la US del adaptador (mismo hash que usará `dai check`).
|
|
@@ -107,7 +107,7 @@ async function cmdLinkUs(key, opts) {
|
|
|
107
107
|
const us = await adapter.fetchUS(key);
|
|
108
108
|
if (!us) fail(`no encontré la US ${key} en el backend ${adapter.kind}. Pasa --us <md> o revisa el .env.`, 2);
|
|
109
109
|
hash = us.ac_hash;
|
|
110
|
-
if (hash == null) fail(`la US ${key} no tiene 'Criterios de aceptación' → sin ac_hash.`, 2);
|
|
110
|
+
if (hash == null) fail(`la US ${key} no tiene una sección 'Criterios de aceptación' con criterios testeables → sin ac_hash, no se puede linkear.\n Agregá la sección en el tracker, o corré /grill-user-story ${key} para pulir la US (te interroga y la re-publica).`, 2);
|
|
111
111
|
title = opts.title || us.title;
|
|
112
112
|
version = us.spec_version || "v1";
|
|
113
113
|
}
|
|
@@ -500,21 +500,16 @@ async function askYesNo(rl, q, def = false) {
|
|
|
500
500
|
return ["s", "si", "sí", "y", "yes"].includes(a);
|
|
501
501
|
}
|
|
502
502
|
|
|
503
|
-
// Asegura que ciertas entradas estén en el .gitignore del repo (crea o agrega).
|
|
504
|
-
function ensureGitignored(repo, entries) {
|
|
505
|
-
const gi = join(repo, ".gitignore");
|
|
506
|
-
const cur = existsSync(gi) ? readFileSync(gi, "utf8") : "";
|
|
507
|
-
const have = new Set(cur.split(/\r?\n/).map((s) => s.trim()));
|
|
508
|
-
const add = entries.filter((e) => !have.has(e));
|
|
509
|
-
if (add.length === 0) return false;
|
|
510
|
-
writeFileSync(gi, cur + (cur && !cur.endsWith("\n") ? "\n" : "") + "\n# dai — secretos, no commitear\n" + add.join("\n") + "\n");
|
|
511
|
-
return true;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
503
|
// ── init: scaffolder interactivo del repo ─────────────────────────────────────
|
|
515
504
|
async function cmdInit(repo, opts) {
|
|
516
505
|
repo = repo || "."; // por defecto, el directorio actual (como git init / npm init)
|
|
517
|
-
if (!existsSync(repo))
|
|
506
|
+
if (!existsSync(repo)) {
|
|
507
|
+
// Error común: `--for claude, cursor` con espacio → la shell parte y 'cursor' cae acá como repo.
|
|
508
|
+
const hint = isAssistantToken(repo)
|
|
509
|
+
? `\n ¿Separaste --for con un espacio? '${repo}' quedó como <repo>. Usá coma SIN espacio: --for claude,cursor`
|
|
510
|
+
: "";
|
|
511
|
+
fail(`no existe el directorio: ${repo}${hint}`);
|
|
512
|
+
}
|
|
518
513
|
const rl = process.stdin.isTTY ? createInterface({ input: process.stdin, output: process.stdout }) : null;
|
|
519
514
|
|
|
520
515
|
process.stdout.write("\n dai · configurar este repo para desarrollo asistido por IA\n");
|
|
@@ -568,11 +563,28 @@ async function cmdInit(repo, opts) {
|
|
|
568
563
|
writeFileSync(join(dai, "VERSION"), readFileSync(join(ROOT, "VERSION"), "utf8"));
|
|
569
564
|
ok(".dai/ moldes (templates) + reglas (governance) del método");
|
|
570
565
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
if (
|
|
566
|
+
// .env.example — aditivo, reflejando el --pm elegido: mismas claves que el .env
|
|
567
|
+
// (con valores VACÍOS, sin secretos) para que el token del tracker esté presente.
|
|
568
|
+
const exSrc = envFor(pm);
|
|
569
|
+
const exPath = join(repo, ".env.example");
|
|
570
|
+
if (existsSync(exPath)) {
|
|
571
|
+
const cur = readFileSync(exPath, "utf8"), merged = mergeEnv(cur, exSrc);
|
|
572
|
+
if (merged !== cur) { writeFileSync(exPath, merged); ok(".env.example claves de dai agregadas (aditivo)"); }
|
|
573
|
+
else ok(".env.example ya tenía la config de dai");
|
|
574
|
+
} else { writeFileSync(exPath, exSrc); ok(".env.example creado"); }
|
|
575
|
+
|
|
576
|
+
// .env — aditivo: agrega las claves de dai que falten; si no existe, lo crea.
|
|
577
|
+
const envPath = join(repo, ".env"), envBlock = envFor(pm);
|
|
578
|
+
if (existsSync(envPath)) {
|
|
579
|
+
const cur = readFileSync(envPath, "utf8"), merged = mergeEnv(cur, envBlock);
|
|
580
|
+
if (merged !== cur) { writeFileSync(envPath, merged); ok(`.env claves de dai agregadas (aditivo, DAI_PM=${pm}${pm === "md" ? "" : " — completa el token"})`); }
|
|
581
|
+
else ok(".env ya tenía la config de dai");
|
|
582
|
+
} else { writeFileSync(envPath, envBlock); ok(`.env listo, DAI_PM=${pm}${pm === "md" ? "" : " (completa el token)"}`); }
|
|
583
|
+
|
|
584
|
+
// .gitignore — versiona los artefactos de dai (según --for), deja fuera solo lo personal.
|
|
585
|
+
const giPath = join(repo, ".gitignore");
|
|
586
|
+
const gi = reconcileGitignore(existsSync(giPath) ? readFileSync(giPath, "utf8") : "", want);
|
|
587
|
+
if (gi.changed) { writeFileSync(giPath, gi.text.endsWith("\n") ? gi.text : gi.text + "\n"); ok(".gitignore ajustado (skills/constitución versionadas; .env y settings.local.json fuera)"); }
|
|
576
588
|
|
|
577
589
|
mkdirSync(join(repo, ".github"), { recursive: true });
|
|
578
590
|
cpSync(join(ROOT, "templates", "pull-request.md"), join(repo, ".github", "pull_request_template.md"));
|
|
@@ -585,15 +597,17 @@ async function cmdInit(repo, opts) {
|
|
|
585
597
|
const dir = join(repo, ".claude", "skills");
|
|
586
598
|
mkdirSync(dir, { recursive: true });
|
|
587
599
|
for (const name of skills) cpSync(join(skillsSrc, name), join(dir, name), { recursive: true });
|
|
588
|
-
|
|
589
|
-
|
|
600
|
+
const cPath = join(repo, "CLAUDE.md"), cCur = existsSync(cPath) ? readFileSync(cPath, "utf8") : "";
|
|
601
|
+
writeFileSync(cPath, upsertBlock(cCur, constitution("claude")));
|
|
602
|
+
ok(`Claude: .claude/skills/ (${skills.length}) + CLAUDE.md${cCur ? " (bloque dai, aditivo)" : ""} → conoce el método y las skills`);
|
|
590
603
|
}
|
|
591
604
|
if (wantCopilot) {
|
|
592
605
|
const pdir = join(repo, ".github", "prompts");
|
|
593
606
|
mkdirSync(pdir, { recursive: true });
|
|
594
607
|
for (const name of skills) writeFileSync(join(pdir, `${name}.prompt.md`), skillToPrompt(readFileSync(join(skillsSrc, name, "SKILL.md"), "utf8")));
|
|
595
|
-
|
|
596
|
-
|
|
608
|
+
const ciPath = join(repo, ".github", "copilot-instructions.md"), ciCur = existsSync(ciPath) ? readFileSync(ciPath, "utf8") : "";
|
|
609
|
+
writeFileSync(ciPath, upsertBlock(ciCur, constitution("copilot")));
|
|
610
|
+
ok(`Copilot: .github/prompts/ (${skills.length}) + copilot-instructions.md${ciCur ? " (bloque dai, aditivo)" : ""}`);
|
|
597
611
|
}
|
|
598
612
|
if (wantCursor) {
|
|
599
613
|
const dir = join(repo, ".cursor", "skills");
|
|
@@ -670,9 +684,11 @@ function cmdDoctor() {
|
|
|
670
684
|
|
|
671
685
|
// Una skill sirve si está en el repo actual (.claude/skills, la puso `dai init`)
|
|
672
686
|
// O global (~/.claude/skills, la puso `dai install`). Reportamos dónde.
|
|
687
|
+
const skillsRoot = join(ROOT, "skills");
|
|
688
|
+
const skillNames = readdirSync(skillsRoot).filter((n) => statSync(join(skillsRoot, n)).isDirectory());
|
|
673
689
|
const localDir = join(process.cwd(), ".claude", "skills");
|
|
674
690
|
info("skills (repo local / global ~/.claude/skills):");
|
|
675
|
-
for (const name of
|
|
691
|
+
for (const name of skillNames) {
|
|
676
692
|
const local = existsSync(join(localDir, name)), global = existsSync(join(CLAUDE_SKILLS_DIR, name));
|
|
677
693
|
if (local && global) ok(`${name} (local + global)`);
|
|
678
694
|
else if (local) ok(`${name} (local, este repo)`);
|
|
@@ -682,7 +698,7 @@ function cmdDoctor() {
|
|
|
682
698
|
|
|
683
699
|
const cursorLocalDir = join(process.cwd(), ".cursor", "skills");
|
|
684
700
|
info("skills Cursor (repo local / global ~/.cursor/skills):");
|
|
685
|
-
for (const name of
|
|
701
|
+
for (const name of skillNames) {
|
|
686
702
|
const local = existsSync(join(cursorLocalDir, name)), global = existsSync(join(CURSOR_SKILLS_DIR, name));
|
|
687
703
|
if (local && global) ok(`${name} (local + global)`);
|
|
688
704
|
else if (local) ok(`${name} (local, este repo)`);
|
package/cli/lib/args.mjs
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
export const camel = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
7
7
|
|
|
8
|
+
// Tokens válidos de `--for`. Sirve para detectar el error común de separar la lista
|
|
9
|
+
// con un espacio (`--for claude, cursor`): la shell parte antes de que dai lo vea, y
|
|
10
|
+
// `cursor` cae como posicional (el `<repo>`). Con esto damos un hint claro.
|
|
11
|
+
const ASSISTANT_TOKENS = new Set(["claude", "copilot", "cursor", "both", "all"]);
|
|
12
|
+
export const isAssistantToken = (s) => ASSISTANT_TOKENS.has(String(s ?? "").toLowerCase());
|
|
13
|
+
|
|
8
14
|
// Parsea el valor de `--for` como una LISTA COMBINABLE de asistentes.
|
|
9
15
|
// Acepta "claude", "copilot", "cursor" (combinables con coma o espacio),
|
|
10
16
|
// "both" (= claude+copilot) y "all" (= los tres). Lanza si hay un token inválido.
|
package/cli/lib/bootstrap.mjs
CHANGED
|
@@ -53,11 +53,66 @@ export function envFor(pm) {
|
|
|
53
53
|
return head + "DAI_PM=md\nDAI_MD_US_DIR=.dai/us\n";
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
// ── Helpers aditivos para `dai init` — no destruir la config de un repo vivo ──
|
|
57
|
+
|
|
58
|
+
// Une aditivamente variables de entorno: agrega solo las claves KEY= que NO estén
|
|
59
|
+
// ya definidas en `existing`. Idempotente; no toca lo demás. Ignora comentarios.
|
|
60
|
+
export function mergeEnv(existing, block) {
|
|
61
|
+
const keyOf = (line) => {
|
|
62
|
+
const m = line.match(/^\s*(?:export\s+)?([\w.-]+)\s*=/);
|
|
63
|
+
return m ? m[1] : null;
|
|
64
|
+
};
|
|
65
|
+
const have = new Set(existing.split(/\r?\n/).map(keyOf).filter(Boolean));
|
|
66
|
+
const toAdd = block.split(/\r?\n/).filter((line) => { const k = keyOf(line); return k && !have.has(k); });
|
|
67
|
+
if (toAdd.length === 0) return existing;
|
|
68
|
+
const sep = existing && !existing.endsWith("\n") ? "\n" : "";
|
|
69
|
+
return existing + sep + "\n# ── dai ──\n" + toAdd.join("\n") + "\n";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Inserta o actualiza un bloque delimitado (marcadores HTML) en una constitución,
|
|
73
|
+
// sin pisar el resto del archivo. Idempotente: re-correr actualiza el bloque, no duplica.
|
|
74
|
+
export function upsertBlock(existing, block, marker = "dai") {
|
|
75
|
+
const start = `<!-- ${marker}:start -->`, end = `<!-- ${marker}:end -->`;
|
|
76
|
+
const wrapped = `${start}\n${block.trim()}\n${end}`;
|
|
77
|
+
const i = existing.indexOf(start), j = existing.indexOf(end);
|
|
78
|
+
if (i !== -1 && j > i) {
|
|
79
|
+
return existing.slice(0, i) + wrapped + existing.slice(j + end.length);
|
|
80
|
+
}
|
|
81
|
+
const base = existing.trimEnd();
|
|
82
|
+
return base ? `${base}\n\n${wrapped}\n` : `${wrapped}\n`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Ajusta el texto de un .gitignore para que los artefactos de dai (según --for)
|
|
86
|
+
// queden versionados, dejando fuera SOLO lo personal (settings.local.json). Quita
|
|
87
|
+
// los ignores "broad" (.claude, CLAUDE.md, .cursor) que los esconderían. Aditivo e
|
|
88
|
+
// idempotente. Devuelve { text, changed }.
|
|
89
|
+
export function reconcileGitignore(text, want) {
|
|
90
|
+
const norm = (s) => { let t = s.trim(); while (t.startsWith("/")) { t = t.slice(1); } while (t.endsWith("/")) { t = t.slice(0, -1); } return t; };
|
|
91
|
+
const broad = new Set();
|
|
92
|
+
const ensure = [".env"];
|
|
93
|
+
if (want.claude) { broad.add("CLAUDE.md"); broad.add(".claude"); ensure.push(".claude/settings.local.json"); }
|
|
94
|
+
if (want.cursor) { broad.add(".cursor"); }
|
|
95
|
+
let changed = false;
|
|
96
|
+
let lines = text.split(/\r?\n/).filter((line) => {
|
|
97
|
+
if (line.trim().startsWith("#")) return true;
|
|
98
|
+
if (broad.has(norm(line))) { changed = true; return false; }
|
|
99
|
+
return true;
|
|
100
|
+
});
|
|
101
|
+
const have = new Set(lines.map((l) => l.trim()));
|
|
102
|
+
const add = ensure.filter((e) => !have.has(e));
|
|
103
|
+
if (add.length) {
|
|
104
|
+
changed = true;
|
|
105
|
+
if (lines.length && lines[lines.length - 1].trim() !== "") lines.push("");
|
|
106
|
+
lines.push("# dai — solo lo personal fuera del control de versiones", ...add);
|
|
107
|
+
}
|
|
108
|
+
return { text: lines.join("\n"), changed };
|
|
109
|
+
}
|
|
110
|
+
|
|
56
111
|
// La constitución del repo: se inyecta como CLAUDE.md (Claude) o
|
|
57
112
|
// copilot-instructions.md (Copilot). Mismo núcleo, distinto encabezado.
|
|
58
113
|
export function constitution(kind) {
|
|
59
114
|
const head = kind === "copilot"
|
|
60
|
-
? "# Instrucciones de Copilot para este repo\n\nEste repo sigue la metodología **dai**.
|
|
115
|
+
? "# Instrucciones de Copilot para este repo\n\nEste repo sigue la metodología **dai**. Aplica estas reglas en todo lo que generes.\n\n> **Superficie:** los prompts de dai (`.github/prompts/`) se invocan solo en VS Code /\n> JetBrains, o como custom agents en el Copilot CLI — no en la app standalone ni en\n> github.com. El CLI `dai` corre en cualquier terminal."
|
|
61
116
|
: kind === "cursor"
|
|
62
117
|
? "# Constitución del proyecto (dai)\n\nEste repo sigue la metodología **dai**. Estas reglas gobiernan todo el trabajo.\n\n> **Superficie:** las skills de dai (`.cursor/skills/`) se invocan con `/nombre-skill`\n> o cuando el agente las detecta por descripción. El CLI `dai` corre en cualquier terminal."
|
|
63
118
|
: "# Constitución del proyecto (dai)\n\nEste repo sigue la metodología **dai**. Estas reglas gobiernan todo el trabajo.";
|
|
@@ -75,8 +130,19 @@ export function constitution(kind) {
|
|
|
75
130
|
- **No vibe coding:** toda implementación arranca de una US con criterios testeables.
|
|
76
131
|
- **TDD:** test primero, por la interfaz pública; sobrevive a un refactor.
|
|
77
132
|
- **El link se autora una vez** (\`implements.yaml\`); la cobertura se **deriva** (nunca a mano).
|
|
133
|
+
- **Verifica el comportamiento, no solo que compile:** que pase el chequeo estático o el build no prueba que funcione; ejercita el flujo real antes de darlo por hecho.
|
|
134
|
+
- **La IA confirma antes de construir:** el asistente declara que entendió esta constitución y la va a obedecer antes de generar código.
|
|
78
135
|
- **Secretos:** en \`.env\` (nunca commiteados). git por **SSH**, APIs por **token scopeado**.
|
|
79
|
-
-
|
|
136
|
+
- **Docs vivas:** una constitución o arquitectura desactualizada es un defecto, no documentación.
|
|
137
|
+
- Separa el QUÉ (funcional) del CÓMO (técnico); no mezcles.
|
|
138
|
+
|
|
139
|
+
## Buenas prácticas (agnósticas)
|
|
140
|
+
|
|
141
|
+
- **Confía en el borde, no en el input:** valida y autoriza en el límite de confianza; no aceptes identidad, permisos ni datos externos sin verificar.
|
|
142
|
+
- **"No existe" > "no autorizado":** si un recurso no pertenece a quien pregunta, responde "no encontrado" en vez de revelar que existe.
|
|
143
|
+
- **Aísla la E/S externa:** toda llamada a un servicio externo vive detrás de un borde dedicado; la capa de presentación no llama afuera directo.
|
|
144
|
+
- **Estático en rojo = no está hecho:** los errores de tipos/lint son bloqueantes, no deuda diferible.
|
|
145
|
+
- **Lo que cambia junto, vive junto:** coloca junto la lógica, la interfaz y los tests de una misma unidad.
|
|
80
146
|
|
|
81
147
|
## Herramientas
|
|
82
148
|
|
package/cli/lib/env.mjs
CHANGED
|
@@ -16,6 +16,12 @@ export function loadEnv(path = ".env", env = process.env) {
|
|
|
16
16
|
val = val.trim();
|
|
17
17
|
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
18
18
|
val = val.slice(1, -1);
|
|
19
|
+
} else {
|
|
20
|
+
// Valor sin comillas: recortar comentario inline (espacio + #). Evita que un
|
|
21
|
+
// `TOKEN=xxx # nota` meta la nota — y caracteres no-ASCII como ← — en el valor,
|
|
22
|
+
// que después rompen headers HTTP (ByteString). Para conservar un # literal, usar comillas.
|
|
23
|
+
const c = val.search(/\s#/);
|
|
24
|
+
if (c !== -1) val = val.slice(0, c).trimEnd();
|
|
19
25
|
}
|
|
20
26
|
if (!(key in env)) env[key] = val;
|
|
21
27
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# ADR-0010 — Versionado y upgrade (compatibilidad, `doctor` version-drift, `dai sync`)
|
|
2
|
+
|
|
3
|
+
- **Estado:** propuesto
|
|
4
|
+
- **Fecha:** 2026-07-09
|
|
5
|
+
- **Decide:** lead / arquitecto de la metodología
|
|
6
|
+
|
|
7
|
+
## Contexto
|
|
8
|
+
|
|
9
|
+
Un equipo scaffoldea su repo con dai `X.Y.Z`. Después publicamos versiones nuevas
|
|
10
|
+
(`2.2`, `2.9`, `3.0`). ¿Qué pasa con su trabajo? ¿Tienen que actualizar el CLI y el
|
|
11
|
+
repo? Hoy no hay política escrita ni herramienta de upgrade — el equipo queda a ciegas.
|
|
12
|
+
|
|
13
|
+
Lo que dai deja en un repo no es una cosa, son **cuatro capas** con impacto distinto
|
|
14
|
+
ante un upgrade (y una premisa de fondo: dai **da herramientas, no obliga** — MANIFIESTO
|
|
15
|
+
Art. 14, "la ceremonia se agrega cuando duele, no antes"):
|
|
16
|
+
|
|
17
|
+
| Capa | Qué es | Naturaleza |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| **CLI** | el binario `dai` (npm global) | por máquina/dev |
|
|
20
|
+
| **Copias scaffoldeadas** | skills, constitución (`CLAUDE.md`/rules), templates, PR template | **caché derivable**, commiteado por repo |
|
|
21
|
+
| **OpenSpec** | `openspec/` | **herramienta aparte** (@fission-ai/openspec), versionado propio |
|
|
22
|
+
| **Dato de trazabilidad** | `implements.yaml` (id, version, **`ac_hash`**) | el **contrato** (ADR-0001, ADR-0004) |
|
|
23
|
+
|
|
24
|
+
Solo la última capa, si cambia, **invalida trabajo hecho**. Por eso el principio rector
|
|
25
|
+
es: **dai no obliga a actualizar** — `doctor` avisa, el equipo decide cuándo.
|
|
26
|
+
|
|
27
|
+
## Decisión
|
|
28
|
+
|
|
29
|
+
### 1. La compatibilidad la comunica el semver (formaliza `RELEASING.md`)
|
|
30
|
+
|
|
31
|
+
- **patch / minor** (`2.0` → `2.2` → `2.9`): el **contrato NO cambia**. Todo lo hecho
|
|
32
|
+
sigue válido; `dai check` da igual que antes. Upgrade **opcional** (fixes/features).
|
|
33
|
+
- **major** (`2.x` → `3.0`): puede cambiar el algoritmo del `ac_hash` o el schema del
|
|
34
|
+
`implements.yaml`. Requiere **migración**.
|
|
35
|
+
|
|
36
|
+
**Invariante duro:** *dentro de una línea major, `ac_hash(input)` es estable* — el hash
|
|
37
|
+
que calcula `2.0` es idéntico al de `2.9` para la misma US. Es lo que vuelve seguros a
|
|
38
|
+
los minors. Se blinda con **golden vectors de `ac_hash` en CI que NO pueden cambiar
|
|
39
|
+
dentro de una major** (romperlos = obligado a bumpear major).
|
|
40
|
+
|
|
41
|
+
### 2. Las copias son caché derivable, no algo que se mantiene a mano
|
|
42
|
+
|
|
43
|
+
Las skills/constitución/templates del repo son **copias** de lo que trae el CLI. Una
|
|
44
|
+
copia vieja **sigue funcionando** (una skill/constitución vieja es un prompt válido).
|
|
45
|
+
No se editan a mano en el repo: se **regeneran** (mismo espíritu que "la cobertura se
|
|
46
|
+
deriva"). La fuente de verdad es la versión del CLI.
|
|
47
|
+
|
|
48
|
+
OpenSpec queda **fuera de alcance**: es otra herramienta con su propio upgrade
|
|
49
|
+
(`openspec`), dai solo la instala/inicia. `dai sync` **no** toca OpenSpec.
|
|
50
|
+
|
|
51
|
+
### 3. `.dai/VERSION` = provenance del scaffold
|
|
52
|
+
|
|
53
|
+
`dai init` ya estampa con qué versión se scaffoldeó el repo. Es el marcador para
|
|
54
|
+
detectar drift entre el repo y el CLI instalado.
|
|
55
|
+
|
|
56
|
+
### 4. `dai doctor` chequea version-drift
|
|
57
|
+
|
|
58
|
+
Compara `.dai/VERSION` (repo) contra la versión del CLI instalado y reporta:
|
|
59
|
+
|
|
60
|
+
| Situación | Mensaje |
|
|
61
|
+
|---|---|
|
|
62
|
+
| iguales | ✓ al día |
|
|
63
|
+
| CLI > repo, **misma major** | ℹ️ refresh disponible (skills/constitución de `vX`, CLI `vY`). `dai sync` cuando quieras — **opcional, nada roto** |
|
|
64
|
+
| CLI **major** > repo | ⚠️ cambio mayor: puede tocar el contrato. Ver `MIGRATION.md`/CHANGELOG antes; `dai sync` + `dai migrate` si aplica |
|
|
65
|
+
| CLI < repo | ⚠️ tu repo se scaffoldeó con una versión más nueva que tu CLI — actualizá el CLI |
|
|
66
|
+
|
|
67
|
+
### 5. `dai sync` — refresco **aditivo** de las copias (comando nuevo)
|
|
68
|
+
|
|
69
|
+
Re-genera skills, constitución (bloque `<!-- dai:start/end -->`), templates y PR
|
|
70
|
+
template **a la versión del CLI**, **aditivo e idempotente**. Reusa la maquinaria del
|
|
71
|
+
init aditivo: `upsertBlock` (constitución), `mergeEnv` (`.env`/`.env.example`),
|
|
72
|
+
`reconcileGitignore`. **No pisa** lo del proyecto (constitución propia, config). Respeta
|
|
73
|
+
el `--for` ya presente en el repo y actualiza `.dai/VERSION`.
|
|
74
|
+
|
|
75
|
+
- Flags: `--dry-run` (qué cambiaría, sin tocar), `--for` (override de asistentes).
|
|
76
|
+
- Es **opt-in**: lo corre el equipo cuando quiere.
|
|
77
|
+
- Relación con `dai install`: `sync` = `install --local . --force` **consciente de la
|
|
78
|
+
constitución + templates + `.dai/VERSION`**, con reporte de drift. Se puede
|
|
79
|
+
implementar como extensión/alias de `install`, pero el verbo explícito comunica mejor
|
|
80
|
+
la intención (refrescar un repo ya inicializado).
|
|
81
|
+
|
|
82
|
+
### 6. Majors: `dai migrate` + `MIGRATION.md` + nota BREAKING
|
|
83
|
+
|
|
84
|
+
Cuando un major cambia el contrato, se provee migración — p. ej. re-derivar y
|
|
85
|
+
re-estampar los `ac_hash` en masa (un `link-us --resync` para todos los
|
|
86
|
+
`implements.yaml`). Documentado en `MIGRATION.md`, marcado **BREAKING** en el CHANGELOG.
|
|
87
|
+
Los majors son **raros y bien soportados**.
|
|
88
|
+
|
|
89
|
+
### 7. Future-proofing del schema (enmienda menor a ADR-0004)
|
|
90
|
+
|
|
91
|
+
Estampar `schema: <n>` en el `implements.yaml` para que un futuro `dai migrate` sepa de
|
|
92
|
+
qué versión de schema migrar. Se puede **diferir** hasta que un major lo necesite, pero
|
|
93
|
+
conviene reservar el campo desde ya.
|
|
94
|
+
|
|
95
|
+
## Consecuencias
|
|
96
|
+
|
|
97
|
+
- ✅ El **número de versión comunica el radio de explosión**: el equipo sabe si un
|
|
98
|
+
upgrade es seguro sin leer diffs.
|
|
99
|
+
- ✅ Upgrades **opt-in** — respeta el ADN (dai no obliga). `doctor` avisa, el equipo
|
|
100
|
+
decide.
|
|
101
|
+
- ✅ Las copias son caché → `dai sync` las refresca sin miedo (aditivo, no pisa).
|
|
102
|
+
- ✅ **Reusa** la maquinaria aditiva ya construida y testeada (`upsertBlock`/`mergeEnv`/
|
|
103
|
+
`reconcileGitignore`, v0.3.0) — costo de implementación bajo.
|
|
104
|
+
- ⚠️ Obliga a **golden vectors de `ac_hash` en CI** inmutables dentro de una major —
|
|
105
|
+
disciplina de release.
|
|
106
|
+
- ⚠️ `dai sync` debe ser **estrictamente aditivo/idempotente**; un bug ahí pisaría
|
|
107
|
+
trabajo (mismo riesgo que tuvo el `init` — ya mitigado y testeado).
|
|
108
|
+
- ⚠️ Hay que comunicar que `dai sync` **no** actualiza OpenSpec (upgrade aparte).
|
|
109
|
+
|
|
110
|
+
## Alternativas consideradas
|
|
111
|
+
|
|
112
|
+
- **Forzar re-scaffold/upgrade en cada versión** — descartado: viola el ADN (dai no
|
|
113
|
+
obliga); rompería la adopción. dai es como git: no te dicta cómo trabajar.
|
|
114
|
+
- **No versionar el scaffold (sin drift check)** — descartado: sin provenance no se
|
|
115
|
+
puede avisar; el equipo queda a ciegas.
|
|
116
|
+
- **Mantener las copias a mano** — descartado: contradice "la cobertura se deriva"; las
|
|
117
|
+
copias son caché, la fuente es el CLI.
|
|
118
|
+
- **`dai sync` destructivo (pisar y re-copiar)** — descartado: pisaría la constitución
|
|
119
|
+
propia del proyecto. Debe ser aditivo (lección del `init`, v0.3.0).
|
|
120
|
+
- **Meter todo en `dai install --force`** — posible; pero `sync` como verbo explícito +
|
|
121
|
+
reporte de drift comunica mejor la intención. Se implementa como extensión de
|
|
122
|
+
`install`.
|
package/docs/adr/README.md
CHANGED
|
@@ -15,6 +15,7 @@ decisión cambia, se escribe un ADR nuevo que supersede al viejo. Molde en
|
|
|
15
15
|
| [0007](0007-modelo-de-autenticacion.md) | Modelo de auth: SSH para git, tokens scopeados para forge/tracker, sin contraseñas | aceptado |
|
|
16
16
|
| [0008](0008-estrategia-de-i18n.md) | Estrategia de i18n: fuente única (español) + traducciones derivadas, `DAI_LANG` en el CLI, por fases | propuesto |
|
|
17
17
|
| [0009](0009-adaptador-cursor.md) | Adaptador nativo para Cursor (skills + rules) con `dai init`/`install`/`doctor` | propuesto |
|
|
18
|
+
| [0010](0010-versionado-y-upgrade.md) | Versionado y upgrade: compatibilidad por semver, `doctor` version-drift, `dai sync` aditivo | propuesto |
|
|
18
19
|
|
|
19
20
|
> Estas son las decisiones que cierran las "Decisiones abiertas" de
|
|
20
21
|
> [`METODOLOGIA.md §7`](../METODOLOGIA.md) y las enmiendas al
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dforce2055/dai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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/",
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
"templates",
|
|
31
31
|
"governance",
|
|
32
32
|
"skills",
|
|
33
|
-
"index.html",
|
|
34
33
|
"manifest.yaml",
|
|
35
34
|
"install.sh",
|
|
36
35
|
".env.example",
|
package/templates/formato-us.md
CHANGED
|
@@ -127,3 +127,44 @@ Lo que quedó sin resolver y necesita una decisión antes de pasar a implementac
|
|
|
127
127
|
- spec_version (número legible) lo sube el PO/skill para comunicar; ac_hash lo
|
|
128
128
|
calcula el CI para detectar. El número comunica, el hash detecta.
|
|
129
129
|
-->
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
<!--
|
|
134
|
+
══════════════════════════════════════════════════════════════════════════════
|
|
135
|
+
EJEMPLO LISTO PARA PROBAR — copiá desde el título de abajo hasta el final.
|
|
136
|
+
Es lo MÍNIMO que funciona (una US real necesita más secciones, ver arriba),
|
|
137
|
+
pero alcanza para ver el flujo:
|
|
138
|
+
|
|
139
|
+
• Sin tracker: guardalo como .dai/us/EJ-1.md y corré:
|
|
140
|
+
dai ac-hash .dai/us/EJ-1.md # imprime el ac_hash
|
|
141
|
+
dai link-us EJ-1 --us .dai/us/EJ-1.md --dry-run # muestra branch + implements.yaml
|
|
142
|
+
• Con tracker: pegalo como una US nueva y corré dai link-us <ID>.
|
|
143
|
+
|
|
144
|
+
La clave es la sección "Criterios de aceptación": es lo que se hashea. Sin ella,
|
|
145
|
+
dai no linkea (por diseño: no hay link sin criterios testeables).
|
|
146
|
+
══════════════════════════════════════════════════════════════════════════════
|
|
147
|
+
-->
|
|
148
|
+
|
|
149
|
+
# Marcar una tarea como completada
|
|
150
|
+
|
|
151
|
+
## Historia
|
|
152
|
+
|
|
153
|
+
Como **usuario de la lista de tareas**
|
|
154
|
+
quiero **marcar una tarea como completada**
|
|
155
|
+
para **distinguir de un vistazo lo que ya hice de lo que me falta**.
|
|
156
|
+
|
|
157
|
+
## Criterios de aceptación
|
|
158
|
+
|
|
159
|
+
- [ ] **AC-1** —
|
|
160
|
+
- **Dado** una tarea pendiente en mi lista
|
|
161
|
+
- **Cuando** la marco como completada
|
|
162
|
+
- **Entonces** queda diferenciada como hecha y deja de contar en el total de pendientes.
|
|
163
|
+
- [ ] **AC-2** —
|
|
164
|
+
- **Dado** una tarea ya completada
|
|
165
|
+
- **Cuando** la vuelvo a marcar
|
|
166
|
+
- **Entonces** vuelve al estado pendiente y se recuenta en el total.
|
|
167
|
+
- [ ] **AC-3** —
|
|
168
|
+
- **Dado** que recargo la página
|
|
169
|
+
- **Cuando** vuelve a cargar mi lista
|
|
170
|
+
- **Entonces** cada tarea conserva el estado (completada o pendiente) que tenía.
|
package/index.html
DELETED
|
@@ -1,481 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="es">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
-
<title>dai — Desarrollo Asistido por IA con trazabilidad</title>
|
|
7
|
-
<meta name="description" content="Un método y un CLI para que la IA programe con trazabilidad y sin vibe coding. Separa el QUÉ del CÓMO, linkéalos una vez, y que la máquina avise sola cuando se desincronizan. Se adapta a cualquier tamaño de equipo." />
|
|
8
|
-
<!-- Favicon SVG inline (sin CDN, nítido en cualquier resolución) -->
|
|
9
|
-
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%236366f1'/%3E%3Cstop offset='1' stop-color='%230ea5e9'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' rx='14' fill='url(%23g)'/%3E%3Ctext x='32' y='48' font-family='ui-sans-serif,system-ui,Arial' font-size='44' font-weight='800' fill='white' text-anchor='middle'%3Ed%3C/text%3E%3C/svg%3E" />
|
|
10
|
-
<meta name="theme-color" content="#6366f1" />
|
|
11
|
-
<style>
|
|
12
|
-
/* Autocontenido: fuentes del sistema + diagramas en CSS. Sin CDN → anda offline. */
|
|
13
|
-
:root{
|
|
14
|
-
--bg:#0b1120; --card:#131c2e; --ink:#eef2f8; --mut:#94a3b8; --line:#263247;
|
|
15
|
-
--brand:#6366f1; --brand-dark:#4f46e5;
|
|
16
|
-
--func:#f59e0b; --func-soft:#3a2f12; --func-ink:#fbbf24; /* el QUÉ */
|
|
17
|
-
--tech:#0ea5e9; --tech-soft:#10273b; --tech-ink:#7dd3fc; /* el CÓMO */
|
|
18
|
-
--ci:#10b981; --ci-soft:#0f2c22; --ci-ink:#6ee7b7; /* la máquina */
|
|
19
|
-
--danger:#ef4444;
|
|
20
|
-
--mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
|
|
21
|
-
--sans:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
|
22
|
-
}
|
|
23
|
-
:root[data-theme="light"]{
|
|
24
|
-
--bg:#f8fafc; --card:#ffffff; --ink:#0f172a; --mut:#475569; --line:#e2e8f0;
|
|
25
|
-
--func-soft:#fef3c7; --func-ink:#92400e; --tech-soft:#e0f2fe; --tech-ink:#075985;
|
|
26
|
-
--ci-soft:#d1fae5; --ci-ink:#065f46;
|
|
27
|
-
}
|
|
28
|
-
@media (prefers-color-scheme: light){
|
|
29
|
-
:root:not([data-theme="dark"]){
|
|
30
|
-
--bg:#f8fafc; --card:#ffffff; --ink:#0f172a; --mut:#475569; --line:#e2e8f0;
|
|
31
|
-
--func-soft:#fef3c7; --func-ink:#92400e; --tech-soft:#e0f2fe; --tech-ink:#075985;
|
|
32
|
-
--ci-soft:#d1fae5; --ci-ink:#065f46;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
*{box-sizing:border-box}
|
|
36
|
-
html{scroll-behavior:smooth}
|
|
37
|
-
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);
|
|
38
|
-
line-height:1.6;-webkit-font-smoothing:antialiased}
|
|
39
|
-
.wrap{max-width:960px;margin:0 auto;padding:0 20px}
|
|
40
|
-
a{color:var(--brand);text-decoration:none}
|
|
41
|
-
a:hover{text-decoration:underline}
|
|
42
|
-
code,kbd{font-family:var(--mono);font-size:.9em}
|
|
43
|
-
h1,h2,h3{line-height:1.2;letter-spacing:-.02em}
|
|
44
|
-
section{padding:64px 0;border-top:1px solid var(--line)}
|
|
45
|
-
.eyebrow{font-family:var(--mono);font-size:.8rem;letter-spacing:.08em;
|
|
46
|
-
text-transform:uppercase;color:var(--mut);margin:0 0 8px}
|
|
47
|
-
h2{font-size:clamp(1.5rem,4vw,2rem);margin:0 0 8px}
|
|
48
|
-
.lede{color:var(--mut);font-size:1.05rem;max-width:62ch;margin:0 0 28px}
|
|
49
|
-
|
|
50
|
-
/* ---------- nav ---------- */
|
|
51
|
-
nav{position:sticky;top:0;z-index:10;backdrop-filter:blur(10px);
|
|
52
|
-
background:color-mix(in srgb,var(--bg) 82%,transparent);border-bottom:1px solid var(--line)}
|
|
53
|
-
nav .wrap{display:flex;align-items:center;gap:16px;height:56px}
|
|
54
|
-
nav .logo{font-weight:800;letter-spacing:-.03em}
|
|
55
|
-
nav .logo b{color:var(--brand)}
|
|
56
|
-
nav .sp{flex:1}
|
|
57
|
-
nav a.nl{color:var(--mut);font-size:.9rem}
|
|
58
|
-
nav a.nl:hover{color:var(--ink);text-decoration:none}
|
|
59
|
-
.toggle{cursor:pointer;border:1px solid var(--line);background:var(--card);
|
|
60
|
-
color:var(--ink);border-radius:8px;padding:5px 9px;font-size:.85rem}
|
|
61
|
-
@media(max-width:640px){nav a.nl{display:none}}
|
|
62
|
-
|
|
63
|
-
/* ---------- hero ---------- */
|
|
64
|
-
.hero{padding:80px 0 64px;text-align:center}
|
|
65
|
-
.hero h1{font-size:clamp(2.2rem,7vw,3.6rem);margin:0 0 16px;font-weight:900}
|
|
66
|
-
.hero h1 .g{background:linear-gradient(120deg,var(--brand),var(--tech));
|
|
67
|
-
-webkit-background-clip:text;background-clip:text;color:transparent}
|
|
68
|
-
.hero p{color:var(--mut);font-size:1.2rem;max-width:60ch;margin:0 auto 28px}
|
|
69
|
-
.cta{display:flex;gap:12px;justify-content:center;flex-wrap:wrap}
|
|
70
|
-
.btn{display:inline-block;padding:11px 20px;border-radius:10px;font-weight:600;font-size:.95rem}
|
|
71
|
-
.btn.p{background:var(--brand);color:#fff}
|
|
72
|
-
.btn.p:hover{background:var(--brand-dark);text-decoration:none}
|
|
73
|
-
.btn.s{border:1px solid var(--line);color:var(--ink)}
|
|
74
|
-
.btn.s:hover{text-decoration:none;border-color:var(--brand)}
|
|
75
|
-
.install{margin:26px auto 0;max-width:520px;background:var(--card);border:1px solid var(--line);
|
|
76
|
-
border-radius:12px;padding:14px 16px;font-family:var(--mono);font-size:.9rem;text-align:left;
|
|
77
|
-
color:var(--ink);overflow-x:auto}
|
|
78
|
-
.install .c{color:var(--ci-ink)}
|
|
79
|
-
.install .m{color:var(--mut)}
|
|
80
|
-
|
|
81
|
-
/* ---------- pills / cards ---------- */
|
|
82
|
-
.grid{display:grid;gap:16px}
|
|
83
|
-
.g3{grid-template-columns:repeat(3,1fr)}
|
|
84
|
-
.g2{grid-template-columns:repeat(2,1fr)}
|
|
85
|
-
@media(max-width:720px){.g3,.g2{grid-template-columns:1fr}}
|
|
86
|
-
.card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:20px;
|
|
87
|
-
transition:transform .25s ease,border-color .25s ease,box-shadow .25s ease}
|
|
88
|
-
.card:hover{transform:translateY(-4px);border-color:color-mix(in srgb,var(--brand) 55%,var(--line));
|
|
89
|
-
box-shadow:0 12px 30px -12px color-mix(in srgb,var(--brand) 45%,transparent)}
|
|
90
|
-
.card h3{margin:0 0 6px;font-size:1.05rem}
|
|
91
|
-
.card p{margin:0;color:var(--mut);font-size:.95rem}
|
|
92
|
-
|
|
93
|
-
/* ---------- animaciones (reveal al scroll + hover) ---------- */
|
|
94
|
-
.reveal{transition:opacity .7s ease,transform .7s cubic-bezier(.2,.7,.2,1)}
|
|
95
|
-
html.anim .reveal{opacity:0;transform:translateY(20px)}
|
|
96
|
-
html.anim .reveal.in{opacity:1;transform:none}
|
|
97
|
-
.lvl,.step .body,.side{transition:transform .25s ease}
|
|
98
|
-
.lvl:hover{transform:translateY(-4px)}
|
|
99
|
-
.install{transition:border-color .25s ease}
|
|
100
|
-
.install:hover{border-color:color-mix(in srgb,var(--ci) 50%,var(--line))}
|
|
101
|
-
@media (prefers-reduced-motion: reduce){
|
|
102
|
-
html.anim .reveal{opacity:1 !important;transform:none !important}
|
|
103
|
-
.card:hover,.lvl:hover{transform:none}
|
|
104
|
-
}
|
|
105
|
-
.ic{width:34px;height:34px;border-radius:9px;display:grid;place-items:center;
|
|
106
|
-
font-size:1.1rem;margin-bottom:12px}
|
|
107
|
-
.ic{transition:transform .25s ease}
|
|
108
|
-
.ic.d{background:linear-gradient(135deg,color-mix(in srgb,var(--brand) 28%,transparent),color-mix(in srgb,var(--tech) 28%,transparent));
|
|
109
|
-
box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--brand) 24%,transparent)}
|
|
110
|
-
.ic.b{background:linear-gradient(135deg,color-mix(in srgb,var(--brand) 26%,transparent),color-mix(in srgb,var(--tech) 26%,transparent))}
|
|
111
|
-
.card:hover .ic{transform:scale(1.08) rotate(-3deg)}
|
|
112
|
-
|
|
113
|
-
.tag{display:inline-block;font-family:var(--mono);font-size:.72rem;font-weight:600;
|
|
114
|
-
padding:2px 8px;border-radius:999px;letter-spacing:.02em}
|
|
115
|
-
.tag.q{background:var(--func-soft);color:var(--func-ink)}
|
|
116
|
-
.tag.c{background:var(--tech-soft);color:var(--tech-ink)}
|
|
117
|
-
.tag.m{background:var(--ci-soft);color:var(--ci-ink)}
|
|
118
|
-
|
|
119
|
-
/* ---------- QUÉ vs CÓMO ---------- */
|
|
120
|
-
.split{display:grid;grid-template-columns:1fr auto 1fr;gap:0;align-items:stretch;
|
|
121
|
-
background:var(--card);border:1px solid var(--line);border-radius:16px;overflow:hidden}
|
|
122
|
-
@media(max-width:720px){.split{grid-template-columns:1fr}}
|
|
123
|
-
.side{padding:24px}
|
|
124
|
-
.side.q{border-right:1px solid var(--line)}
|
|
125
|
-
@media(max-width:720px){.side.q{border-right:0;border-bottom:1px solid var(--line)}}
|
|
126
|
-
.side h4{margin:0 0 4px;font-size:1.1rem}
|
|
127
|
-
.side .who{color:var(--mut);font-size:.85rem;margin:0 0 14px}
|
|
128
|
-
.side ul{margin:0;padding-left:18px;color:var(--mut);font-size:.92rem}
|
|
129
|
-
.link{display:grid;place-items:center;padding:0 8px;background:
|
|
130
|
-
color-mix(in srgb,var(--brand) 10%,transparent)}
|
|
131
|
-
.link .b{font-family:var(--mono);font-size:.72rem;color:var(--brand);
|
|
132
|
-
writing-mode:vertical-rl;transform:rotate(180deg);letter-spacing:.1em;font-weight:700}
|
|
133
|
-
@media(max-width:720px){.link{padding:10px}.link .b{writing-mode:horizontal-tb;transform:none}}
|
|
134
|
-
|
|
135
|
-
/* ---------- steps / journey ---------- */
|
|
136
|
-
.steps{position:relative;margin-top:8px}
|
|
137
|
-
.step{display:grid;grid-template-columns:44px 1fr;gap:18px;padding:14px 0;position:relative}
|
|
138
|
-
.step:not(:last-child)::before{content:"";position:absolute;left:21px;top:44px;bottom:-14px;
|
|
139
|
-
width:2px;background:var(--line)}
|
|
140
|
-
.num{width:44px;height:44px;border-radius:12px;display:grid;place-items:center;
|
|
141
|
-
font-weight:800;font-family:var(--mono);background:var(--card);border:1px solid var(--line);z-index:1}
|
|
142
|
-
.step.q .num{border-color:var(--func);color:var(--func-ink)}
|
|
143
|
-
.step.c .num{border-color:var(--tech);color:var(--tech-ink)}
|
|
144
|
-
.step.m .num{border-color:var(--ci);color:var(--ci-ink)}
|
|
145
|
-
.step .body h3{margin:0 0 3px;font-size:1.08rem}
|
|
146
|
-
.step .body p{margin:0 0 8px;color:var(--mut);font-size:.95rem}
|
|
147
|
-
.step .tool{font-family:var(--mono);font-size:.82rem;background:var(--bg);
|
|
148
|
-
border:1px solid var(--line);border-radius:8px;padding:3px 9px;display:inline-block;color:var(--ink)}
|
|
149
|
-
|
|
150
|
-
/* ---------- traceability demo ---------- */
|
|
151
|
-
.demo{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:8px}
|
|
152
|
-
.term{font-family:var(--mono);font-size:.86rem;background:#0a0f1e;border-radius:12px;
|
|
153
|
-
padding:18px 18px;color:#cbd5e1;overflow-x:auto}
|
|
154
|
-
:root[data-theme="light"] .term{background:#0f172a;color:#e2e8f0}
|
|
155
|
-
@media(prefers-color-scheme:light){:root:not([data-theme="dark"]) .term{background:#0f172a;color:#e2e8f0}}
|
|
156
|
-
.term .p{color:#64748b}
|
|
157
|
-
.term .ok{color:#6ee7b7}
|
|
158
|
-
.term .warn{color:#fca5a5}
|
|
159
|
-
.term .cmd{color:#e2e8f0}
|
|
160
|
-
.term .dim{color:#64748b}
|
|
161
|
-
.term b{color:#fff;font-weight:600}
|
|
162
|
-
|
|
163
|
-
/* ---------- dial ---------- */
|
|
164
|
-
.dial{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
|
|
165
|
-
@media(max-width:720px){.dial{grid-template-columns:1fr}}
|
|
166
|
-
.lvl{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:18px}
|
|
167
|
-
.lvl .n{font-family:var(--mono);font-size:.78rem;color:var(--brand);font-weight:700}
|
|
168
|
-
.lvl h4{margin:6px 0 4px;font-size:1.05rem}
|
|
169
|
-
.lvl p{margin:0;color:var(--mut);font-size:.9rem}
|
|
170
|
-
|
|
171
|
-
/* ---------- start ---------- */
|
|
172
|
-
ol.start{counter-reset:s;list-style:none;padding:0;margin:0}
|
|
173
|
-
ol.start li{position:relative;padding:0 0 18px 40px}
|
|
174
|
-
ol.start li::before{counter-increment:s;content:counter(s);position:absolute;left:0;top:0;
|
|
175
|
-
width:26px;height:26px;border-radius:8px;background:var(--brand);color:#fff;
|
|
176
|
-
display:grid;place-items:center;font-weight:700;font-size:.85rem}
|
|
177
|
-
ol.start code{background:var(--card);border:1px solid var(--line);border-radius:7px;
|
|
178
|
-
padding:2px 7px;display:inline-block}
|
|
179
|
-
ol.start .d{color:var(--mut);font-size:.9rem}
|
|
180
|
-
|
|
181
|
-
footer{border-top:1px solid var(--line);padding:40px 0 60px;color:var(--mut);font-size:.9rem}
|
|
182
|
-
footer .wrap{display:flex;gap:20px;flex-wrap:wrap;align-items:center}
|
|
183
|
-
footer .sp{flex:1}
|
|
184
|
-
</style>
|
|
185
|
-
</head>
|
|
186
|
-
<body>
|
|
187
|
-
|
|
188
|
-
<nav>
|
|
189
|
-
<div class="wrap">
|
|
190
|
-
<span class="logo"><b>dai</b> · Desarrollo Asistido por IA</span>
|
|
191
|
-
<span class="sp"></span>
|
|
192
|
-
<a class="nl" href="#problema">Problema</a>
|
|
193
|
-
<a class="nl" href="#flujo">El flujo</a>
|
|
194
|
-
<a class="nl" href="#traza">Trazabilidad</a>
|
|
195
|
-
<a class="nl" href="#empezar">Empezar</a>
|
|
196
|
-
<button class="toggle" id="tg" aria-label="Cambiar tema">◐</button>
|
|
197
|
-
</div>
|
|
198
|
-
</nav>
|
|
199
|
-
|
|
200
|
-
<!-- ══════════ HERO ══════════ -->
|
|
201
|
-
<header class="hero wrap">
|
|
202
|
-
<h1>La IA programa. <span class="g">@dai te da la trazabilidad.</span></h1>
|
|
203
|
-
<p>Un método —y un CLI— para desarrollo asistido por IA <b>sin vibe coding</b> y con
|
|
204
|
-
trazabilidad real: separa el <span class="tag q">QUÉ</span> del
|
|
205
|
-
<span class="tag c">CÓMO</span>, linkéalos una sola vez, y deja que la máquina
|
|
206
|
-
avise sola cuando se desincronizan. De un dev solo a una organización entera —
|
|
207
|
-
el mismo método.</p>
|
|
208
|
-
<div class="cta">
|
|
209
|
-
<a class="btn p" href="#empezar">Empezar ahora</a>
|
|
210
|
-
<a class="btn s" href="https://github.com/dforce2055/dai">Ver en GitHub</a>
|
|
211
|
-
<a class="btn s" href="https://www.npmjs.com/package/@dforce2055/dai">Ver en npm</a>
|
|
212
|
-
</div>
|
|
213
|
-
<div class="install">
|
|
214
|
-
<span class="m"># instala el CLI (Node ≥18, cero dependencias)</span><br>
|
|
215
|
-
<span class="c">npm i -g @dforce2055/dai</span><br>
|
|
216
|
-
<span class="c">dai init</span> <span class="m"># configura tu repo: Claude/Copilot + OpenSpec + el link</span>
|
|
217
|
-
</div>
|
|
218
|
-
</header>
|
|
219
|
-
|
|
220
|
-
<!-- ══════════ PROBLEMA ══════════ -->
|
|
221
|
-
<section id="problema">
|
|
222
|
-
<div class="wrap">
|
|
223
|
-
<p class="eyebrow">El problema</p>
|
|
224
|
-
<h2>La IA acelera el código. Y también acelera el caos.</h2>
|
|
225
|
-
<p class="lede">Tres dolores que la IA no resuelve sola — al contrario, los amplifica
|
|
226
|
-
si no hay método.</p>
|
|
227
|
-
<div class="grid g3">
|
|
228
|
-
<div class="card">
|
|
229
|
-
<div class="ic d">🔌</div>
|
|
230
|
-
<h3>Desincronización silenciosa</h3>
|
|
231
|
-
<p>El requerimiento cambia, el código sigue con la versión vieja, y se descubre
|
|
232
|
-
recién en la demo. Nadie se enteró a tiempo.</p>
|
|
233
|
-
</div>
|
|
234
|
-
<div class="card">
|
|
235
|
-
<div class="ic d">🎲</div>
|
|
236
|
-
<h3>Vibe coding</h3>
|
|
237
|
-
<p>La IA construye sobre una idea vaga y sale cualquier cosa. Rápido, sí — pero
|
|
238
|
-
sin garantía de que sea lo que se pedía.</p>
|
|
239
|
-
</div>
|
|
240
|
-
<div class="card">
|
|
241
|
-
<div class="ic d">🔀</div>
|
|
242
|
-
<h3>QUÉ y CÓMO mezclados</h3>
|
|
243
|
-
<p>El funcional se mete en la implementación; el dev redefine el negocio. Se pierde
|
|
244
|
-
quién es dueño de qué.</p>
|
|
245
|
-
</div>
|
|
246
|
-
</div>
|
|
247
|
-
</div>
|
|
248
|
-
</section>
|
|
249
|
-
|
|
250
|
-
<!-- ══════════ LA IDEA ══════════ -->
|
|
251
|
-
<section id="idea">
|
|
252
|
-
<div class="wrap">
|
|
253
|
-
<p class="eyebrow">La idea</p>
|
|
254
|
-
<h2>Separa el QUÉ del CÓMO. Linkéalos una sola vez.</h2>
|
|
255
|
-
<p class="lede">El requerimiento (el QUÉ) y la implementación (el CÓMO) son cosas
|
|
256
|
-
distintas, con dueños distintos. dai las mantiene atadas — y la dirección inversa
|
|
257
|
-
se <b>deriva</b>, nunca se escribe a mano.</p>
|
|
258
|
-
|
|
259
|
-
<div class="split">
|
|
260
|
-
<div class="side q">
|
|
261
|
-
<span class="tag q">EL QUÉ · funcional</span>
|
|
262
|
-
<h4 style="margin-top:12px">La User Story</h4>
|
|
263
|
-
<p class="who">dueño: PO / analista · vive en Jira o ClickUp</p>
|
|
264
|
-
<ul>
|
|
265
|
-
<li>Criterios de aceptación testeables</li>
|
|
266
|
-
<li>Lo produce una skill de IA por interrogación</li>
|
|
267
|
-
<li>Identidad estable: el ticket del tracker</li>
|
|
268
|
-
</ul>
|
|
269
|
-
</div>
|
|
270
|
-
<div class="link"><span class="b">implements →</span></div>
|
|
271
|
-
<div class="side">
|
|
272
|
-
<span class="tag c">EL CÓMO · técnico</span>
|
|
273
|
-
<h4 style="margin-top:12px">El código + su spec</h4>
|
|
274
|
-
<p class="who">dueño: dev / ingeniero · vive en el repo</p>
|
|
275
|
-
<ul>
|
|
276
|
-
<li>Declara <code>implements: US@versión</code> — una vez</li>
|
|
277
|
-
<li>Diseño y tareas generados con OpenSpec</li>
|
|
278
|
-
<li>Se construye con tests primero (TDD)</li>
|
|
279
|
-
</ul>
|
|
280
|
-
</div>
|
|
281
|
-
</div>
|
|
282
|
-
|
|
283
|
-
<div class="grid g3" style="margin-top:16px">
|
|
284
|
-
<div class="card"><span class="tag q">1</span><h3 style="margin-top:10px">Identidad estable</h3>
|
|
285
|
-
<p>El QUÉ nace con un ID único (el ticket). Es lo único que lo hace linkeable.</p></div>
|
|
286
|
-
<div class="card"><span class="tag c">2</span><h3 style="margin-top:10px">Un solo link, a mano</h3>
|
|
287
|
-
<p>El código apunta al QUÉ. La cobertura inversa (quién implementó qué) la genera la máquina.</p></div>
|
|
288
|
-
<div class="card"><span class="tag m">3</span><h3 style="margin-top:10px">@version que grita</h3>
|
|
289
|
-
<p>Número legible + hash de criterios. Si el QUÉ cambia, los CÓMO atrasados se marcan solos.</p></div>
|
|
290
|
-
</div>
|
|
291
|
-
</div>
|
|
292
|
-
</section>
|
|
293
|
-
|
|
294
|
-
<!-- ══════════ EL FLUJO GUIADO ══════════ -->
|
|
295
|
-
<section id="flujo">
|
|
296
|
-
<div class="wrap">
|
|
297
|
-
<p class="eyebrow">El flujo</p>
|
|
298
|
-
<h2>Tu Scrum de siempre — con IA en cada paso</h2>
|
|
299
|
-
<p class="lede">dai no reemplaza tu proceso: lo potencia. En cada etapa hay una skill
|
|
300
|
-
de IA o un comando que hace el trabajo pesado. <span class="tag q">QUÉ</span>
|
|
301
|
-
<span class="tag c">CÓMO</span> <span class="tag m">máquina</span></p>
|
|
302
|
-
|
|
303
|
-
<div class="steps">
|
|
304
|
-
<div class="step q"><div class="num">1</div><div class="body">
|
|
305
|
-
<h3>Define el QUÉ</h3>
|
|
306
|
-
<p>El analista describe la idea. Una skill de IA lo <b>interroga</b> hasta que la
|
|
307
|
-
User Story es testeable, y la publica en el tracker. La IA no inventa: pregunta.</p>
|
|
308
|
-
<span class="tool">/grill-intent → /grill-user-story → ClickUp / Jira</span>
|
|
309
|
-
</div></div>
|
|
310
|
-
|
|
311
|
-
<div class="step c"><div class="num">2</div><div class="body">
|
|
312
|
-
<h3>Abre el CÓMO, atado al QUÉ</h3>
|
|
313
|
-
<p>El dev arranca la implementación. dai trae la US del tracker, crea la branch y
|
|
314
|
-
siembra el link — <b>correcto por construcción</b>, sin tipear el ID a mano.</p>
|
|
315
|
-
<span class="tool">dai link-us <ID> → branch + implements.yaml</span>
|
|
316
|
-
</div></div>
|
|
317
|
-
|
|
318
|
-
<div class="step c"><div class="num">3</div><div class="body">
|
|
319
|
-
<h3>Diseña con OpenSpec</h3>
|
|
320
|
-
<p>OpenSpec toma la US y genera la propuesta, el diseño y las tareas — co-localizados
|
|
321
|
-
con el link, en la misma branch. El QUÉ se convierte en un plan técnico.</p>
|
|
322
|
-
<span class="tool">/opsx:propose → proposal · design · tasks</span>
|
|
323
|
-
</div></div>
|
|
324
|
-
|
|
325
|
-
<div class="step c"><div class="num">4</div><div class="body">
|
|
326
|
-
<h3>Implementa con tests primero</h3>
|
|
327
|
-
<p>TDD en rebanadas verticales: un test, el código mínimo, repetir. La IA escribe el
|
|
328
|
-
test como spec ejecutable. Anti vibe-coding de verdad.</p>
|
|
329
|
-
<span class="tool">/tdd · red → green → refactor</span>
|
|
330
|
-
</div></div>
|
|
331
|
-
|
|
332
|
-
<div class="step m"><div class="num">5</div><div class="body">
|
|
333
|
-
<h3>Verifica la trazabilidad</h3>
|
|
334
|
-
<p>¿Tu código sigue al día con la US? dai re-deriva el hash de la US viva y compara.
|
|
335
|
-
Si el QUÉ cambió, te avisa <b>y te dice cómo resincronizar</b>.</p>
|
|
336
|
-
<span class="tool">dai check → ✅ al día / ⚠️ atrasado</span>
|
|
337
|
-
</div></div>
|
|
338
|
-
|
|
339
|
-
<div class="step c"><div class="num">6</div><div class="body">
|
|
340
|
-
<h3>Crea y revisa la PR</h3>
|
|
341
|
-
<p>dai arma el texto de la PR desde el template, precargado con la US, el estado del
|
|
342
|
-
check y los enlaces. Tu PR la revisas tú; la de un compañero, con la skill de review.</p>
|
|
343
|
-
<span class="tool">dai pr · /dai-review (la PR de otro)</span>
|
|
344
|
-
</div></div>
|
|
345
|
-
|
|
346
|
-
<div class="step m"><div class="num">7</div><div class="body">
|
|
347
|
-
<h3>Mergea y estampa la cobertura</h3>
|
|
348
|
-
<p>Al mergear, dai escribe en el tracker <b>quién implementó qué</b>, contra qué
|
|
349
|
-
versión, con branch y commit. La trazabilidad inversa — nadie la mantiene a mano.</p>
|
|
350
|
-
<span class="tool">dai stamp → cobertura en el tracker</span>
|
|
351
|
-
</div></div>
|
|
352
|
-
</div>
|
|
353
|
-
</div>
|
|
354
|
-
</section>
|
|
355
|
-
|
|
356
|
-
<!-- ══════════ TRAZABILIDAD (demo) ══════════ -->
|
|
357
|
-
<section id="traza">
|
|
358
|
-
<div class="wrap">
|
|
359
|
-
<p class="eyebrow">La pieza que lo cambia todo</p>
|
|
360
|
-
<h2>Cuando el QUÉ cambia, el desajuste se hace visible solo</h2>
|
|
361
|
-
<p class="lede">El PO edita un criterio en el tracker. Tú no tocaste una línea de código.
|
|
362
|
-
Y aun así, dai lo detecta — porque compara el hash de la US viva contra el que dejaste
|
|
363
|
-
estampado.</p>
|
|
364
|
-
|
|
365
|
-
<div class="demo">
|
|
366
|
-
<div class="term">
|
|
367
|
-
<span class="p">$</span> <span class="cmd">dai check</span>
|
|
368
|
-
<span class="ok">✅ 86ajcjwzk al día (v1)</span>
|
|
369
|
-
|
|
370
|
-
<span class="dim"># … el PO agrega un criterio de aceptación en el tracker …</span>
|
|
371
|
-
|
|
372
|
-
<span class="p">$</span> <span class="cmd">dai check</span>
|
|
373
|
-
<span class="warn">⚠️ 86ajcjwzk ATRASADO: implementaste 2124e94f, la US viva es 51abbfa0</span>
|
|
374
|
-
|
|
375
|
-
<span class="dim">El QUÉ cambió desde que lo implementaste. Para resincronizar:</span>
|
|
376
|
-
<b>dai link-us 86ajcjwzk --resync</b> <span class="dim"># re-estampa contra la US viva</span>
|
|
377
|
-
</div>
|
|
378
|
-
</div>
|
|
379
|
-
<p class="lede" style="margin-top:22px">Nadie le avisó al dev. El link versionado lo
|
|
380
|
-
avisó solo. Esa es la diferencia entre <i>esperar que alguien se dé cuenta</i> y
|
|
381
|
-
<i>que el sistema te lo diga</i>.</p>
|
|
382
|
-
</div>
|
|
383
|
-
</section>
|
|
384
|
-
|
|
385
|
-
<!-- ══════════ SE ADAPTA ══════════ -->
|
|
386
|
-
<section id="escala">
|
|
387
|
-
<div class="wrap">
|
|
388
|
-
<p class="eyebrow">Un solo método</p>
|
|
389
|
-
<h2>Se adapta a cualquier tamaño de equipo</h2>
|
|
390
|
-
<p class="lede">No hay una versión para equipos chicos y otra para grandes. Hay
|
|
391
|
-
<b>un protocolo invariante</b> y un dial de ceremonia que sube o baja según la escala.
|
|
392
|
-
Cada capa se agrega cuando duele, no antes.</p>
|
|
393
|
-
<div class="dial">
|
|
394
|
-
<div class="lvl"><span class="n">NIVEL 1</span><h4>Un dev solo</h4>
|
|
395
|
-
<p>OpenSpec y el CLI, todo local. La US puede vivir como un <code>.md</code>. Cero
|
|
396
|
-
infraestructura. El link y el check funcionan igual.</p></div>
|
|
397
|
-
<div class="lvl"><span class="n">NIVEL 2</span><h4>Equipo compacto</h4>
|
|
398
|
-
<p>Aparece el tracker (ClickUp/Jira) para coordinar. El link referencia la US; el
|
|
399
|
-
check y el stamp corren a mano o en un hook.</p></div>
|
|
400
|
-
<div class="lvl"><span class="n">NIVEL 3</span><h4>Organización grande</h4>
|
|
401
|
-
<p>Muchos repos, equipos separados, CI que estampa cobertura y matriz de ambientes.
|
|
402
|
-
El mismo protocolo, con más plomería.</p></div>
|
|
403
|
-
</div>
|
|
404
|
-
<p class="lede" style="margin-top:20px">El dev que aprende el nivel chico ya sabe el
|
|
405
|
-
grande. Nadie mantiene dos metodologías.</p>
|
|
406
|
-
</div>
|
|
407
|
-
</section>
|
|
408
|
-
|
|
409
|
-
<!-- ══════════ EMPEZAR ══════════ -->
|
|
410
|
-
<section id="empezar">
|
|
411
|
-
<div class="wrap">
|
|
412
|
-
<p class="eyebrow">Empezar</p>
|
|
413
|
-
<h2>De cero a tu primera US trazada</h2>
|
|
414
|
-
<p class="lede">Requiere Node ≥ 18. El CLI no tiene dependencias — anda incluso en redes
|
|
415
|
-
corporativas cerradas.</p>
|
|
416
|
-
<ol class="start">
|
|
417
|
-
<li><code>npm i -g @dforce2055/dai</code>
|
|
418
|
-
<div class="d">Instala el CLI. El comando queda <code>dai</code>.</div></li>
|
|
419
|
-
<li><code>dai init</code>
|
|
420
|
-
<div class="d">Scaffolder interactivo: eliges asistente (Claude/Copilot), tracker
|
|
421
|
-
(md/ClickUp/Jira) y si sumas OpenSpec. Deja el repo listo.</div></li>
|
|
422
|
-
<li><code>dai link-us <ID></code>
|
|
423
|
-
<div class="d">Trae la US del tracker, crea la branch y el link. Implementas con TDD.</div></li>
|
|
424
|
-
<li><code>dai check</code> → <code>dai pr</code> → <code>dai stamp</code>
|
|
425
|
-
<div class="d">Verificas que sigues al día, creas la PR precargada, y al mergear
|
|
426
|
-
estampas la cobertura. El círculo cerrado.</div></li>
|
|
427
|
-
</ol>
|
|
428
|
-
<div class="cta" style="justify-content:flex-start;margin-top:8px">
|
|
429
|
-
<a class="btn p" href="https://github.com/dforce2055/dai">Documentación completa</a>
|
|
430
|
-
<a class="btn s" href="https://github.com/dforce2055/dai/blob/main/docs/EJEMPLO-END-TO-END.md">Ver un ejemplo real</a>
|
|
431
|
-
</div>
|
|
432
|
-
</div>
|
|
433
|
-
</section>
|
|
434
|
-
|
|
435
|
-
<footer>
|
|
436
|
-
<div class="wrap">
|
|
437
|
-
<span><b style="color:var(--brand)">dai</b> — Desarrollo Asistido por IA</span>
|
|
438
|
-
<span class="sp"></span>
|
|
439
|
-
<a href="https://github.com/dforce2055/dai">GitHub</a>
|
|
440
|
-
<a href="https://www.npmjs.com/package/@dforce2055/dai">npm</a>
|
|
441
|
-
<a href="https://github.com/dforce2055/dai/blob/main/docs/MANIFIESTO.md">Manifiesto</a>
|
|
442
|
-
<a href="https://github.com/dforce2055/dai/blob/main/LICENSE">GPLv3</a>
|
|
443
|
-
</div>
|
|
444
|
-
</footer>
|
|
445
|
-
|
|
446
|
-
<script>
|
|
447
|
-
// JS mínimo: toggle de tema + animaciones de reveal. Nada externo.
|
|
448
|
-
(function(){
|
|
449
|
-
var r=document.documentElement, t=document.getElementById('tg');
|
|
450
|
-
try{var s=localStorage.getItem('dai-theme'); if(s) r.setAttribute('data-theme',s);}catch(e){}
|
|
451
|
-
t.addEventListener('click',function(){
|
|
452
|
-
var cur=r.getAttribute('data-theme');
|
|
453
|
-
var m=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';
|
|
454
|
-
var next=(cur||m)==='dark'?'light':'dark';
|
|
455
|
-
r.setAttribute('data-theme',next);
|
|
456
|
-
try{localStorage.setItem('dai-theme',next);}catch(e){}
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
// Reveal al hacer scroll (fade + slide up), con stagger por grupo.
|
|
460
|
-
var reduce=window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
461
|
-
var els=document.querySelectorAll(
|
|
462
|
-
'section h2, section .lede, .card, .step, .lvl, .split, .demo, .install, ol.start li, .cta');
|
|
463
|
-
if(reduce || !('IntersectionObserver' in window)){ return; } // sin animar: todo visible
|
|
464
|
-
r.classList.add('anim'); // recién acá ocultamos (fallback sin JS = visible)
|
|
465
|
-
els.forEach(function(el){
|
|
466
|
-
el.classList.add('reveal');
|
|
467
|
-
// stagger: retraso según la posición del elemento entre sus hermanos
|
|
468
|
-
var i=0,p=el.previousElementSibling;
|
|
469
|
-
while(p){ i++; p=p.previousElementSibling; }
|
|
470
|
-
el.style.transitionDelay=Math.min(i,6)*70+'ms';
|
|
471
|
-
});
|
|
472
|
-
var io=new IntersectionObserver(function(entries){
|
|
473
|
-
entries.forEach(function(e){
|
|
474
|
-
if(e.isIntersecting){ e.target.classList.add('in'); io.unobserve(e.target); }
|
|
475
|
-
});
|
|
476
|
-
},{threshold:.12, rootMargin:'0px 0px -8% 0px'});
|
|
477
|
-
els.forEach(function(el){ io.observe(el); });
|
|
478
|
-
})();
|
|
479
|
-
</script>
|
|
480
|
-
</body>
|
|
481
|
-
</html>
|