@dforce2055/dai 0.5.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 +30 -0
- package/CONTRIBUTING.md +12 -0
- package/README.md +27 -4
- package/VERSION +1 -1
- package/cli/dai.mjs +134 -4
- package/cli/lib/bootstrap.mjs +12 -0
- package/cli/lib/semver.mjs +15 -0
- package/cli/lib/skills-source.mjs +26 -0
- package/docs/adr/0012-upgrade-self-update-del-cli.md +59 -0
- package/docs/adr/0013-skills-externas-install-from.md +56 -0
- package/governance/commit-convention.md +4 -0
- package/governance/human-authorship.md +61 -0
- package/package.json +1 -1
- package/templates/skill.md +45 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,34 @@
|
|
|
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
|
+
|
|
19
|
+
## [0.6.0] — 2026-07-12
|
|
20
|
+
|
|
21
|
+
Self-update del CLI y blindaje de autoría del repo.
|
|
22
|
+
|
|
23
|
+
### Agregado
|
|
24
|
+
- `dai upgrade` (alias `update`): actualiza el CLI global a la última publicada
|
|
25
|
+
(`npm i -g …@latest`), con `--check` y `--dry-run`. No toca el repo: reporta el
|
|
26
|
+
drift del scaffold pero deja el `dai sync` explícito del mantenedor (ADR-0012).
|
|
27
|
+
|
|
28
|
+
### Interno
|
|
29
|
+
- Guard de autoría: rechaza commits autorados/co-autorados por agentes de IA
|
|
30
|
+
(check de CI `authorship` + hook local + `governance/human-authorship.md`).
|
|
31
|
+
- Eliminado `.mailmap` (ya no cumplía función).
|
|
32
|
+
- **111 tests** (+1: `planUpgrade`).
|
|
33
|
+
|
|
6
34
|
## [0.5.0] — 2026-07-10
|
|
7
35
|
|
|
8
36
|
**`archive` en el flujo** ([ADR-0011](docs/adr/0011-archive-gate-de-aprobacion.md)): cerrar el CÓMO
|
|
@@ -166,6 +194,8 @@ ClickUp y Jira Cloud.
|
|
|
166
194
|
- Tests de las rutas de red (jira/clickup/forge) con `fetch` mockeado. Sin links rotos;
|
|
167
195
|
`files` de npm sin tests ni secretos.
|
|
168
196
|
|
|
197
|
+
[0.7.0]: https://github.com/dforce2055/dai/releases/tag/v0.7.0
|
|
198
|
+
[0.6.0]: https://github.com/dforce2055/dai/releases/tag/v0.6.0
|
|
169
199
|
[0.5.0]: https://github.com/dforce2055/dai/releases/tag/v0.5.0
|
|
170
200
|
[0.4.0]: https://github.com/dforce2055/dai/releases/tag/v0.4.0
|
|
171
201
|
[0.3.1]: https://github.com/dforce2055/dai/releases/tag/v0.3.1
|
package/CONTRIBUTING.md
CHANGED
|
@@ -16,6 +16,13 @@ git clone <tu-fork> dai && cd dai
|
|
|
16
16
|
npm test # 48 tests, sin `npm install` (no hay deps)
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
Opcional, para que los hooks del repo te avisen antes de commitear (cero
|
|
20
|
+
dependencias, sin husky):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
git config core.hooksPath .githooks # valida convención de commits + autoría humana
|
|
24
|
+
```
|
|
25
|
+
|
|
19
26
|
## Reglas de oro
|
|
20
27
|
|
|
21
28
|
1. **Cero dependencias de runtime.** El CLI corre en redes cerradas sin `npm install`
|
|
@@ -29,6 +36,11 @@ npm test # 48 tests, sin `npm install` (no hay deps)
|
|
|
29
36
|
necesita un ADR (ver abajo).
|
|
30
37
|
4. **Secretos nunca en el repo.** Tokens solo en `.env` (gitignored) o el secret
|
|
31
38
|
store del CI. git usa **SSH**, las APIs usan **tokens scopeados** (ADR-0007).
|
|
39
|
+
5. **Autoría humana.** El código de `dai` lo **autora y revisa una persona**. No se
|
|
40
|
+
aceptan commits cuyo *author*, *committer* o `Co-authored-by` sea un agente o bot
|
|
41
|
+
(Cursor, Copilot, Claude, …). Un agente puede ayudarte, pero el cambio lo firmas
|
|
42
|
+
tú: con tu identidad y sin el trailer del agente. Un check de CI lo bloquea en cada
|
|
43
|
+
PR (ver [`governance/human-authorship.md`](governance/human-authorship.md)).
|
|
32
44
|
|
|
33
45
|
## Flujo (la propia metodología)
|
|
34
46
|
|
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:
|
|
@@ -127,7 +127,7 @@ flowchart TD
|
|
|
127
127
|
|
|
128
128
|
| # | Fase | Cómo | Quién |
|
|
129
129
|
|---|---|---|---|
|
|
130
|
-
| 1 | **Instalar el CLI** | `npm i -g @dforce2055/dai` (o `npm link` en dev) · `dai --version` | dev |
|
|
130
|
+
| 1 | **Instalar el CLI** | `npm i -g @dforce2055/dai` (o `npm link` en dev) · `dai --version` · actualizar después: `dai upgrade` | dev |
|
|
131
131
|
| 2 | **Bootstrap del repo** | `dai init` → `.dai` + Claude/Copilot/Cursor + config + PR template | dev/lead |
|
|
132
132
|
| 3a | **Definir el QUÉ** | `/grill-user-story` (una US) · `/grill-epic` (algo grande) · `/doc-to-backlog` (un doc) — te interrogan hasta una US testeable | PO / analista |
|
|
133
133
|
| 3b | **Publicar la US** | la skill la sube al tracker vía **MCP**, o con **`dai publish <us.md>`** (crea el issue vía token, sin MCP) → devuelve el key | PO / IA |
|
|
@@ -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
|
@@ -28,8 +28,9 @@ 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
30
|
import { parseFlags, parseAssistants, isAssistantToken } from "./lib/args.mjs";
|
|
31
|
-
import { versionDrift } from "./lib/semver.mjs";
|
|
32
|
-
import {
|
|
31
|
+
import { versionDrift, planUpgrade } from "./lib/semver.mjs";
|
|
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`);
|
|
@@ -822,6 +907,43 @@ function reportDrift(repo = process.cwd()) {
|
|
|
822
907
|
return status;
|
|
823
908
|
}
|
|
824
909
|
|
|
910
|
+
// ── upgrade: self-update del CLI global (ADR-0012) ────────────────────────────
|
|
911
|
+
// Actualiza el paquete npm global de dai a la última publicada. NO toca el repo:
|
|
912
|
+
// si hay drift del scaffold, solo lo reporta (el `dai sync` queda explícito, del
|
|
913
|
+
// mantenedor). El nombre sale de package.json → robusto si cambia el scope.
|
|
914
|
+
function cmdUpgrade(opts) {
|
|
915
|
+
const name = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).name;
|
|
916
|
+
const currentV = readFileSync(join(ROOT, "VERSION"), "utf8").trim();
|
|
917
|
+
const manual = C.cy(`npm i -g ${name}@latest`);
|
|
918
|
+
info(`dai upgrade — CLI v${currentV} (${name})`);
|
|
919
|
+
|
|
920
|
+
let latestV;
|
|
921
|
+
try {
|
|
922
|
+
latestV = execFileSync(npmBin("npm"), ["view", name, "version"], { encoding: "utf8" }).trim();
|
|
923
|
+
} catch {
|
|
924
|
+
fail(`no pude consultar el registry (¿sin red?). Actualizá a mano: ${manual}`, 1);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const plan = planUpgrade(currentV, latestV);
|
|
928
|
+
if (plan.action === "unknown") { warn(`no pude comparar versiones (actual '${currentV}', última '${latestV}')`); return; }
|
|
929
|
+
if (plan.action === "up-to-date") { ok(`ya estás en la última (v${currentV})`); reportDrift(); return; }
|
|
930
|
+
if (plan.action === "ahead") { info(`tu CLI (v${currentV}) es más nuevo que el registry (v${latestV}) — nada que actualizar`); reportDrift(); return; }
|
|
931
|
+
|
|
932
|
+
// plan.action === "upgrade"
|
|
933
|
+
if (opts.check) { process.stdout.write(`${C.b(C.y("⬆️ hay update"))} — v${plan.from} → v${plan.to}. Corré ${C.cy("dai upgrade")}\n`); return; }
|
|
934
|
+
if (opts.dryRun) { info(`[dry-run] correría: npm i -g ${name}@latest (v${plan.from} → v${plan.to})`); return; }
|
|
935
|
+
|
|
936
|
+
info(`actualizando v${plan.from} → v${plan.to} …`);
|
|
937
|
+
try {
|
|
938
|
+
execFileSync(npmBin("npm"), ["install", "-g", `${name}@latest`], { stdio: "inherit" });
|
|
939
|
+
} catch {
|
|
940
|
+
fail(`el install falló. Probá a mano: ${manual}`, 1);
|
|
941
|
+
}
|
|
942
|
+
ok(`CLI actualizado a v${plan.to}`);
|
|
943
|
+
const st = reportDrift();
|
|
944
|
+
if (st === null) process.stdout.write(" (No estás en un repo dai — entrá al repo y, si hace falta, corré `dai sync`.)\n");
|
|
945
|
+
}
|
|
946
|
+
|
|
825
947
|
// ── doctor: diagnóstico ───────────────────────────────────────────────────────
|
|
826
948
|
function cmdDoctor() {
|
|
827
949
|
loadEnv();
|
|
@@ -898,9 +1020,15 @@ switch (cmd) {
|
|
|
898
1020
|
case "pr": cmdPr(opts).catch((e) => fail(String(e.message))); break;
|
|
899
1021
|
case "done": cmdDone(opts); break;
|
|
900
1022
|
case "archive": cmdArchive(pos[0], opts); break;
|
|
901
|
-
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;
|
|
902
1028
|
case "init": cmdInit(pos[0], opts).catch((e) => fail(String(e.message))); break;
|
|
903
1029
|
case "sync": cmdSync(pos[0], opts); break;
|
|
1030
|
+
case "upgrade":
|
|
1031
|
+
case "update": cmdUpgrade(opts); break;
|
|
904
1032
|
case "docs": cmdDocs(pos[0]); break;
|
|
905
1033
|
case "doctor": cmdDoctor(); break;
|
|
906
1034
|
case "version": cmdVersion(); break;
|
|
@@ -920,12 +1048,14 @@ switch (cmd) {
|
|
|
920
1048
|
" pr [--assignee u] [--base b] [--draft] [--yes] crea TU PR/MR precargada (muestra + confirma)\n" +
|
|
921
1049
|
" forge comment <ref> --body-file <f> · forge pr <ref> comentar/leer una PR ajena (github/gitlab)\n\n" +
|
|
922
1050
|
"Instalación:\n" +
|
|
923
|
-
" 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" +
|
|
924
1053
|
" init [<repo>] scaffolder interactivo del repo (asistente, gestor, OpenSpec)\n" +
|
|
925
1054
|
" --for <asistentes> claude|copilot|cursor (combinables con coma) · o both|all (default all)\n" +
|
|
926
1055
|
" ej: --for claude,cursor · --for copilot · --for all\n" +
|
|
927
1056
|
" --pm md|jira|clickup · --openspec (con flags salteas las preguntas)\n" +
|
|
928
1057
|
" sync [<repo>] [--dry-run] [--for <asistentes>] refresca skills/constitución/templates a la versión del CLI (aditivo; no toca .env ni OpenSpec)\n" +
|
|
1058
|
+
" upgrade [--check] [--dry-run] (alias: update) actualiza el CLI global a la última (npm i -g …@latest) y avisa si el repo quedó atrasado (ADR-0012)\n" +
|
|
929
1059
|
" docs <destino> documentación conceptual → <destino>\n" +
|
|
930
1060
|
" doctor diagnóstico del entorno\n\n" +
|
|
931
1061
|
" (config: .env — ver .env.example)\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) {
|
package/cli/lib/semver.mjs
CHANGED
|
@@ -28,3 +28,18 @@ export function versionDrift(repoV, cliV) {
|
|
|
28
28
|
if (cmp < 0) return "cli-behind";
|
|
29
29
|
return c.major > r.major ? "major-behind" : "minor-behind";
|
|
30
30
|
}
|
|
31
|
+
|
|
32
|
+
// Plan de `dai upgrade` (ADR-0012): compara el CLI instalado (currentV) con la
|
|
33
|
+
// última publicada en el registry (latestV). Núcleo puro; el I/O (npm view /
|
|
34
|
+
// npm i -g) vive en el comando.
|
|
35
|
+
// up-to-date → iguales → { action, version }
|
|
36
|
+
// ahead → el CLI local es más nuevo → { action, current, latest }
|
|
37
|
+
// upgrade → hay una versión más nueva → { action, from, to }
|
|
38
|
+
// unknown → alguna versión no parsea → { action }
|
|
39
|
+
export function planUpgrade(currentV, latestV) {
|
|
40
|
+
const cmp = compareVersions(currentV, latestV);
|
|
41
|
+
if (cmp === null) return { action: "unknown" };
|
|
42
|
+
if (cmp === 0) return { action: "up-to-date", version: currentV };
|
|
43
|
+
if (cmp > 0) return { action: "ahead", current: currentV, latest: latestV };
|
|
44
|
+
return { action: "upgrade", from: currentV, to: latestV };
|
|
45
|
+
}
|
|
@@ -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,59 @@
|
|
|
1
|
+
# ADR-0012 — `dai upgrade` (self-update del CLI)
|
|
2
|
+
|
|
3
|
+
- **Estado:** aceptado
|
|
4
|
+
- **Fecha:** 2026-07-12
|
|
5
|
+
- **Decide:** lead / arquitecto de la metodología
|
|
6
|
+
|
|
7
|
+
## Contexto
|
|
8
|
+
|
|
9
|
+
dai tiene **dos cosas versionadas e independientes**: el **CLI** (el paquete npm
|
|
10
|
+
global, una copia por dev) y el **scaffold del repo** (`.dai/`, skills, `.dai/VERSION`,
|
|
11
|
+
compartido y commiteado). El drift entre ambos ya se **detecta** (`versionDrift`,
|
|
12
|
+
ADR-0010) y `dai doctor`/`dai version` imprimen el estado.
|
|
13
|
+
|
|
14
|
+
Pero actualizar el **CLI** era **manual**: cuando el detector veía el CLI atrasado,
|
|
15
|
+
imprimía el texto `npm i -g @dforce2055/dai` para que el dev lo copiara y corriera a
|
|
16
|
+
mano. No había comando. Fricción repetida, y un detector que empujaba a algo que no
|
|
17
|
+
existía como acción de dai.
|
|
18
|
+
|
|
19
|
+
Escenario típico: un dev vuelve el lunes con el CLI en v0.2.0 y ya salió v0.5.0.
|
|
20
|
+
Tiene que actualizar su CLI (y después traer el repo con `git pull`, que el
|
|
21
|
+
mantenedor ya sincronizó).
|
|
22
|
+
|
|
23
|
+
## Decisión
|
|
24
|
+
|
|
25
|
+
Agregamos **`dai upgrade`** (alias `update`): **self-update del CLI global** a la
|
|
26
|
+
última versión publicada en el registry.
|
|
27
|
+
|
|
28
|
+
- **Scope: solo el CLI.** Corre `npm i -g <name>@latest` (el `<name>` sale de
|
|
29
|
+
`package.json`, robusto si cambia el scope). `--check` solo informa; `--dry-run`
|
|
30
|
+
muestra el comando sin correrlo.
|
|
31
|
+
- **NO toca el repo.** Tras actualizar, **reporta** el drift del scaffold (`reportDrift`)
|
|
32
|
+
pero **no corre `dai sync`**. El `sync` queda como acto **explícito del mantenedor**:
|
|
33
|
+
un dev con CLI viejo que sincronizara **degradaría** el `.dai/` del repo, y sincronizar
|
|
34
|
+
desde cada máquina genera commits en conflicto.
|
|
35
|
+
- **El núcleo es puro y testeado** (`planUpgrade` en `lib/semver.mjs`): decide
|
|
36
|
+
`up-to-date | ahead | upgrade`. El I/O (`npm view` / `npm i -g`) es la cáscara del
|
|
37
|
+
comando, no se unit-testea (mismo criterio que el `fetch`).
|
|
38
|
+
|
|
39
|
+
## Consecuencias
|
|
40
|
+
|
|
41
|
+
- **Más fácil:** un comando cierra el loop de "CLI atrasado" en vez de copiar un hint.
|
|
42
|
+
La separación de roles queda nítida: **`upgrade` = tu CLI** (self-update),
|
|
43
|
+
**`sync` = el repo** (mantenedor, commiteado).
|
|
44
|
+
- **Se acepta pagar:** depende del **registry** (es online; en redes cerradas —ADR-0006—
|
|
45
|
+
falla con mensaje claro y fallback al `npm i -g` manual). Asume **npm** como package
|
|
46
|
+
manager del global (no pnpm/yarn/volta/asdf); si el install falla, deriva al comando
|
|
47
|
+
manual. No auto-sincroniza el repo (a propósito).
|
|
48
|
+
|
|
49
|
+
## Alternativas consideradas
|
|
50
|
+
|
|
51
|
+
- **Auto-correr `dai sync` tras el upgrade** — descartado: un dev degradaría el repo con
|
|
52
|
+
un CLI viejo y sincronizar desde cada máquina genera conflictos. El `sync` es del
|
|
53
|
+
mantenedor.
|
|
54
|
+
- **Pinnear dai como devDependency por-repo** (sin global → sin drift, alineado en
|
|
55
|
+
`npm install`) — descartado como default: dai se diseñó **global y zero-dep para redes
|
|
56
|
+
cerradas** (ADR-0006), y no todos los repos son npm. Queda como opción del consumidor,
|
|
57
|
+
no del método.
|
|
58
|
+
- **No hacer nada** (dejar el hint manual) — descartado: fricción repetida sobre una
|
|
59
|
+
acción que el propio detector ya recomendaba.
|
|
@@ -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.
|
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
|
|
36
36
|
Se dejan pasar sin validar: `Merge …`, `Revert …`, `fixup!`/`squash!` y commits de bots (`🤖…`).
|
|
37
37
|
|
|
38
|
+
> Esto es solo la validación de **formato**. La **autoría** es otra regla: en el repo
|
|
39
|
+
> de dai no se aceptan commits autorados ni co-autorados por un agente
|
|
40
|
+
> (ver [`human-authorship.md`](human-authorship.md)).
|
|
41
|
+
|
|
38
42
|
## Relación con la trazabilidad
|
|
39
43
|
|
|
40
44
|
El link formal QUÉ↔CÓMO vive en el `implements.yaml` (no en el mensaje del commit). Pero si
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Autoría humana — quién firma el código de dai
|
|
2
|
+
|
|
3
|
+
> El código de `dai` lo **autora y revisa una persona**. Un agente puede ayudarte a
|
|
4
|
+
> escribirlo, pero el commit lo firmas tú: con tu identidad, y sin dejar al agente
|
|
5
|
+
> como co-autor. No es una postura sobre cómo trabajas — es una regla de contribución
|
|
6
|
+
> de **este repo**, y un check la blinda, sin depender de que nadie "se acuerde"
|
|
7
|
+
> (mismo espíritu que [`ci-rules.md`](ci-rules.md) y [`commit-convention.md`](commit-convention.md)).
|
|
8
|
+
|
|
9
|
+
## La regla
|
|
10
|
+
|
|
11
|
+
Ningún commit que entre a `dai` puede tener un **agente o bot** como:
|
|
12
|
+
|
|
13
|
+
- **author** — quien escribió el cambio,
|
|
14
|
+
- **committer** — quien lo registró, ni
|
|
15
|
+
- **`Co-authored-by:`** — un co-autor en el mensaje.
|
|
16
|
+
|
|
17
|
+
Concretamente se rechazan identidades de asistentes de código (Cursor, GitHub
|
|
18
|
+
Copilot, Claude, Devin, Codeium, Windsurf, … y cualquier `…[bot]`). La lista es
|
|
19
|
+
editable en [`scripts/no-agent-authors.sh`](../scripts/no-agent-authors.sh).
|
|
20
|
+
|
|
21
|
+
## Por qué
|
|
22
|
+
|
|
23
|
+
- **La persona firma ([Art. 5](../docs/MANIFIESTO.md#art-5)).** El código que entra a
|
|
24
|
+
`dai` es una decisión de una persona que lo entiende y se hace responsable. Un
|
|
25
|
+
commit firmado por un agente diluye ese "alguien responde por esto".
|
|
26
|
+
- **No vibe coding ([Art. 7](../docs/MANIFIESTO.md#art-7)).** El aporte no es "lo que
|
|
27
|
+
salió del chat": es un cambio que revisaste, entendiste y hiciste tuyo.
|
|
28
|
+
- **Higiene del historial.** El grafo de *Contributors* del repo se arma con
|
|
29
|
+
author/committer/co-author. Un trailer de agente mete al bot como contribuidor —
|
|
30
|
+
y sacarlo después obliga a reescribir historia.
|
|
31
|
+
|
|
32
|
+
## Distinción importante (no es scope de la metodología)
|
|
33
|
+
|
|
34
|
+
Esto **no** dice cómo debes usar agentes en *tus* proyectos. La metodología es
|
|
35
|
+
agnóstica de la herramienta de abajo ([Art. 2](../docs/MANIFIESTO.md#art-2)): usa los
|
|
36
|
+
agentes como quieras. Esta regla gobierna **la contribución al repo de dai**, igual
|
|
37
|
+
que el naming de ramas o la convención de commits. Por eso vive en `governance/` y
|
|
38
|
+
**no** se publica como parte de las plantillas de la metodología.
|
|
39
|
+
|
|
40
|
+
## Cómo cumplirla
|
|
41
|
+
|
|
42
|
+
Si un agente te ayudó con un cambio: **revísalo, apropiate de él y commitealo con tu
|
|
43
|
+
identidad**, sin el trailer `Co-authored-by`. Configura tu identidad una vez:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git config user.name "Tu Nombre"
|
|
47
|
+
git config user.email "tu@email"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Cómo se blinda
|
|
51
|
+
|
|
52
|
+
Dos capas, un solo script ([`scripts/no-agent-authors.sh`](../scripts/no-agent-authors.sh),
|
|
53
|
+
cero dependencias):
|
|
54
|
+
|
|
55
|
+
| Capa | Qué | Cuándo |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| **Hook local** | [`.githooks/commit-msg`](../.githooks/commit-msg) valida el commit en curso (`--pending`). | Al commitear, si activaste `git config core.hooksPath .githooks`. Es opt-in y salteable — feedback temprano, no barrera. |
|
|
58
|
+
| **CI (el gate)** | El job `authorship` de [`ci.yml`](../.github/workflows/ci.yml) valida todos los commits del PR (`--range`). | En cada PR. Como *required check* **bloquea el merge**. No es salteable. |
|
|
59
|
+
|
|
60
|
+
El hook avisa temprano; el CI es la barrera real. El commit siempre es de una persona
|
|
61
|
+
antes de que un mantenedor lo firme ([`CONTRIBUTING.md`](../CONTRIBUTING.md)).
|
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.
|