Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,18 @@ jobs:
--stack backend/nestjs:apps/api,frontend/nextjs:apps/web,mobile/flutter:apps/mobile >/dev/null )
rm -rf "$tmp"
echo "✅ instalador OK en monorepo"

pi:
# La capa de Pi dentro de Pi real, con su proveedor simulado: sin modelo ni API key.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22 # Pi 1.4.0 pide Node >= 22.19
- name: Instalar Pi 1.4.0 (versión fija, sin scripts)
run: npm install --prefix "$RUNNER_TEMP/pi" --no-save --save-exact --ignore-scripts --no-audit --no-fund @earendil-works/[email protected]
- name: Integración con Pi
env:
PI_BIN: ${{ runner.temp }}/pi/node_modules/.bin/pi
run: node --test test/pi.test.cjs
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ Formato basado en [Keep a Changelog](https://keepachangelog.com/) y [SemVer](htt

## [No publicado]

### Added
- **Base para Pi Agent (`--pi`)**: primera parte del soporte para [Pi Agent](https://github.com/earendil-works/pi) ≥ 1.4.0, el mismo enfoque que system-design-kit 1.2.0. Es una capa aditiva: sin `--pi` el instalador hace exactamente lo de antes. Con `--pi`:
- `.pi/settings.json` carga las skills desde `.claude/skills` y el comando `/status` desde `.claude/commands/kit/status.md`, sin copias. `/swarm` no se carga, porque depende de los subagentes de Claude Code. Si el archivo ya existe se fusiona, y si no es JSON válido no se toca.
- `.pi/extensions/dev-starter-kit.ts`:
- Expande los `@imports` de `CLAUDE.md` (`CLAUDE.project.md` y la baseline), que Pi lee pero no resuelve.
- Añade las reglas de `.claude/rules/` que Claude Code carga siempre, las que no tienen `paths:`: las del stack en un proyecto de un solo stack y las propias del equipo. En monorepo, las reglas por carpeta todavía no se cargan en Pi.
- Pone en el contexto los avisos del inicio de sesión.
- Muestra `🧰 dev-kit` en la barra de estado cuando la capa está activa.
- Helpers nuevos, zero-dep: `claude-imports.cjs`, el mismo de system-design-kit (reglas de Claude Code; nunca incluye `.env` reales ni `~/.ssh` o `~/.aws`), y `claude-rules.cjs`, que lee `.claude/rules/` como Claude Code (con o sin `paths:`).
- El manifiesto registra la capa, así que `--update` la recuerda. Al terminar, el instalador explica la confianza del proyecto, `PI_TELEMETRY=0`, por qué conviene instalar ripgrep y fd, y que en Pi no hay subagentes.
- CI: job que prueba la capa dentro de Pi 1.4.0 real con su proveedor simulado, sin modelo ni API key.

### Changed
- Dentro de Pi (`PI_CODING_AGENT=true`), el aviso de inicio de `hook-handler.cjs` sugiere `/skill:project-context`.
- Las 9 skills que nombran archivos del proyecto aclaran que sus rutas son relativas a la raíz del proyecto. Pi le dice al modelo que las referencias de una skill son relativas a la carpeta de la skill, y en las pruebas un modelo pequeño buscaba los archivos ahí.
- `swarm-orchestration` y la sección 3 de la baseline dicen que los subagentes son solo de Claude Code. `loop-task` avisa que en Pi todavía no tiene su puerta.

## [3.2.0] - 2026-10-27

### Added
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ Configuración `.claude` unificada para backend, frontend, mobile y blockchain.
│ ├── agents/ # 14 agentes (5 core + 9 especialistas) — ver "Agentes incluidos"
│ ├── skills/ # 19 skills (flujo + contexto + ingeniería + seguridad) — ver "Skills incluidas"
│ ├── commands/ # /swarm, /kit:status
│ ├── helpers/ # hook-handler, loop, statusline
│ ├── helpers/ # claude-imports, claude-rules, hook-handler, loop, statusline
│ └── templates/ # PROJECT.template.md (definición del proyecto)
├── shared/.pi/extensions/ # capa opcional para Pi Agent (--pi): imports de CLAUDE.md, reglas y avisos
├── stacks/ # OVERLAYS: 13 stacks seleccionables + un `_common` por categoría
│ ├── backend/{_common,nestjs,express,fastapi,django,php,spring,dotnet}
│ ├── frontend/{_common,angular,react,nextjs}
│ ├── mobile/{_common,react-native,flutter}
│ └── blockchain/solidity
├── scripts/ # validate.cjs (integridad del kit) · stacks.cjs (fuente única)
└── test/ # tests del hook-handler y de --update (node:test, zero-dep)
└── test/ # tests de helpers, instalador y capa de Pi (node:test, zero-dep)
```

## Uso
Expand Down
78 changes: 70 additions & 8 deletions install.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
* 6. (opcional) Instala agentes/skills externos curados (claude-code-templates) y deps npm por stack.
* 7. Indica los plugins de Claude Code a añadir (shared + por-stack).
* 8. Imprime un resumen del proyecto (stack, tests, calidad, entrega, git, entorno) y sus huecos.
* 9. Con --pi, añade la capa para Pi Agent (shared/.pi → .pi) y asegura que .pi/settings.json
* cargue las skills de .claude/ (sin copiarlas).
*
* Flags:
* --stack <cat/stack>[:dir][,…] fuerza el stack (si no, autodetecta). En monorepo, uno por carpeta:
* --stack backend/nestjs:apps/api,frontend/nextjs:apps/web
* --yes, -y no interactivo: usa el default de cada confirmación
* --all acepta TODO (externos + deps). Combina con --yes para desatendido total
* --no-external no instala componentes externos (claude-code-templates / npm)
* --pi añade la capa para Pi Agent; --update la recuerda (manifiesto)
* --update, --force sobrescribe la capa base con la última versión (conserva memoria y CLAUDE.project.md)
* --dry-run simula: imprime qué haría, no escribe ni ejecuta
* --help, -h muestra esta ayuda
Expand All @@ -38,6 +41,7 @@ const FLAGS = {
// Retirado junto con Ruflo (claude-flow). Se acepta para no romper scripts, pero no hace nada.
noFlowLegacy: argv.includes("--no-flow"),
noExternal: argv.includes("--no-external"),
pi: argv.includes("--pi"),
update: argv.includes("--update") || argv.includes("--force"),
dryRun: argv.includes("--dry-run"),
help: argv.includes("--help") || argv.includes("-h"),
Expand All @@ -60,6 +64,8 @@ Uso: node install.js [opciones]
--yes, -y no interactivo: usa el default de cada confirmación (conservador)
--all acepta TODO (externos + deps). Usa "--yes --all" para desatendido total
--no-external no instala componentes externos
--pi añade la capa para Pi Agent (.pi/): usa las mismas skills y expande los
imports de CLAUDE.md. --update la recuerda.
--update, --force actualiza la capa base sin perder nada tuyo: solo reescribe los archivos
que siguen igual que como los dejó el kit (según .claude/.kit-manifest.json).
Lo que hayas editado se conserva y la versión nueva queda como *.kit-new.
Expand Down Expand Up @@ -654,15 +660,40 @@ const GITIGNORE = [".claude/memory/", ".claude/agent-memory-local/", ".claude/lo
// captura. Git aplica la última regla que casa, así que las negaciones van SIEMPRE después.
const GITIGNORE_KEEP = ["!.env.example", "!.env.sample", "!.env.template"];

// ---------- .pi/settings.json (con --pi) ----------
// Es de la persona (tema, modelo, paquetes…): el kit solo se asegura de que Pi cargue las skills
// de .claude/ y el comando /status (de .claude/commands/kit/status.md), sin copiarlos. /swarm no
// se carga: depende de los subagentes de Claude Code. Las rutas de ese archivo son relativas a .pi/.
const PI_RESOURCES = { skills: ["../.claude/skills"], prompts: ["../.claude/commands/kit/status.md"] };
function mergePiSettings() {
const p = path.join(CWD, ".pi", "settings.json");
const exists = fs.existsSync(p);
const cur = exists ? readJSON(p) : {};
const keys = Object.keys(PI_RESOURCES);
if (!cur || typeof cur !== "object" || Array.isArray(cur) || keys.some((k) => cur[k] !== undefined && !Array.isArray(cur[k]))) {
console.log(` ⚠️ .pi/settings.json no tiene el formato esperado: no se toca. Agrega a mano ${keys.map((k) => `"${k}": ${JSON.stringify(PI_RESOURCES[k])}`).join(" y ")}.`);
return;
}
const next = { ...cur };
for (const k of keys) next[k] = [...(cur[k] || []), ...PI_RESOURCES[k].filter((r) => !(cur[k] || []).includes(r))];
if (JSON.stringify(next) === JSON.stringify(cur)) return;
const dry = FLAGS.dryRun ? "(dry-run) " : "";
if (!FLAGS.dryRun) {
fs.mkdirSync(path.dirname(p), { recursive: true });
fs.writeFileSync(p, JSON.stringify(next, null, 2) + "\n");
}
console.log(` ${dry}${exists ? "🔀" : "➕"} .pi/settings.json (skills y /status de .claude/)`);
}

// Asegura que .gitignore cubra memoria/runtime/backups/secretos (idempotente).
function ensureGitignore() {
function ensureGitignore(withPi = false) {
const gi = path.join(CWD, ".gitignore");
const cur = fs.existsSync(gi) ? fs.readFileSync(gi, "utf8") : "";
const have = new Set(cur.split(/\r?\n/).map((l) => l.trim()));
// `npm init` no crea .gitignore y el propio kit instala devDeps: sin esto, el primer
// `git add .` mete node_modules/ al repo.
const node = fs.existsSync(path.join(CWD, "package.json")) && !have.has("node_modules") && !have.has("/node_modules") ? ["node_modules/"] : [];
const add = [...node, ...GITIGNORE].filter((n) => !have.has(n));
const add = [...node, ...GITIGNORE, ...(withPi ? [".pi.backup.*"] : [])].filter((n) => !have.has(n));
// Si acabamos de añadir ".env.*", reemitimos las negaciones aunque ya existan más arriba:
// colocadas antes del patrón no harían nada, y la línea duplicada es inocua.
const keep = GITIGNORE_KEEP.filter((n) => !have.has(n) || add.includes(".env.*"));
Expand Down Expand Up @@ -1163,11 +1194,18 @@ async function main() {
}
stacks = r.stacks;
}
// --pi se recuerda en el manifiesto: un --update posterior no la pierde.
const withPi = FLAGS.pi || Boolean(prevManifest && prevManifest.pi);
const piDir = path.join(CWD, ".pi");

// backup
if (fs.existsSync(claudeDir) && !FLAGS.dryRun) {
const bk = `.claude.backup.${Date.now()}`;
fs.cpSync(claudeDir, path.join(CWD, bk), { recursive: true });
console.log(`\n📦 Backup: ${bk}`);
const stamp = Date.now();
for (const dir of withPi ? [claudeDir, piDir] : [claudeDir]) {
if (fs.existsSync(dir) && !FLAGS.dryRun) {
const bk = `${path.basename(dir)}.backup.${stamp}`;
fs.cpSync(dir, path.join(CWD, bk), { recursive: true });
console.log(`\n📦 Backup: ${bk}`);
}
}

if (FLAGS.noFlowLegacy) {
Expand Down Expand Up @@ -1196,15 +1234,30 @@ async function main() {
for (const r of stackRules(stacks)) applyFile(r.rel, r.buf, path.join(claudeDir, r.rel), layer);
const remembered = retireFiles(layer, claudeDir);
reportLayer(layer);

// 1b) capa de Pi Agent: mismas reglas de manifiesto, en su propia sección.
const piLayer = {
prev: (prevManifest && prevManifest.pi && prevManifest.pi.files) || {},
next: {},
added: 0, updated: 0, same: 0, kept: 0, userOwned: 0, merged: false, conflicts: [], retiredSettings: [],
retired: 0, retiredKept: 0,
};
if (withPi) {
console.log("\n🧩 Capa de Pi Agent (shared/.pi)...");
applyLayer(path.join(KIT, "shared", ".pi"), piDir, piLayer);
mergePiSettings();
reportLayer(piLayer);
}
if (!FLAGS.dryRun) {
fs.writeFileSync(manifestPath, JSON.stringify({ version: VERSION, stacks, files: { ...remembered, ...layer.next } }, null, 2) + "\n");
const m = { version: VERSION, stacks, files: { ...remembered, ...layer.next }, ...(withPi ? { pi: { files: piLayer.next } } : {}) };
fs.writeFileSync(manifestPath, JSON.stringify(m, null, 2) + "\n");
}

// 2) Definición del proyecto + CLAUDE.md raíz (añade/actualiza nuestro bloque; respeta lo demás)
console.log("\n📝 Definición del proyecto y CLAUDE.md...");
const hasProject = await scaffoldProject();
composeClaudeMd(hasProject);
ensureGitignore();
ensureGitignore(withPi);
await scaffoldEnvExample();

// 3) restos de Ruflo (solo lectura)
Expand Down Expand Up @@ -1285,6 +1338,15 @@ async function main() {
console.log(` • CLAUDE.md compuesto (proyecto + baseline) y reglas del stack en .claude/rules/kit/: ${describeStacks(stacks)}`);
console.log(" • Memoria: la nativa de Claude Code + la de agente en .claude/agent-memory/ (versionada); .gitignore actualizado");
console.log(" • Siguiente paso: abre Claude Code y corre /project-context para rellenar CLAUDE.project.md");
if (withPi) {
console.log("\n🧩 Pi Agent (capa .pi/):");
console.log(" • Abre pi en el proyecto y acepta su confianza: sin ella Pi no carga .pi/ (ni las skills, ni los");
console.log(' imports de CLAUDE.md, ni las reglas). Con la capa activa verás "🧰 dev-kit" abajo.');
console.log(" • Las skills se invocan como /skill:<nombre> (/skill:project-context, /skill:tdd…) y el estado del kit con /status.");
console.log(" • En Pi no hay subagentes: /swarm y los agentes de .claude/agents/ son solo de Claude Code.");
console.log(" • Pi envía telemetría anónima de instalación por defecto: PI_TELEMETRY=0 la apaga.");
console.log(" • Instala ripgrep y fd con el gestor de paquetes de tu sistema: si faltan, Pi descarga la última versión sin fijarla.");
}

try {
summarizeProject(stacks);
Expand Down
14 changes: 14 additions & 0 deletions scripts/validate.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* - Sin entidades HTML sin escapar (&lt; / &gt;) en agentes/skills
* - JSON válido: components.json, shared/.claude/settings.json
* - Sintaxis JS: install.js, helpers, este script
* - Capa de Pi: la extensión carga helpers de .claude/ que existen
* - Overlays de stack presentes y no vacíos; plantilla de proyecto presente
*/
const fs = require("fs");
Expand Down Expand Up @@ -128,6 +129,18 @@ if (declIds.size === 0) errors.push("install.js: no se pudo extraer el objeto ST
for (const id of declIds) if (!fsIds.has(id)) errors.push(`STACKS["${id}"] (install.js) sin overlay stacks/${id}/.claude/CLAUDE.md`);
for (const id of fsIds) if (!declIds.has(id)) errors.push(`overlay stacks/${id} sin entrada en STACKS de install.js (no se detectaría)`);

// --- capa de Pi Agent ---
// La extensión carga helpers de .claude/ por ruta relativa: si uno se renombra o se borra, Pi
// fallaría al cargarla en cada proyecto con --pi.
for (const ext of walk(path.join(ROOT, "shared/.pi/extensions"), (p) => p.endsWith(".ts"))) {
const refs = [...fs.readFileSync(ext, "utf8").matchAll(/\.\.\/\.\.\/\.claude\/helpers\/([\w.-]+)/g)].map((m) => m[1]);
if (!refs.length) errors.push(`${rel(ext)}: no carga ningún helper de .claude/`);
for (const h of refs) {
if (!fs.existsSync(path.join(ROOT, "shared/.claude/helpers", h))) errors.push(`${rel(ext)}: carga .claude/helpers/${h}, que no existe`);
else ok.push(`Pi: ${path.basename(ext)} → ${h}`);
}
}

// --- es-MX: peninsularismos prohibidos (agentes, skills, overlays, comandos, plantillas, helpers; ver CONTRIBUTING.md) ---
// Excluye CLAUDE.base.md y CONTRIBUTING.md a propósito: ahí vive el glosario que cita estos términos.
const ESMX = /\b(coste|costes|montar|montamos|montas|ordenador|ordenadores|fichero|ficheros|vale|pillas|pillamos|pill[eé])\b/i;
Expand All @@ -138,6 +151,7 @@ const esmxFiles = [
...walk(path.join(ROOT, "shared/.claude/commands"), (p) => p.endsWith(".md")),
...walk(path.join(ROOT, "shared/templates"), (p) => p.endsWith(".md")),
...walk(path.join(ROOT, "shared/.claude/helpers"), (p) => /\.(cjs|mjs|js)$/.test(p)),
...walk(path.join(ROOT, "shared/.pi"), (p) => p.endsWith(".ts")),
path.join(ROOT, "install.js"), // es el texto que más lee el usuario: sale por consola en cada instalación
];
for (const f of esmxFiles) {
Expand Down
1 change: 1 addition & 0 deletions shared/.claude/CLAUDE.base.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Nunca en la raíz. En `.gitignore`: `/docs/*` con `!/docs/adr/` después (no `/d
## 3. Coordinación de agentes (nativa de Claude Code)

La orquestación multiagente usa solo herramientas nativas (tool `Agent`, agentes de `.claude/agents/`, worktrees). Sin runtimes de orquestación de terceros.
En Pi Agent esta sección no aplica: Pi no tiene subagentes nativos, así que ahí el trabajo va en secuencia en la misma sesión.

- **Agentes core** (ver `.claude/agents/`):
- `planner` → antes de toda feature grande: descompone, identifica riesgos, secuencia.
Expand Down
Loading
Loading