@dforce2055/dai 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +26 -3
- package/VERSION +1 -1
- package/cli/dai.mjs +93 -3
- package/cli/lib/bootstrap.mjs +12 -0
- package/cli/lib/skills-source.mjs +26 -0
- package/docs/adr/0013-skills-externas-install-from.md +56 -0
- package/package.json +1 -1
- package/templates/skill.md +45 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
Formato basado en [Keep a Changelog](https://keepachangelog.com/). Versionado semver
|
|
4
4
|
(ver `VERSION`).
|
|
5
5
|
|
|
6
|
+
## [0.7.0] — 2026-07-14
|
|
7
|
+
|
|
8
|
+
**dai es el distribuidor de skills de cualquier stack, sin opinar sobre su contenido.**
|
|
9
|
+
|
|
10
|
+
### Agregado
|
|
11
|
+
- `dai skills install` — namespace de skills (`dai install` queda como alias). Con
|
|
12
|
+
`--from <git-url|path>[#ref]` instala **skills externas** por-stack (.NET, Java,
|
|
13
|
+
Rust…) desde un repo git (público o privado por SSH) o un path local, convertidas
|
|
14
|
+
para los 3 asistentes (Claude/Cursor/Copilot).
|
|
15
|
+
Self-service, one-off, sin registro; `dai sync` no las toca ([ADR-0013](docs/adr/0013-skills-externas-install-from.md)).
|
|
16
|
+
Valida el contrato mínimo (`SKILL.md` con `name` + `description`) y saltea con aviso
|
|
17
|
+
las malformadas; molde en [`templates/skill.md`](templates/skill.md). No valida el contenido.
|
|
18
|
+
|
|
6
19
|
## [0.6.0] — 2026-07-12
|
|
7
20
|
|
|
8
21
|
Self-update del CLI y blindaje de autoría del repo.
|
|
@@ -181,6 +194,7 @@ ClickUp y Jira Cloud.
|
|
|
181
194
|
- Tests de las rutas de red (jira/clickup/forge) con `fetch` mockeado. Sin links rotos;
|
|
182
195
|
`files` de npm sin tests ni secretos.
|
|
183
196
|
|
|
197
|
+
[0.7.0]: https://github.com/dforce2055/dai/releases/tag/v0.7.0
|
|
184
198
|
[0.6.0]: https://github.com/dforce2055/dai/releases/tag/v0.6.0
|
|
185
199
|
[0.5.0]: https://github.com/dforce2055/dai/releases/tag/v0.5.0
|
|
186
200
|
[0.4.0]: https://github.com/dforce2055/dai/releases/tag/v0.4.0
|
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ deja las skills disponibles:
|
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
51
|
npm i -g @dforce2055/dai # el CLI
|
|
52
|
-
dai install
|
|
52
|
+
dai skills install # skills de IA → Claude y Cursor (global por defecto)
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
Y después, en el chat del asistente, según lo que tengas:
|
|
@@ -157,8 +157,10 @@ flowchart TD
|
|
|
157
157
|
| Comando | Qué hace |
|
|
158
158
|
|---|---|
|
|
159
159
|
| `dai init [<repo>]` | scaffolder interactivo del repo. Flags: `--for claude\|copilot\|both\|cursor\|all` (asistente, default `all`) · `--pm md\|jira\|clickup` (tracker) · `--openspec` |
|
|
160
|
-
| `dai install [--global \| --local <repo>] [--force] [--dry-run] [--for claude\|cursor\|all]` | instala/actualiza skills de
|
|
160
|
+
| `dai skills install [--global \| --local <repo>] [--force] [--dry-run] [--for claude\|cursor\|all]` | instala/actualiza las skills de dai en Claude y/o Cursor (`--for all` por defecto). `--force` re-copia. Alias: **`dai install`**. Ej: `dai skills install --local . --for cursor --force` |
|
|
161
|
+
| `dai skills install --from <git-url\|path>[#ref] [--for …]` | instala **skills externas** (por-stack: .NET, Java, …) desde un repo/dir, **convertidas para los 3 asistentes**. Self-service, one-off, sin registro; `dai sync` no las toca. Colisión con una skill de dai → salta ([ADR-0013](docs/adr/0013-skills-externas-install-from.md)). Ej: `dai skills install --from github.com/mi-org/net-skills` |
|
|
161
162
|
| `dai sync [--dry-run] [--for <asistentes>]` | **refresca** skills, constitución, templates y PR template a la versión del CLI — **aditivo** (no pisa tu `CLAUDE.md`), no toca el `.env` ni OpenSpec. Detecta los asistentes del repo o pasás `--for`. `--dry-run` muestra qué cambiaría ([ADR-0010](docs/adr/0010-versionado-y-upgrade.md)) |
|
|
163
|
+
| `dai upgrade [--check] [--dry-run]` · alias `dai update` | **actualiza el CLI global** a la última publicada (`npm i -g …@latest`) — self-update. **No toca el repo**: reporta el drift del scaffold pero deja el `dai sync` al mantenedor. `--check` solo informa · `--dry-run` muestra el comando ([ADR-0012](docs/adr/0012-upgrade-self-update-del-cli.md)) |
|
|
162
164
|
| `dai publish <us.md>` | crea la US en el tracker (Jira/ClickUp/md) desde un `.md` y devuelve el key. Es el fallback del MCP para publicar sin el asistente |
|
|
163
165
|
| `dai link-us <ID> [--us <md>]` | crea branch + `implements.yaml`; sin `--us` trae la US del tracker |
|
|
164
166
|
| `dai link-us <ID> --resync` | re-estampa el `ac_hash` contra la US viva (tras un ⚠️ de check) |
|
|
@@ -173,12 +175,33 @@ flowchart TD
|
|
|
173
175
|
| `dai doctor` · `dai docs <dest>` · `dai version` | diagnóstico del entorno (incluye **version-drift** del scaffold) · copiar la doc · versión (`dai version` avisa si tu repo quedó atrás) |
|
|
174
176
|
|
|
175
177
|
> **🆕 Mantené tu repo al día — `dai sync`.** Las skills, la constitución y los templates son un
|
|
176
|
-
> *caché derivable* del CLI. Cuando actualizás `dai` (`
|
|
178
|
+
> *caché derivable* del CLI. Cuando actualizás `dai` (`dai upgrade`), **`dai doctor` y
|
|
177
179
|
> `dai version` te avisan solos** si tu scaffold quedó atrás — con color y un `⬆️` —, y **`dai sync`**
|
|
178
180
|
> lo refresca: **aditivo** (conserva tu `CLAUDE.md` propio), sin tocar el `.env` ni OpenSpec. Probá sin
|
|
179
181
|
> riesgo con `dai sync --dry-run`. El versionado es semver: patch/minor no rompen nada; solo un major
|
|
180
182
|
> pediría migración. ([ADR-0010](docs/adr/0010-versionado-y-upgrade.md))
|
|
181
183
|
|
|
184
|
+
> **🧩 Skills de cualquier stack — `dai skills install --from`.** Además de las skills de
|
|
185
|
+
> dai, cada equipo suma las suyas (por-stack: .NET, Java, Rust…) desde su propio repo:
|
|
186
|
+
> `dai skills install --from github.com/tu-org/net-skills`. dai las **convierte para los 3
|
|
187
|
+
> asistentes** (Claude/Cursor/Copilot) e instala. **dai es el distribuidor de skills de
|
|
188
|
+
> cualquier stack, sin opinar sobre su contenido** — self-service, sin registro; `dai sync`
|
|
189
|
+
> sigue siendo solo de dai. ([ADR-0013](docs/adr/0013-skills-externas-install-from.md))
|
|
190
|
+
>
|
|
191
|
+
> La fuente puede ser **pública, privada (por SSH) o un path local**: dai no hace auth
|
|
192
|
+
> propia, delega en git — **si podés `git clone` el repo, dai instala desde ahí**. Para
|
|
193
|
+
> privados usá la forma SSH (`git@github.com:tu-org/net-skills.git`), consistente con el
|
|
194
|
+
> modelo de auth de dai ([ADR-0007](docs/adr/0007-modelo-de-autenticacion.md)). Público =
|
|
195
|
+
> cero fricción entre equipos/máquinas.
|
|
196
|
+
>
|
|
197
|
+
> **Cómo armar una skill que dai ingiera.** Cada skill es un directorio con un `SKILL.md`
|
|
198
|
+
> en **formato Agent Skills de Claude**: frontmatter con **`name`** y **`description`**
|
|
199
|
+
> (obligatorios) + el cuerpo con las instrucciones. dai **valida ese contrato**: si a un
|
|
200
|
+
> `SKILL.md` le falta `name`/`description`, o un dir no tiene `SKILL.md`, lo **saltea con
|
|
201
|
+
> un warn** (no instala una skill rota). El **contenido** no lo valida — bajo tu criterio.
|
|
202
|
+
> Molde: [`templates/skill.md`](templates/skill.md) · ejemplos reales: las skills de dai
|
|
203
|
+
> en [`skills/`](skills/).
|
|
204
|
+
|
|
182
205
|
Skills (se invocan en el asistente): `/doc-to-backlog` · `/grill-intent` · `/grill-epic` · `/grill-user-story` · `/link-us` ·
|
|
183
206
|
`/tdd` · `/dai-review`. Config del tracker (`md`\|`jira`\|`clickup`) y tokens: en `.env` —
|
|
184
207
|
ver [`.env.example`](.env.example). Auth (SSH + tokens): [ADR-0007](docs/adr/0007-modelo-de-autenticacion.md).
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.7.0
|
package/cli/dai.mjs
CHANGED
|
@@ -29,7 +29,8 @@ import { composePrBody, prTitle, forgeTool } from "./lib/pr.mjs";
|
|
|
29
29
|
import { dirsEqual } from "./lib/fsutil.mjs";
|
|
30
30
|
import { parseFlags, parseAssistants, isAssistantToken } from "./lib/args.mjs";
|
|
31
31
|
import { versionDrift, planUpgrade } from "./lib/semver.mjs";
|
|
32
|
-
import {
|
|
32
|
+
import { parseSource } from "./lib/skills-source.mjs";
|
|
33
|
+
import { skillToPrompt, skillToCursor, validateSkill, constitution, constitutionCursorRule, envFor, mergeEnv, upsertBlock, reconcileGitignore } from "./lib/bootstrap.mjs";
|
|
33
34
|
|
|
34
35
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
35
36
|
|
|
@@ -416,6 +417,7 @@ async function cmdPr(opts) {
|
|
|
416
417
|
|
|
417
418
|
// ── install: skills → ~/.claude/skills o <repo>/.claude/skills ────────────────
|
|
418
419
|
async function cmdInstall(opts) {
|
|
420
|
+
if (opts.from !== undefined) return cmdInstallFrom(opts); // skills externas (ADR-0013)
|
|
419
421
|
const skillsSrc = join(ROOT, "skills");
|
|
420
422
|
const skills = readdirSync(skillsSrc).filter((n) => statSync(join(skillsSrc, n)).isDirectory());
|
|
421
423
|
let want;
|
|
@@ -489,6 +491,89 @@ async function cmdInstall(opts) {
|
|
|
489
491
|
if (rl) rl.close();
|
|
490
492
|
}
|
|
491
493
|
|
|
494
|
+
// ── skills install --from: skills EXTERNAS (por-stack) desde un repo/dir ───────
|
|
495
|
+
// Self-service, one-off, sin registro ni sync (ADR-0013). Convierte cada skill
|
|
496
|
+
// para los 3 asistentes y la instala en el repo. No pisa las skills built-in de
|
|
497
|
+
// dai (colisión → warn + skip). `dai sync` NO las toca: es solo de dai.
|
|
498
|
+
function cmdInstallFrom(opts) {
|
|
499
|
+
if (typeof opts.from !== "string" || !opts.from.trim())
|
|
500
|
+
fail("--from necesita una fuente: un git URL (github.com/org/skills[#ref]) o un path local", 2);
|
|
501
|
+
let want;
|
|
502
|
+
try { want = parseAssistants(typeof opts.for === "string" ? opts.for : "all"); }
|
|
503
|
+
catch (e) { fail(`--for ${e.message}`); }
|
|
504
|
+
|
|
505
|
+
let src;
|
|
506
|
+
try { src = parseSource(opts.from); } catch (e) { fail(`--from ${e.message}`, 2); }
|
|
507
|
+
|
|
508
|
+
// Resolver la fuente a un directorio local.
|
|
509
|
+
let root, tmp = null;
|
|
510
|
+
if (src.type === "git") {
|
|
511
|
+
tmp = mkdtempSync(join(tmpdir(), "dai-skills-"));
|
|
512
|
+
info(`clonando ${src.location}${src.ref ? " @ " + src.ref : ""} …`);
|
|
513
|
+
const args = ["clone", "--depth", "1"];
|
|
514
|
+
if (src.ref) args.push("--branch", src.ref);
|
|
515
|
+
args.push(src.location, tmp);
|
|
516
|
+
try { git(args); }
|
|
517
|
+
catch (e) { rmSync(tmp, { recursive: true, force: true }); fail(`no pude clonar la fuente: ${String(e.message).split("\n")[0]}`, 1); }
|
|
518
|
+
root = tmp;
|
|
519
|
+
} else {
|
|
520
|
+
root = src.location;
|
|
521
|
+
if (!existsSync(root)) fail(`no existe la fuente: ${root}`, 2);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const cleanup = () => { if (tmp) rmSync(tmp, { recursive: true, force: true }); };
|
|
525
|
+
|
|
526
|
+
// El dir de skills: <root>/skills, o <root> si ya contiene <name>/SKILL.md.
|
|
527
|
+
const skillsDir = existsSync(join(root, "skills")) ? join(root, "skills") : root;
|
|
528
|
+
let entries;
|
|
529
|
+
try {
|
|
530
|
+
entries = readdirSync(skillsDir).filter((n) => {
|
|
531
|
+
try { return statSync(join(skillsDir, n)).isDirectory(); } catch { return false; }
|
|
532
|
+
});
|
|
533
|
+
} catch { cleanup(); fail(`no pude leer la fuente: ${skillsDir}`, 2); }
|
|
534
|
+
if (entries.length === 0) { cleanup(); fail("la fuente no tiene directorios de skill", 2); }
|
|
535
|
+
|
|
536
|
+
// Destino: repo local (default cwd), o --local <repo> / --global.
|
|
537
|
+
const repo = opts.global ? homedir() : (typeof opts.local === "string" ? opts.local : process.cwd());
|
|
538
|
+
const builtins = new Set(readdirSync(join(ROOT, "skills")));
|
|
539
|
+
const forStr = ["claude", "copilot", "cursor"].filter((a) => want[a]).join("+");
|
|
540
|
+
info(`skills install --from ${opts.from} · ${entries.length} candidata(s) · asistentes: ${forStr}${opts.dryRun ? " [dry-run]" : ""}`);
|
|
541
|
+
|
|
542
|
+
let installed = 0, skipped = 0;
|
|
543
|
+
for (const name of entries) {
|
|
544
|
+
const srcDir = join(skillsDir, name);
|
|
545
|
+
const skillPath = join(srcDir, "SKILL.md");
|
|
546
|
+
// Validación estructural (contrato mínimo, no contenido — ADR-0013).
|
|
547
|
+
if (!existsSync(skillPath)) { warn(`'${name}' no tiene SKILL.md — salto`); skipped++; continue; }
|
|
548
|
+
if (builtins.has(name)) { warn(`'${name}' choca con una skill de dai — salto (renombrala, p.ej. ${name}-<stack>)`); skipped++; continue; }
|
|
549
|
+
const md = readFileSync(skillPath, "utf8");
|
|
550
|
+
const bad = validateSkill(md);
|
|
551
|
+
if (bad) { warn(`'${name}' inválida: ${bad} — salto (ver templates/skill.md)`); skipped++; continue; }
|
|
552
|
+
if (opts.dryRun) { info(`[dry-run] ${name} → ${forStr}`); continue; }
|
|
553
|
+
if (want.claude) {
|
|
554
|
+
const t = join(repo, ".claude", "skills", name);
|
|
555
|
+
rmSync(t, { recursive: true, force: true }); mkdirSync(dirname(t), { recursive: true }); cpSync(srcDir, t, { recursive: true });
|
|
556
|
+
}
|
|
557
|
+
if (want.cursor) {
|
|
558
|
+
const t = join(repo, ".cursor", "skills", name);
|
|
559
|
+
rmSync(t, { recursive: true, force: true }); cpSync(srcDir, t, { recursive: true }); writeFileSync(join(t, "SKILL.md"), skillToCursor(md));
|
|
560
|
+
}
|
|
561
|
+
if (want.copilot) {
|
|
562
|
+
const pdir = join(repo, ".github", "prompts"); mkdirSync(pdir, { recursive: true });
|
|
563
|
+
writeFileSync(join(pdir, `${name}.prompt.md`), skillToPrompt(md));
|
|
564
|
+
}
|
|
565
|
+
ok(name); installed++;
|
|
566
|
+
}
|
|
567
|
+
cleanup();
|
|
568
|
+
if (opts.dryRun) { info("dry-run: nada escrito."); return; }
|
|
569
|
+
process.stdout.write("\n");
|
|
570
|
+
if (installed === 0) {
|
|
571
|
+
fail(`0 skills instaladas${skipped ? ` (${skipped} salteada(s))` : ""} — revisá el formato: cada skill es un dir con SKILL.md y frontmatter (name + description). Molde en templates/skill.md`, 1);
|
|
572
|
+
}
|
|
573
|
+
ok(`${installed} skill(s) externa(s) instalada(s)${skipped ? `, ${skipped} salteada(s)` : ""} desde ${opts.from}`);
|
|
574
|
+
warn("skills externas — bajo tu criterio: dai las convierte e instala, no las vetea ni las trackea. `dai sync` no las toca.");
|
|
575
|
+
}
|
|
576
|
+
|
|
492
577
|
// ── helpers de prompt interactivo ─────────────────────────────────────────────
|
|
493
578
|
async function askMenu(rl, title, choices, def) {
|
|
494
579
|
process.stdout.write(`\n ${title}\n`);
|
|
@@ -935,7 +1020,11 @@ switch (cmd) {
|
|
|
935
1020
|
case "pr": cmdPr(opts).catch((e) => fail(String(e.message))); break;
|
|
936
1021
|
case "done": cmdDone(opts); break;
|
|
937
1022
|
case "archive": cmdArchive(pos[0], opts); break;
|
|
938
|
-
case "install": cmdInstall(opts).catch((e) => fail(String(e.message))); break;
|
|
1023
|
+
case "install": cmdInstall(opts).catch((e) => fail(String(e.message))); break; // alias de `dai skills install`
|
|
1024
|
+
case "skills":
|
|
1025
|
+
if (pos[0] === "install" || pos[0] === undefined) cmdInstall(opts).catch((e) => fail(String(e.message)));
|
|
1026
|
+
else fail(`subcomando de skills desconocido: '${pos[0]}' (por ahora: install)`, 2);
|
|
1027
|
+
break;
|
|
939
1028
|
case "init": cmdInit(pos[0], opts).catch((e) => fail(String(e.message))); break;
|
|
940
1029
|
case "sync": cmdSync(pos[0], opts); break;
|
|
941
1030
|
case "upgrade":
|
|
@@ -959,7 +1048,8 @@ switch (cmd) {
|
|
|
959
1048
|
" pr [--assignee u] [--base b] [--draft] [--yes] crea TU PR/MR precargada (muestra + confirma)\n" +
|
|
960
1049
|
" forge comment <ref> --body-file <f> · forge pr <ref> comentar/leer una PR ajena (github/gitlab)\n\n" +
|
|
961
1050
|
"Instalación:\n" +
|
|
962
|
-
" install [--global | --local <repo>] [--force] [--dry-run] [--for <asistentes>] skills
|
|
1051
|
+
" skills install [--global | --local <repo>] [--force] [--dry-run] [--for <asistentes>] instala las skills de dai (alias: `install`)\n" +
|
|
1052
|
+
" skills install --from <git-url|path>[#ref] [--for <asistentes>] instala skills EXTERNAS (por-stack), convertidas para los 3 asistentes (ADR-0013)\n" +
|
|
963
1053
|
" init [<repo>] scaffolder interactivo del repo (asistente, gestor, OpenSpec)\n" +
|
|
964
1054
|
" --for <asistentes> claude|copilot|cursor (combinables con coma) · o both|all (default all)\n" +
|
|
965
1055
|
" ej: --for claude,cursor · --for copilot · --for all\n" +
|
package/cli/lib/bootstrap.mjs
CHANGED
|
@@ -14,6 +14,18 @@ export function parseFrontmatter(md) {
|
|
|
14
14
|
return { name, description, body: m[2].trim() };
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// Valida el contrato MÍNIMO de un SKILL.md para que dai lo ingiera y lo convierta a
|
|
18
|
+
// los 3 asistentes (y para que Claude/Cursor/Copilot lo carguen): frontmatter con
|
|
19
|
+
// `name` y `description`. Devuelve null si está OK, o un string con el motivo.
|
|
20
|
+
// NO valida el contenido de la skill — eso es criterio del equipo (ADR-0013).
|
|
21
|
+
export function validateSkill(md) {
|
|
22
|
+
const { name, description } = parseFrontmatter(md);
|
|
23
|
+
if (!name && !description) return "sin frontmatter (falta name y description)";
|
|
24
|
+
if (!name) return "falta 'name' en el frontmatter";
|
|
25
|
+
if (!description) return "falta 'description' en el frontmatter";
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
17
29
|
// Transforma un SKILL.md (Claude) en un prompt file de Copilot (.prompt.md).
|
|
18
30
|
// Cambia el frontmatter; el cuerpo (la lógica) es el mismo.
|
|
19
31
|
export function skillToPrompt(md) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// dai · resolución de una fuente de skills externas (`dai skills install --from`).
|
|
2
|
+
// Núcleo puro (sin fs ni red): clasifica el argumento en git URL o path local y
|
|
3
|
+
// separa el ref (`#branch`/`#tag`). Ver ADR-0013.
|
|
4
|
+
|
|
5
|
+
// Devuelve { type: 'git'|'path', location, ref }.
|
|
6
|
+
// git → URL clonable. `host/org/repo` (sin esquema) se normaliza a https://.
|
|
7
|
+
// path → ruta local (relativa o absoluta). El `ref` se ignora aguas abajo.
|
|
8
|
+
// ref → lo que va después de '#', o null.
|
|
9
|
+
export function parseSource(src) {
|
|
10
|
+
const raw = String(src ?? "").trim();
|
|
11
|
+
if (!raw) throw new Error("fuente vacía (pasá un git URL o un path)");
|
|
12
|
+
|
|
13
|
+
// Separar el ref (#branch/tag). El scp de git (git@host:org/repo) no usa '#'.
|
|
14
|
+
let ref = null, loc = raw;
|
|
15
|
+
const hash = raw.lastIndexOf("#");
|
|
16
|
+
if (hash > 0) { ref = raw.slice(hash + 1) || null; loc = raw.slice(0, hash); }
|
|
17
|
+
|
|
18
|
+
// Path local explícito (./ ../ / ~/).
|
|
19
|
+
if (/^(\.\.?\/|\/|~\/)/.test(loc)) return { type: "path", location: loc, ref };
|
|
20
|
+
// git por sintaxis de URL (https, ssh, scp git@host:…).
|
|
21
|
+
if (/^(https?:\/\/|ssh:\/\/|git@|[\w.-]+@)/.test(loc)) return { type: "git", location: loc, ref };
|
|
22
|
+
// host/org/repo (p.ej. github.com/org/skills) → git https.
|
|
23
|
+
if (/^[\w.-]+\.[\w.-]+\/.+/.test(loc)) return { type: "git", location: "https://" + loc, ref };
|
|
24
|
+
// Resto: path local relativo sin ./.
|
|
25
|
+
return { type: "path", location: loc, ref };
|
|
26
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# ADR-0013 — `dai skills install --from` (skills externas por-stack)
|
|
2
|
+
|
|
3
|
+
- **Estado:** aceptado
|
|
4
|
+
- **Fecha:** 2026-07-14
|
|
5
|
+
- **Decide:** lead / arquitecto de la metodología
|
|
6
|
+
|
|
7
|
+
## Contexto
|
|
8
|
+
|
|
9
|
+
dai distribuye **sus** skills (las bundleadas: `grill-*`, `link-us`, `tdd`, …) e
|
|
10
|
+
instala/convierte para Claude, Cursor y Copilot. Pero los equipos tienen skills
|
|
11
|
+
**propias de su stack** (.NET, Java, Rust, …) que hoy no tienen lugar: o las copian
|
|
12
|
+
a mano por repo (duplicación, sin conversión a los 3 asistentes), o las meten en el
|
|
13
|
+
paquete dai — que **rompe el ADN**: dai es agnóstico del stack, opina solo en su
|
|
14
|
+
dominio (trazabilidad/distribución), no sobre *qué* dicen tus skills.
|
|
15
|
+
|
|
16
|
+
Falta una forma de que dai **convierta e instale** skills externas, sin volverse
|
|
17
|
+
dueño ni gatekeeper de ellas.
|
|
18
|
+
|
|
19
|
+
## Decisión
|
|
20
|
+
|
|
21
|
+
Agregamos **`dai skills install --from <git-url|path>[#ref]`**: instala skills
|
|
22
|
+
**externas** desde un repo/dir (con estructura `skills/<nombre>/SKILL.md`, la misma
|
|
23
|
+
de dai), **convertidas para los 3 asistentes** (Claude copia · Cursor `skillToCursor`
|
|
24
|
+
· Copilot `skillToPrompt` → `.github/prompts/`).
|
|
25
|
+
|
|
26
|
+
- **`dai skills` es el namespace canónico** de las operaciones de skills, consistente
|
|
27
|
+
con `dai forge <verb>`. `dai skills install` (sin `--from`) instala las de dai;
|
|
28
|
+
**`dai install` queda como alias** silencioso (backward-compatible).
|
|
29
|
+
- **Self-service, one-off, sin registro.** No hay autorización central ni persistencia:
|
|
30
|
+
no existe `.dai/sources`, dai **no lleva registro** de qué repos usan skills externas.
|
|
31
|
+
El equipo las suma **bajo su propio criterio**.
|
|
32
|
+
- **`dai sync` NO las toca** — sigue siendo **solo** de las skills de dai. Si el equipo
|
|
33
|
+
actualiza sus skills, re-corre `--from`.
|
|
34
|
+
- **Colisión** con una skill built-in de dai → **warn + skip** (no se pisa la
|
|
35
|
+
metodología; renombran, p. ej. `tdd-dotnet`).
|
|
36
|
+
|
|
37
|
+
## Consecuencias
|
|
38
|
+
|
|
39
|
+
- **Más fácil:** cada equipo suma sus skills por-stack sin tocar dai ni pedir permiso,
|
|
40
|
+
y las escribe **una vez** (dai las sirve a Claude/Cursor/Copilot). El namespace
|
|
41
|
+
`dai skills` deja lugar a crecer (`skills list`, …) sin comandos sueltos.
|
|
42
|
+
- **Se acepta pagar:** es **one-off** (dai no mantiene esas skills al día; re-corrés
|
|
43
|
+
`--from`). **Sin registro** → dai no sabe qué repos tienen skills externas, a
|
|
44
|
+
propósito: cero gatekeeping, bajo riesgo del equipo. Las fuentes remotas dependen de
|
|
45
|
+
git/red, y la **auth se delega en git** (público sin más; privado por SSH o credential
|
|
46
|
+
helper — dai no autentica): *si podés `git clone` la fuente, dai instala desde ahí*.
|
|
47
|
+
|
|
48
|
+
## Alternativas consideradas
|
|
49
|
+
|
|
50
|
+
- **Persistido (`.dai/sources`) + integrado a `dai sync`** — descartado: obliga a dai a
|
|
51
|
+
**registrar y mantener** fuentes externas (gatekeeping que el equipo no quiere), y
|
|
52
|
+
acopla `dai sync` —que debe ser solo de dai— a repos ajenos.
|
|
53
|
+
- **Meter las skills de stack en el paquete dai** — descartado: rompe el ADN (dai
|
|
54
|
+
agnóstico del stack).
|
|
55
|
+
- **Comando suelto `dai install --from` sin namespace** — descartado: `dai skills <verb>`
|
|
56
|
+
es más claro y consistente con `dai forge <verb>`; `dai install` queda como alias.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dforce2055/dai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
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/",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
MOLDE DE SKILL · dai
|
|
3
|
+
─────────────────────────────────────────────────────────────────
|
|
4
|
+
Formato: Agent Skills de Claude (SKILL.md). dai lo INGIERE y lo convierte a los 3
|
|
5
|
+
asistentes (Claude copia · Cursor `skillToCursor` · Copilot `.prompt.md`), con
|
|
6
|
+
`dai skills install --from <repo|path>` (ADR-0013).
|
|
7
|
+
|
|
8
|
+
Estructura del repo/dir fuente:
|
|
9
|
+
skills/
|
|
10
|
+
<nombre-de-la-skill>/
|
|
11
|
+
SKILL.md ← este archivo (renombrado a SKILL.md, en MAYÚSCULAS)
|
|
12
|
+
<recursos...> ← opcional: scripts, plantillas, refs que la skill use
|
|
13
|
+
|
|
14
|
+
CONTRATO MÍNIMO que dai valida (si falta, la salta con un warn):
|
|
15
|
+
- frontmatter con `name` y `description` (ambos obligatorios, en una línea).
|
|
16
|
+
- `name`: slug en kebab-case, IGUAL al nombre del directorio.
|
|
17
|
+
- `description`: una frase — con esto el agente decide CUÁNDO usar la skill.
|
|
18
|
+
|
|
19
|
+
dai NO valida el CONTENIDO (qué dice la skill, qué hacen sus scripts): eso es
|
|
20
|
+
criterio del equipo. Las instalás bajo tu propio riesgo.
|
|
21
|
+
-->
|
|
22
|
+
---
|
|
23
|
+
name: mi-skill
|
|
24
|
+
description: Qué hace la skill y cuándo conviene invocarla — concreto y orientado al disparador.
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# <Título de la skill>
|
|
28
|
+
|
|
29
|
+
Instrucciones para el agente: qué hacer, paso a paso. El **cuerpo es el mismo** para
|
|
30
|
+
Claude, Cursor y Copilot — dai solo ajusta el frontmatter por asistente.
|
|
31
|
+
|
|
32
|
+
## Cuándo usarla
|
|
33
|
+
|
|
34
|
+
El disparador concreto (qué pide el usuario, o qué situación la activa).
|
|
35
|
+
|
|
36
|
+
## Pasos
|
|
37
|
+
|
|
38
|
+
1. …
|
|
39
|
+
2. …
|
|
40
|
+
3. …
|
|
41
|
+
|
|
42
|
+
## Notas
|
|
43
|
+
|
|
44
|
+
Convenciones del stack, ejemplos, o límites de la skill. Si necesita archivos de
|
|
45
|
+
apoyo, ponelos junto al `SKILL.md` y referencialos por ruta relativa.
|