From 2df23d5b33f462b9fee5df76f646de4bf0da475c Mon Sep 17 00:00:00 2001 From: Toni Ramiro Date: Sun, 18 Oct 2026 18:58:51 +0200 Subject: [PATCH] release v0.3.0 (origen c00f020) --- LICENSE | 21 ++ README.md | 17 ++ extensions/modelo-proyecto.ts | 244 ++++++++++++++++++ package.json | 23 ++ skills/new-project/SKILL.md | 74 ++++++ skills/new-project/references/plantillas.md | 43 +++ skills/new-project/scripts/new-project.sh | 154 +++++++++++ skills/new-project/templates/.gitignore.tmpl | 27 ++ skills/new-project/templates/AGENTS.md.tmpl | 88 +++++++ .../new-project/templates/CHANGELOG.md.tmpl | 24 ++ skills/new-project/templates/INSTALL.md.tmpl | 79 ++++++ skills/new-project/templates/README.md.tmpl | 58 +++++ skills/new-project/templates/TODO.md.tmpl | 22 ++ 13 files changed, 874 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 extensions/modelo-proyecto.ts create mode 100644 package.json create mode 100644 skills/new-project/SKILL.md create mode 100644 skills/new-project/references/plantillas.md create mode 100755 skills/new-project/scripts/new-project.sh create mode 100644 skills/new-project/templates/.gitignore.tmpl create mode 100644 skills/new-project/templates/AGENTS.md.tmpl create mode 100644 skills/new-project/templates/CHANGELOG.md.tmpl create mode 100644 skills/new-project/templates/INSTALL.md.tmpl create mode 100644 skills/new-project/templates/README.md.tmpl create mode 100644 skills/new-project/templates/TODO.md.tmpl diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..05739c0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Toni Ramiro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..63b1c72 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# pi-new-project (distribución) + +Versión instalable de **pi-new-project** v0.3.0: solo los ficheros que pi +necesita para usar el paquete (skill `new-project` y orden `/modelo-proyecto`). + +```bash +pi install git:git.sargatxet.cloud/sargatxet/pi-new-project-dist +# versión fija: +pi install git:git.sargatxet.cloud/sargatxet/pi-new-project-dist@v0.3.0 +``` + +Documentación, cambios e incidencias: + +> Repositorio generado automáticamente desde https://git.sargatxet.cloud/sargatxet/pi-new-project +> (commit `c00f020`). No hagas cambios aquí: se perderán en la próxima publicación. + +Licencia MIT, ver [`LICENSE`](LICENSE). diff --git a/extensions/modelo-proyecto.ts b/extensions/modelo-proyecto.ts new file mode 100644 index 0000000..fb45fc8 --- /dev/null +++ b/extensions/modelo-proyecto.ts @@ -0,0 +1,244 @@ +/** + * modelo-proyecto — modelo por defecto de un proyecto. + * + * Guarda el modelo elegido en `/.pi/settings.json` (`defaultProvider` + * y `defaultModel`), que pi aplica al iniciar cualquier sesión nueva en ese + * proyecto (una vez concedida la confianza del proyecto). + * + * - Orden `/modelo-proyecto [proveedor/modelo]`: muestra los modelos + * disponibles para elegir (o usa el indicado), lo guarda en el proyecto del + * directorio actual y lo activa en la sesión en curso. + * - Herramienta `elegir_modelo_proyecto`: la usa la skill `new-project` para + * que el usuario elija el modelo al crear un proyecto en otra ruta. + */ + +import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs"; +import { join, resolve } from "node:path"; +import type { Api, Model } from "@earendil-works/pi-ai"; +import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; +import { DynamicBorder } from "@earendil-works/pi-coding-agent"; +import { Container, type SelectItem, SelectList, Text } from "@earendil-works/pi-tui"; +import { Type } from "typebox"; + +const SETTINGS_REL = join(".pi", "settings.json"); +/** Máximo de modelos visibles a la vez en el selector (la lista se desplaza). */ +const MAX_VISIBLES = 15; + +function clave(m: Model): string { + return `${m.provider}/${m.id}`; +} + +function kilos(n: number | undefined): string { + if (!n) return "?"; + return n >= 1_000_000 ? `${(n / 1_000_000).toFixed(1)}M` : `${Math.round(n / 1000)}K`; +} + +function etiqueta(m: Model, actual?: string): string { + const marca = actual === clave(m) ? " ← actual del proyecto" : ""; + return `${clave(m)} (${m.name ?? m.id}, ctx ${kilos(m.contextWindow)})${marca}`; +} + +function disponibles(ctx: ExtensionContext): Model[] { + return [...ctx.modelRegistry.getAvailable()].sort((a, b) => clave(a).localeCompare(clave(b))); +} + +function leerSettings(proyecto: string): Record { + const ruta = join(proyecto, SETTINGS_REL); + if (!existsSync(ruta)) return {}; + const texto = readFileSync(ruta, "utf-8").trim(); + if (!texto) return {}; + const datos = JSON.parse(texto); + if (typeof datos !== "object" || datos === null || Array.isArray(datos)) { + throw new Error(`${ruta} no contiene un objeto JSON`); + } + return datos as Record; +} + +export function modeloDelProyecto(proyecto: string): string | undefined { + try { + const s = leerSettings(proyecto); + if (typeof s.defaultProvider === "string" && typeof s.defaultModel === "string") { + return `${s.defaultProvider}/${s.defaultModel}`; + } + } catch { + // settings ilegible: se trata como sin modelo + } + return undefined; +} + +/** Fusiona defaultProvider/defaultModel en .pi/settings.json sin tocar el resto. */ +function guardarModelo(proyecto: string, m: Model): string { + const ruta = join(proyecto, SETTINGS_REL); + const datos = leerSettings(proyecto); + datos.defaultProvider = m.provider; + datos.defaultModel = m.id; + mkdirSync(join(proyecto, ".pi"), { recursive: true }); + const tmp = `${ruta}.tmp`; + writeFileSync(tmp, `${JSON.stringify(datos, null, 2)}\n`, "utf-8"); + renameSync(tmp, ruta); + return ruta; +} + +function buscar(ctx: ExtensionContext, texto: string): Model | undefined { + const t = texto.trim(); + const lista = disponibles(ctx); + return ( + lista.find((m) => clave(m) === t) ?? + (t.includes("/") ? undefined : lista.filter((m) => m.id === t).at(0)) + ); +} + +async function elegir(ctx: ExtensionContext, proyecto: string): Promise | undefined> { + const lista = disponibles(ctx); + if (lista.length === 0) throw new Error("no hay modelos disponibles (¿falta /login o credenciales?)"); + const actual = modeloDelProyecto(proyecto); + const titulo = `Modelo por defecto del proyecto (${proyecto})`; + + // Fuera de la TUI (p. ej. RPC) no hay componentes propios: diálogo estándar. + if (ctx.mode !== "tui") { + const opciones = lista.map((m) => etiqueta(m, actual)); + const elegido = await ctx.ui.select(titulo, opciones); + if (!elegido) return undefined; + return lista[opciones.indexOf(elegido)]; + } + + const items: SelectItem[] = lista.map((m) => ({ value: clave(m), label: etiqueta(m, actual) })); + const valor = await ctx.ui.custom((tui, theme, _kb, done) => { + const contenedor = new Container(); + contenedor.addChild(new DynamicBorder((s) => theme.fg("accent", s))); + contenedor.addChild(new Text(theme.fg("accent", theme.bold(titulo)))); + + // Ventana de MAX_VISIBLES elementos que se desplaza con el cursor; + // ↑ en el primero salta al último y ↓ en el último vuelve al primero. + const selector = new SelectList(items, Math.min(items.length, MAX_VISIBLES), { + selectedPrefix: (t) => theme.fg("accent", t), + selectedText: (t) => theme.fg("accent", t), + description: (t) => theme.fg("muted", t), + scrollInfo: (t) => theme.fg("dim", t), + noMatch: (t) => theme.fg("warning", t), + }); + const inicial = actual ? items.findIndex((i) => i.value === actual) : -1; + if (inicial >= 0) selector.setSelectedIndex(inicial); + selector.onSelect = (item) => done(item.value); + selector.onCancel = () => done(null); + contenedor.addChild(selector); + + contenedor.addChild(new Text(theme.fg("dim", "↑↓ moverse • enter elegir • esc cancelar"))); + contenedor.addChild(new DynamicBorder((s) => theme.fg("accent", s))); + + return { + render: (width: number) => contenedor.render(width), + invalidate: () => contenedor.invalidate(), + handleInput: (data: string) => { + selector.handleInput(data); + tui.requestRender(); + }, + }; + }); + if (!valor) return undefined; + return lista.find((m) => clave(m) === valor); +} + +export default function (pi: ExtensionAPI) { + // Lista para autocompletar (getArgumentCompletions no recibe ctx). + let cache: string[] = []; + pi.on("session_start", (_event, ctx) => { + try { + cache = disponibles(ctx).map(clave); + } catch { + cache = []; + } + }); + + pi.registerCommand("modelo-proyecto", { + description: "Elige el modelo por defecto de este proyecto (se guarda en .pi/settings.json)", + getArgumentCompletions: (prefijo) => { + const p = prefijo.trim().toLowerCase(); + const items = cache.filter((k) => k.toLowerCase().includes(p)).map((k) => ({ value: k, label: k })); + return items.length ? items : null; + }, + handler: async (args, ctx) => { + const proyecto = ctx.cwd; + let modelo: Model | undefined; + try { + if (args.trim()) { + modelo = buscar(ctx, args); + if (!modelo) { + ctx.ui.notify(`Modelo "${args.trim()}" no disponible. Usa /modelo-proyecto sin argumentos para elegir.`, "error"); + return; + } + } else { + if (!ctx.hasUI) { + ctx.ui.notify("Sin interfaz: indica el modelo, /modelo-proyecto proveedor/modelo", "error"); + return; + } + modelo = await elegir(ctx, proyecto); + if (!modelo) { + ctx.ui.notify("Sin cambios: no se eligió modelo.", "info"); + return; + } + } + const ruta = guardarModelo(proyecto, modelo); + const activo = await pi.setModel(modelo); + ctx.ui.notify( + `Modelo del proyecto: ${clave(modelo)} (guardado en ${ruta})` + + (activo ? "; activado en esta sesión." : "; no se pudo activar ahora (¿credenciales?)."), + activo ? "info" : "warning", + ); + } catch (err) { + ctx.ui.notify(`modelo-proyecto: ${(err as Error).message}`, "error"); + } + }, + }); + + pi.registerTool({ + name: "elegir_modelo_proyecto", + label: "Elegir modelo del proyecto", + description: + "Muestra al usuario la lista de modelos disponibles para que elija el modelo por defecto de un proyecto " + + "y lo guarda en /.pi/settings.json (defaultProvider/defaultModel), que pi usará en las nuevas sesiones " + + "de ese proyecto. Úsala al crear un proyecto. No elijas tú el modelo: deja que el usuario lo seleccione. " + + "Pasa 'modelo' (proveedor/id) solo si el usuario ya lo ha dicho explícitamente.", + parameters: Type.Object({ + ruta: Type.String({ description: "Directorio raíz del proyecto" }), + modelo: Type.Optional( + Type.String({ description: "proveedor/id ya indicado por el usuario; si falta se muestra el selector" }), + ), + }), + async execute(_id, params, _signal, _onUpdate, ctx) { + const proyecto = resolve(ctx.cwd, params.ruta.replace(/^~(?=$|\/)/, process.env.HOME ?? "~")); + let modelo: Model | undefined; + if (params.modelo) { + modelo = buscar(ctx, params.modelo); + if (!modelo) { + const lista = disponibles(ctx).map(clave).join(", "); + throw new Error(`modelo "${params.modelo}" no disponible. Disponibles: ${lista}`); + } + } else { + if (!ctx.hasUI) { + const lista = disponibles(ctx).map(clave).join("\n"); + throw new Error( + `sin interfaz para mostrar el selector. Pregunta al usuario y vuelve a llamar con 'modelo'. Disponibles:\n${lista}`, + ); + } + modelo = await elegir(ctx, proyecto); + if (!modelo) { + return { + content: [{ type: "text", text: "El usuario canceló la elección; no se guardó modelo. Pregúntale si quiere elegir uno." }], + details: { proyecto, modelo: null }, + }; + } + } + const ruta = guardarModelo(proyecto, modelo); + return { + content: [ + { + type: "text", + text: `Modelo por defecto del proyecto: ${clave(modelo)}. Guardado en ${ruta}. Se cambia con /modelo-proyecto.`, + }, + ], + details: { proyecto, modelo: clave(modelo), ruta }, + }; + }, + }); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..7f47f7a --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "pi-new-project", + "version": "0.3.0", + "private": true, + "license": "MIT", + "description": "Paquete de pi con la skill new-project (base documental de un proyecto) y la orden /modelo-proyecto (modelo por defecto del proyecto).", + "homepage": "https://git.sargatxet.cloud/sargatxet/pi-new-project", + "repository": { + "type": "git", + "url": "git+https://git.sargatxet.cloud/sargatxet/pi-new-project.git" + }, + "keywords": [ + "pi-package" + ], + "pi": { + "extensions": [ + "./extensions" + ], + "skills": [ + "./skills" + ] + } +} diff --git a/skills/new-project/SKILL.md b/skills/new-project/SKILL.md new file mode 100644 index 0000000..a2e4dc3 --- /dev/null +++ b/skills/new-project/SKILL.md @@ -0,0 +1,74 @@ +--- +name: new-project +description: Genera la base documental de un proyecto nuevo (AGENTS.md, README.md, CHANGELOG.md, TODO.md, INSTALL.md y .gitignore) a partir de plantillas, y deja que el usuario elija el modelo por defecto del proyecto. Úsala cuando el usuario pida crear un proyecto, repositorio o directorio nuevo, inicializar su documentación o "montar el esqueleto" de un repo. +license: MIT +--- + +# new-project + +Genera el esqueleto documental de un proyecto. Todas las plantillas están en +`templates/` y el andamiaje lo hace `scripts/new-project.sh`. + +## Procedimiento + +1. **Recoge los datos** que falten antes de ejecutar nada: + - ruta de destino (obligatoria); + - nombre del proyecto (por defecto, el nombre del directorio destino); + - descripción en una frase (obligatoria: se usa en README y AGENTS); + - autor (opcional; por defecto `git config user.name`); + - si hay que inicializar un repositorio git (`--git`). + No inventes la descripción: si el usuario no la da, pregúntala o propón una + y confírmala. +2. **Ejecuta el script** desde este directorio de skill: + + ```bash + scripts/new-project.sh --nombre "Mi Proyecto" --descripcion "Qué hace" \ + --git /ruta/al/proyecto + ``` + + El script no sobreescribe ficheros existentes salvo con `--force` y muestra + un resumen de lo creado. Usa `--dry-run` para revisar antes. +3. **Modelo por defecto del proyecto** (obligatorio): el usuario elige el modelo + de entre los existentes; tú no lo eliges. + - Llama a la herramienta `elegir_modelo_proyecto` con `ruta` = destino del + proyecto. Muestra al usuario el selector con los modelos disponibles y + guarda su elección en `/.pi/settings.json` (`defaultProvider` y + `defaultModel`). pi lo usará en todas las sesiones nuevas del proyecto. + - Si la herramienta no existe o no hay interfaz: ejecuta `pi --list-models`, + enséñale la lista al usuario, pregúntale cuál quiere y guárdalo con + `scripts/new-project.sh --modelo proveedor/id ` (solo crea + `.pi/settings.json` si no existe). + - Si el usuario cancela, déjalo sin fijar e indícale que puede hacerlo más + tarde con `/modelo-proyecto`. +4. **Revisa el resultado**: abre `README.md` y `AGENTS.md` del proyecto generado + y completa lo que quede como `PENDIENTE` (objetivos, stack, comandos de + verificación reales). No dejes marcadores de plantilla ni secciones vacías. +5. **Ajusta** `INSTALL.md` con los requisitos y pasos reales del stack elegido. +6. **Commit inicial** si se creó repo git (incluye `.pi/settings.json`): + + ```bash + git add -A && git commit -m "docs: base documental inicial" + ``` +7. Informa al usuario de la ruta creada, los ficheros, el modelo elegido, el + commit y de que puede cambiar el modelo en cualquier momento con + `/modelo-proyecto` dentro del proyecto. + +## Reglas + +- No ejecutes el script sobre un directorio que ya tiene `AGENTS.md` o `README.md` + sin avisar y confirmar (usaría `--force`). +- Las secciones `## Reglas de modelo` (salvo el modelo por defecto, que gestiona + `/modelo-proyecto`) y `## Reglas de funcionamiento` de + `AGENTS.md` se dejan como `PENDIENTE` a propósito: se acuerdan después, por + proyecto. No las rellenes con suposiciones. +- Idioma de los documentos generados: español. +- Rutas relativas siempre desde este directorio de skill. + +## Referencia + +- `scripts/new-project.sh --help` — todas las opciones. +- `/modelo-proyecto [proveedor/id]` — cambia el modelo por defecto del proyecto + del directorio actual (sin argumento, muestra el selector). +- `templates/` — plantillas con marcadores `{{PROYECTO}}`, `{{DESCRIPCION}}`, + `{{AUTOR}}`, `{{FECHA}}`, `{{FECHA_ISO}}`. +- `references/plantillas.md` — qué contiene cada documento y cuándo tocarlo. diff --git a/skills/new-project/references/plantillas.md b/skills/new-project/references/plantillas.md new file mode 100644 index 0000000..3126018 --- /dev/null +++ b/skills/new-project/references/plantillas.md @@ -0,0 +1,43 @@ +# Plantillas de la base documental + +Qué genera `new-project.sh` y cuándo hay que tocar cada documento. + +| Documento | Para qué sirve | Cuándo actualizarlo | +| --- | --- | --- | +| `AGENTS.md` | Reglas de trabajo para agentes y colaboradores: flujo, commits, verificación y las futuras reglas de modelo y de funcionamiento. | Al cambiar el flujo, las reglas, los comandos de verificación o al fijar las reglas de modelo. | +| `README.md` | Puerta de entrada: qué es el proyecto, estado, instalación rápida, uso y estructura. | Al cambiar el propósito, el uso o la estructura. | +| `INSTALL.md` | Instalación reproducible: requisitos, dependencias, configuración, verificación, arranque, actualización y desinstalación. | Al añadir dependencias, variables de entorno o cambiar pasos de instalación. | +| `CHANGELOG.md` | Historial *Keep a Changelog*: `Añadido` / `Cambiado` / `Corregido` / `Eliminado`. | En cada cambio, sin excepción. | +| `TODO.md` | Tareas en curso, pendientes y hechas, incluida la deuda técnica. | Al abrir o cerrar tareas. | +| `.gitignore` | Exclusiones básicas: secretos, dependencias descargadas, artefactos de build, logs. | Al detectar ficheros que no deben versionarse. | +| `.pi/settings.json` | Modelo por defecto de pi para el proyecto (`defaultProvider`, `defaultModel`). Lo crea `elegir_modelo_proyecto` o `--modelo`. | Solo con `/modelo-proyecto`, a petición del usuario. | + +## Marcadores + +El script sustituye estos marcadores en todas las plantillas: + +| Marcador | Valor | +| --- | --- | +| `{{PROYECTO}}` | `--nombre` o, si falta, el nombre del directorio destino. | +| `{{DESCRIPCION}}` | `--descripcion` o un `PENDIENTE` explícito. | +| `{{AUTOR}}` | `--autor` o `git config user.name`. | +| `{{FECHA}}` y `{{FECHA_ISO}}` | Fecha de ejecución en formato `AAAA-MM-DD`. | + +## Secciones reservadas + +`AGENTS.md` incluye a propósito dos secciones con `PENDIENTE`: + +- `## Reglas de modelo` — explica dónde está el modelo por defecto y cómo + cambiarlo; el criterio de selección por tarea queda `PENDIENTE`. +- `## Reglas de funcionamiento` — estándares, ramas, revisión y automatización. + +No se rellenan al generar el proyecto: se acuerdan después, proyecto a proyecto. + +## Modelo por defecto + +- Al crear el proyecto, el usuario elige el modelo en el selector de la + herramienta `elegir_modelo_proyecto` (alternativa: `--modelo proveedor/id`). +- Se guarda en `/.pi/settings.json`; pi lo aplica en cada sesión nueva + del proyecto si éste es de confianza. +- Se cambia en cualquier momento con `/modelo-proyecto`, que fusiona el valor con + el resto de ajustes del fichero y activa el modelo en la sesión actual. diff --git a/skills/new-project/scripts/new-project.sh b/skills/new-project/scripts/new-project.sh new file mode 100755 index 0000000..658b07f --- /dev/null +++ b/skills/new-project/scripts/new-project.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +# Genera la base documental de un proyecto a partir de las plantillas de la skill. +set -euo pipefail + +# bash >= 5.2 sustituye '&' del reemplazo por el patrón encontrado; lo desactivamos +# para que los valores con '&' o '\' se inserten literales. +shopt -u patsub_replacement 2>/dev/null || true + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +TEMPLATES_DIR="$SCRIPT_DIR/../templates" + +PROYECTO="" +DESCRIPCION="" +AUTOR="" +DESTINO="" +MODELO="" +INIT_GIT=0 +FORCE=0 +DRY_RUN=0 +FECHA_ISO="$(date +%Y-%m-%d)" + +uso() { + cat <<'EOF' +Uso: new-project.sh [opciones] + +Genera AGENTS.md, README.md, CHANGELOG.md, TODO.md, INSTALL.md y .gitignore. + +Opciones: + -n, --nombre NOMBRE Nombre del proyecto (por defecto: nombre del directorio). + -d, --descripcion TEXTO Descripción en una frase (por defecto: PENDIENTE). + -a, --autor NOMBRE Autor (por defecto: git config user.name). + -m, --modelo PROV/ID Modelo por defecto del proyecto; se guarda en + .pi/settings.json (lista: pi --list-models). + En pi es preferible la herramienta + elegir_modelo_proyecto o la orden /modelo-proyecto. + -g, --git Inicializa un repositorio git en el destino. + -f, --force Sobreescribe ficheros que ya existan. + --dry-run Muestra lo que haría sin escribir nada. + -h, --help Muestra esta ayuda. +EOF +} + +die() { printf 'error: %s\n' "$*" >&2; exit 1; } +aviso() { printf 'aviso: %s\n' "$*" >&2; } +requiere() { [[ $# -ge 2 && -n "$2" && "$2" != -* ]] || die "$1 necesita un valor"; } + +while [[ $# -gt 0 ]]; do + case "$1" in + -n | --nombre) requiere "$@"; PROYECTO="$2"; shift 2 ;; + -d | --descripcion) requiere "$@"; DESCRIPCION="$2"; shift 2 ;; + -a | --autor) requiere "$@"; AUTOR="$2"; shift 2 ;; + -m | --modelo) requiere "$@"; MODELO="$2"; shift 2 ;; + -g | --git) INIT_GIT=1; shift ;; + -f | --force) FORCE=1; shift ;; + --dry-run) DRY_RUN=1; shift ;; + -h | --help) uso; exit 0 ;; + --) shift; break ;; + -*) die "opción desconocida: $1 (usa --help)" ;; + *) break ;; + esac +done + +[[ $# -ge 1 ]] || { uso >&2; exit 2; } +DESTINO="$1" +[[ $# -eq 1 ]] || die "solo se admite un directorio destino" + +[[ -d "$TEMPLATES_DIR" ]] || die "no encuentro las plantillas en $TEMPLATES_DIR" + +if [[ -z "$PROYECTO" ]]; then + PROYECTO="$(basename -- "${DESTINO%/}")" +fi +[[ -n "$PROYECTO" ]] || die "no puedo deducir el nombre del proyecto; usa --nombre" + +if [[ -z "$DESCRIPCION" ]]; then + DESCRIPCION="PENDIENTE: describir $PROYECTO en una frase." + aviso "sin --descripcion; se deja un PENDIENTE en README.md y AGENTS.md" +fi + +if [[ -z "$AUTOR" ]]; then + AUTOR="$(git config user.name 2>/dev/null || true)" + [[ -n "$AUTOR" ]] || AUTOR="PENDIENTE" +fi + +if [[ -n "$MODELO" ]]; then + [[ "$MODELO" =~ ^[^/[:space:]\"]+/[^[:space:]\"]+$ ]] \ + || die "--modelo debe tener la forma proveedor/id (p. ej. anthropic/claude-sonnet-4-5)" +fi + +FICHEROS=(AGENTS.md README.md CHANGELOG.md TODO.md INSTALL.md .gitignore) + +render() { + local plantilla="$1" salida="$2" contenido + contenido="$(<"$plantilla")" + contenido="${contenido//'{{PROYECTO}}'/$PROYECTO}" + contenido="${contenido//'{{DESCRIPCION}}'/$DESCRIPCION}" + contenido="${contenido//'{{AUTOR}}'/$AUTOR}" + contenido="${contenido//'{{FECHA}}'/$FECHA_ISO}" + contenido="${contenido//'{{FECHA_ISO}}'/$FECHA_ISO}" + printf '%s\n' "$contenido" > "$salida" +} + +destino_abs="$(cd -- "$DESTINO" 2>/dev/null && pwd || true)" +[[ -n "$destino_abs" ]] || destino_abs="$DESTINO" + +creados=() +omitidos=() + +for fichero in "${FICHEROS[@]}"; do + ruta="$DESTINO/$fichero" + if [[ -e "$ruta" && $FORCE -eq 0 ]]; then + omitidos+=("$fichero") + aviso "$fichero ya existe; se omite (usa --force para sobreescribir)" + continue + fi + if [[ $DRY_RUN -eq 1 ]]; then + printf 'dry-run: escribiría %s\n' "$ruta" + else + mkdir -p -- "$DESTINO" + render "$TEMPLATES_DIR/$fichero.tmpl" "$ruta" + fi + creados+=("$fichero") +done + +if [[ -n "$MODELO" ]]; then + settings="$DESTINO/.pi/settings.json" + if [[ -e "$settings" && $FORCE -eq 0 ]]; then + omitidos+=(".pi/settings.json") + aviso ".pi/settings.json ya existe; se omite (usa /modelo-proyecto en pi para cambiar el modelo sin perder otros ajustes)" + elif [[ $DRY_RUN -eq 1 ]]; then + printf 'dry-run: escribiría %s (modelo %s)\n' "$settings" "$MODELO" + creados+=(".pi/settings.json") + else + mkdir -p -- "$DESTINO/.pi" + printf '{\n "defaultProvider": "%s",\n "defaultModel": "%s"\n}\n' \ + "${MODELO%%/*}" "${MODELO#*/}" > "$settings" + creados+=(".pi/settings.json") + fi +fi + +if [[ $INIT_GIT -eq 1 && $DRY_RUN -eq 0 ]]; then + if [[ -d "$DESTINO/.git" ]]; then + aviso "$DESTINO ya es un repositorio git" + else + git -C "$DESTINO" init -q + printf 'git: repositorio inicializado en %s\n' "$destino_abs" + fi +fi + +printf '\nproyecto: %s\n' "$PROYECTO" +printf 'destino: %s\n' "$destino_abs" +printf 'modelo: %s\n' "${MODELO:-sin fijar (elígelo con /modelo-proyecto)}" +printf 'creados: %s\n' "${creados[*]:-ninguno}" +[[ ${#omitidos[@]} -eq 0 ]] || printf 'omitidos: %s\n' "${omitidos[*]}" +[[ $DRY_RUN -eq 1 ]] || printf 'siguiente: revisa README.md, INSTALL.md y las secciones PENDIENTE de AGENTS.md\n' diff --git a/skills/new-project/templates/.gitignore.tmpl b/skills/new-project/templates/.gitignore.tmpl new file mode 100644 index 0000000..0b3be00 --- /dev/null +++ b/skills/new-project/templates/.gitignore.tmpl @@ -0,0 +1,27 @@ +# Secretos y configuración local +.env +.env.* +!.env.example + +# Dependencias descargadas +node_modules/ +vendor/ +venv/ +.venv/ +__pycache__/ +*.py[cod] + +# Artefactos de build +dist/ +build/ +target/ +out/ +*.log +logs/ + +# Sistema operativo y editores +.DS_Store +Thumbs.db +.idea/ +.vscode/ +*.swp diff --git a/skills/new-project/templates/AGENTS.md.tmpl b/skills/new-project/templates/AGENTS.md.tmpl new file mode 100644 index 0000000..3c46095 --- /dev/null +++ b/skills/new-project/templates/AGENTS.md.tmpl @@ -0,0 +1,88 @@ +# AGENTS.md + +Guía para agentes y colaboradores que trabajen en **{{PROYECTO}}**. +Léela antes de tocar cualquier fichero y respétala en cada cambio. + +## Propósito del proyecto + +{{DESCRIPCION}} + +## Contenido del repositorio + +| Ruta | Descripción | +| --- | --- | +| `AGENTS.md` | Este documento: reglas de trabajo. | +| `README.md` | Visión general y puesta en marcha. | +| `INSTALL.md` | Instalación detallada y dependencias. | +| `CHANGELOG.md` | Historial de cambios (*Keep a Changelog*). | +| `TODO.md` | Tareas pendientes y deuda técnica. | +| `.pi/settings.json` | Ajustes de pi del proyecto: modelo por defecto. | +| _(completar)_ | Añade aquí el código y los recursos del proyecto. | + +## Reglas de modelo + +### Modelo por defecto + +El modelo por defecto del proyecto se guarda en `.pi/settings.json` +(`defaultProvider` y `defaultModel`) y pi lo aplica al iniciar cada sesión nueva +en este directorio (requiere haber confiado en el proyecto: `/trust`). + +- Para cambiarlo: `/modelo-proyecto` (muestra los modelos disponibles) o + `/modelo-proyecto proveedor/id`. +- No edites a mano ni cambies ese modelo sin que lo pida el usuario. +- `.pi/settings.json` se versiona; un cambio de modelo se commitea como + cualquier otro cambio. + +### Otras reglas + +PENDIENTE: definir el contexto y el criterio de selección de modelos por tarea +(coste, VRAM, ventana, velocidad). Se acuerda tarea por tarea; hasta entonces, +no fijar valores aquí. + +## Reglas de funcionamiento + +PENDIENTE: definir flujo de trabajo, estándares de código, comandos obligatorios, +política de ramas y de revisión, y cualquier automatización propia del proyecto. + +## Reglas obligatorias + +1. **Todo cambio se documenta y se commitea.** Cada vez que se modifique + cualquier fichero del repositorio: + - Añadir una entrada en `CHANGELOG.md` (fecha, versión y tipo: + `Añadido` / `Cambiado` / `Corregido` / `Eliminado`). + - Actualizar `TODO.md` si el cambio abre, cierra o modifica tareas. + - Actualizar `README.md`, `INSTALL.md` o los documentos que correspondan si + cambia el comportamiento, la instalación o el uso. + - Crear **un commit de git** con mensaje claro en imperativo. +2. **Nunca dejar documentación desfasada.** Si el código y los documentos no + coinciden, el cambio está incompleto. +3. **No commitear** secretos, binarios, artefactos de build, logs ni dependencias + descargadas (`.gitignore` cubre lo básico; amplíalo según el stack). +4. **Idioma: español** para documentación, mensajes de commit y comentarios. + +## Verificación antes de commitear + +PENDIENTE: sustituir por los comandos reales del stack (formato, tipos, tests, +lint). Ningún commit sin verificación en verde. + +```bash +# ejemplo +# make check +``` + +## Flujo de trabajo + +1. Editar los ficheros necesarios. +2. Ejecutar las verificaciones anteriores. +3. Actualizar `CHANGELOG.md`, `TODO.md` y la documentación afectada. +4. `git add -A && git commit -m "tipo(alcance): descripción breve"`. +5. Informar del cambio y del hash del commit. + +## Convención de mensajes de commit + +`tipo(alcance): resumen` — tipos: `feat`, `fix`, `docs`, `chore`, `refactor`, +`test`, `build`, `perf`. + +Ejemplos: +- `docs: documentar instalación y puesta en marcha` +- `feat(api): añadir búsqueda paginada` diff --git a/skills/new-project/templates/CHANGELOG.md.tmpl b/skills/new-project/templates/CHANGELOG.md.tmpl new file mode 100644 index 0000000..9d4f615 --- /dev/null +++ b/skills/new-project/templates/CHANGELOG.md.tmpl @@ -0,0 +1,24 @@ +# Changelog + +Todos los cambios relevantes de este proyecto se documentan en este fichero. + +El formato sigue [*Keep a Changelog*](https://keepachangelog.com/es-ES/1.1.0/) +y el proyecto usa [Versionado Semántico](https://semver.org/lang/es/). + +## [No publicado] + +### Añadido + +- Base documental inicial del proyecto ({{FECHA}}): `AGENTS.md`, `README.md`, + `INSTALL.md`, `CHANGELOG.md`, `TODO.md` y `.gitignore`. + + diff --git a/skills/new-project/templates/INSTALL.md.tmpl b/skills/new-project/templates/INSTALL.md.tmpl new file mode 100644 index 0000000..9175d5c --- /dev/null +++ b/skills/new-project/templates/INSTALL.md.tmpl @@ -0,0 +1,79 @@ +# INSTALL — {{PROYECTO}} + +Instalación, configuración y puesta en marcha de **{{PROYECTO}}**. + +{{DESCRIPCION}} + +## 1. Requisitos + +PENDIENTE: completar con el stack real. + +| Componente | Versión mínima | Notas | +| --- | --- | --- | +| Sistema operativo | PENDIENTE | | +| Lenguaje / runtime | PENDIENTE | | +| Gestor de paquetes | PENDIENTE | | +| Otros | PENDIENTE | | + +Comprueba lo instalado: + +```bash +# PENDIENTE: comandos de comprobación (p. ej. python3 --version) +``` + +## 2. Obtener el código + +```bash +git clone +cd {{PROYECTO}} +``` + +## 3. Dependencias + +```bash +# PENDIENTE: instalación de dependencias +``` + +## 4. Configuración + +PENDIENTE: variables de entorno, ficheros de configuración y secretos. Indica +qué ficheros hay que crear y de dónde copiarlos (`.env.example`, etc.). + +```bash +# PENDIENTE: cp .env.example .env y editar +``` + +| Variable | Obligatoria | Descripción | +| --- | --- | --- | +| PENDIENTE | | | + +## 5. Verificación de la instalación + +```bash +# PENDIENTE: comando de humo que demuestre que funciona +``` + +## 6. Arranque + +```bash +# PENDIENTE: comando de arranque en desarrollo y en producción +``` + +## 7. Actualización + +```bash +git pull +# PENDIENTE: reinstalar dependencias y aplicar migraciones si las hay +``` + +## 8. Desinstalación + +PENDIENTE: qué borrar y qué dejar. + +## 9. Resolución de problemas + +| Síntoma | Causa probable | Solución | +| --- | --- | --- | +| PENDIENTE | | | + +Documento creado el {{FECHA}} por {{AUTOR}}. diff --git a/skills/new-project/templates/README.md.tmpl b/skills/new-project/templates/README.md.tmpl new file mode 100644 index 0000000..8f13bdd --- /dev/null +++ b/skills/new-project/templates/README.md.tmpl @@ -0,0 +1,58 @@ +# {{PROYECTO}} + +{{DESCRIPCION}} + +## Estado + +- Versión: `0.1.0` +- Fecha de creación: {{FECHA}} +- Autor: {{AUTOR}} +- Estado: en desarrollo + +## Requisitos + +PENDIENTE: lenguajes, versiones y dependencias del sistema. Ver `INSTALL.md`. + +## Instalación rápida + +```bash +# PENDIENTE: sustituir por los comandos reales +git clone +cd {{PROYECTO}} +``` + +Instrucciones completas en [`INSTALL.md`](INSTALL.md). + +## Uso + +PENDIENTE: ejemplo mínimo de uso o de arranque. + +```bash +# PENDIENTE +``` + +## Estructura + +```text +{{PROYECTO}}/ +├── AGENTS.md # reglas de trabajo para agentes y colaboradores +├── README.md # este documento +├── INSTALL.md # instalación y dependencias +├── CHANGELOG.md # historial de cambios +├── TODO.md # tareas pendientes +└── .pi/ + └── settings.json # modelo por defecto de pi (cambiar con /modelo-proyecto) +``` + +## Documentación + +| Documento | Contenido | +| --- | --- | +| [`AGENTS.md`](AGENTS.md) | Reglas, flujo de trabajo y convenciones. | +| [`INSTALL.md`](INSTALL.md) | Instalación, configuración y desinstalación. | +| [`CHANGELOG.md`](CHANGELOG.md) | Qué cambió y cuándo. | +| [`TODO.md`](TODO.md) | Qué falta y qué está en curso. | + +## Licencia + +PENDIENTE: indicar licencia del proyecto. diff --git a/skills/new-project/templates/TODO.md.tmpl b/skills/new-project/templates/TODO.md.tmpl new file mode 100644 index 0000000..66fb9cf --- /dev/null +++ b/skills/new-project/templates/TODO.md.tmpl @@ -0,0 +1,22 @@ +# TODO — {{PROYECTO}} + +Tareas pendientes y deuda técnica. Al cerrar una tarea, muévela a *Hecho* y +anota el cambio en `CHANGELOG.md`. + +## En curso + +- [ ] _(nada en curso)_ + +## Pendiente + +- [ ] Definir las **reglas de modelo** del proyecto en `AGENTS.md` (criterio por + tarea; el modelo por defecto se cambia con `/modelo-proyecto`). +- [ ] Definir las **reglas de funcionamiento** del proyecto en `AGENTS.md`. +- [ ] Completar los `PENDIENTE` de `README.md` e `INSTALL.md`. +- [ ] Fijar los comandos de verificación reales en `AGENTS.md`. +- [ ] Elegir licencia. + +## Hecho + +- [x] {{FECHA}} — base documental inicial (`AGENTS.md`, `README.md`, `INSTALL.md`, + `CHANGELOG.md`, `TODO.md`, `.gitignore`).