Preparar y ejecutar commits estandarizados según Conventional Commits, inferidos del diff real del repositorio.
Alcance: cada commit captura un único cambio lógico con mensaje semántico; una invocación puede producir varios (ver Selección de flujo). El push es opcional (ver Política de commit y push) y nunca ocurre en invocación delegada. No toca la configuración global de git ni aplica operaciones destructivas. Preguntar lo que no esté claro; no inventar tipo, scope ni descripción.
Formato canónico:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Cómo preguntar al usuario
Mecanismo, ritmo y fallback compartidos: ${PLUGIN_ROOT}/references/asking.md.
Cada vez que este skill o sus referencias digan preguntar, pedir, confirmar, validar o sugerir algo al usuario, asume ese mecanismo; no se repite allí.
No repreguntar lo que ya esté en el contexto de la sesión, en el diff o en una propuesta ya mostrada.
Excepciones al ritmo, una por turno: propuesta de división (una sola por invocación, solo cuando el diff se reparte en varios commits — un commit único no se confirma), commit en una rama de integración no declarada en integrationBranches.
Política de commit y push
Antes de ejecutar este skill, DEBES leer ${PLUGIN_ROOT}/references/git.md.
Las reglas de git.md son obligatorias y determinan si se muestra la propuesta de división y se espera confirmación (commitConfirmation) y si, además —solo en invocación directa del usuario, nunca en invocación delegada—, se hace push tras completar el/los commits (push).
No continúes hasta haber leído y aplicado git.md.
Rutas de las referencias compartidas
${PLUGIN_ROOT} es la raíz del plugin instalado (la carpeta que contiene skills/, agents/ y references/), y toda referencia compartida de este skill se escribe como ${PLUGIN_ROOT}/references/<archivo>.md. Para resolverla: la raíz es la carpeta desde la que se cargó este archivo, dos niveles arriba (<raíz>/skills/<nombre>/SKILL.md) — el cliente anuncia esa ubicación al invocar el skill (p. ej. «Base directory for this skill: …»). Si esa ubicación no está disponible, localizar la instalación buscando el manifiesto del plugin: PLUGIN_ROOT="$(dirname "$(grep -rl '\"name\": *\"sdd-devkit\"' "$HOME/.claude/plugins" --include=plugin.json 2>/dev/null | head -1)")". En ambos casos, verificar antes de usarla que $PLUGIN_ROOT/references/language.md existe. El destino de cada enlace markdown (../../references/…) existe solo para navegar el repositorio en GitHub o en un editor: no resolverlo desde el directorio de trabajo. Nunca buscar references/ en el proyecto: un <proyecto>/references/language.md que no existe no es un archivo que falte, es una ruta mal resuelta — corregir la raíz y volver a leer, sin preguntar al usuario ni saltarse la lectura.
Resolución de idioma
Antes de ejecutar este skill, DEBES leer ${PLUGIN_ROOT}/references/language.md.
Las reglas de language.md son obligatorias y tienen prioridad para determinar el idioma de todos los artefactos, documentos y mensajes generados por este skill.
No continúes hasta haber leído y aplicado language.md.
Excepción deliberada: el tipo y el scope van siempre en inglés (Conventional Commits), salvo convención explícita del equipo. El idioma resuelto aplica solo a la parte en lenguaje natural (descripción, body, footers). El output y los mensajes de error de git no se traducen.
Enlaces a archivos del proyecto: DEBES leer y aplicar ${PLUGIN_ROOT}/references/linking.md — todo archivo o carpeta existente del proyecto que este skill mencione en un artefacto, documento o mensaje (identificadores como US-XXX o WF-XXX, rutas, índices) se escribe como enlace Markdown que lo abre.
Convenciones del mensaje
- Tipo y scope: en inglés (ver «Resolución de idioma»).
- Header: la primera línea completa (
<type>[scope]: <description>) no puede exceder 100 caracteres. Es un límite duro: si se supera, acortar la descripción antes de proponer el commit; nunca commitear un header más largo. - Descripción: verbo imperativo presente (
add,fix,remove,validate), indica qué cambia y no cómo, máximo 72 caracteres, sin punto final, sin mayúscula inicial. - Breaking change:
!tras tipo/scope (feat!:) o footerBREAKING CHANGE: <detalle>. - Footer de issue:
Closes #123oRefs #456, solo si el usuario aporta el número.
| Tipo | Propósito |
|---|---|
feat |
Nueva funcionalidad |
fix |
Corrección de bug |
docs |
Solo documentación |
style |
Formato/estilo, sin lógica |
refactor |
Refactorización, sin feature ni fix |
perf |
Mejora de rendimiento |
test |
Añadir o actualizar tests |
build |
Sistema de build o dependencias |
ci |
Configuración de CI |
chore |
Mantenimiento o miscelánea |
revert |
Revertir un commit |
Inferencia desde el diff
Aplicar en orden; si nada encaja con confianza, preguntar al usuario.
Tipo — por los archivos y la naturaleza del cambio:
| Patrón observado | Tipo |
|---|---|
Solo *.md, README*, docs/**, CHANGELOG* |
docs |
Solo *.test.*, *.spec.*, __tests__/**, tests/** |
test |
Solo CI (.github/workflows/**, .gitlab-ci.*, etc.) |
ci |
Solo build (Dockerfile, deps de package.json, pom.xml, etc.) |
build |
| Solo formato/espaciado, sin lógica modificada | style |
Archivos nuevos en src/** que añaden funcionalidad usable |
feat |
| Cambio en lógica existente que corrige un bug descrito | fix |
| Reestructuración interna sin cambiar comportamiento observable | refactor |
| Cambio cuyo único fin es más velocidad o menos consumo | perf |
| Reversión de un commit anterior | revert |
| Configuración, scripts auxiliares, dependencias menores | chore |
Scope — primer criterio que aplique:
- Archivos bajo un único módulo (
src/auth/**) → nombre del módulo. - Archivos de una feature o dominio identificable → nombre de la feature.
- Cambio transversal a una capa → nombre de la capa (
api,db,ui,config). - Sin scope claro → omitirlo. No inventar genéricos (
misc,update,code).
Selección de flujo
| Condición | Flujo |
|---|---|
| Sin cambios en el repo | Informar y no commitear |
| Diff cubre un único tema lógico | Commit estándar |
| Diff mezcla temas sin relación (docs + feature, fix + refactor, módulos distintos) | Múltiples cambios lógicos |
git commit falló por un pre-commit hook |
Recuperación tras fallo de hook |
| Invocado por otro skill, no por el usuario | Mismos flujos, más el contrato de invocación |
Invocación desde otro skill
work-integrate y pr-create invocan este flujo automáticamente, una sola vez por cierre y después de sus puertas (el árbol contiene entonces el trabajo pendiente, las correcciones y los informes de las puertas), y siguen adelante con su propio proceso según el resultado. No lo invocan para sus commits mecánicos —el cierre chore(<ID>): cerrar trabajo en la rama base ni el archivado docs(specs): archivar <ID>—, que hacen directos según Commit directo de ${PLUGIN_ROOT}/references/git.md: ahí no hay nada que inferir. No cambia lo que hace este skill —sigue aplicando la validación completa, y sigue confirmando la división cuando el diff se reparte en varios commits—, pero sí fija cuatro cosas:
- El objetivo es dejar el árbol limpio, no un único commit. El alcance habitual («un cambio lógico») se refiere a cada commit, no a la invocación: si lo pendiente mezcla temas, se resuelve con el flujo de múltiples cambios lógicos hasta que no quede nada. El invocador re-comprueba
git statusy vuelve a llamar sobre el remanente, así que dejarlo a medias solo alarga el ciclo. - Contrato de salida. Al terminar, reportar en cuál de estos tres estados se queda, porque el invocador decide con eso: (a) limpio — todo commiteado; (b) parcialmente limpio por decisión de alcance — se commiteó parte y el resto se dejó fuera a propósito (el invocador volverá a llamar sobre el remanente); (c) detenido, con el motivo — rama con
commitPolicy: pull_request, rama de integración no declarada y sin confirmar, hook que no pasa, o una decisión que el usuario no resolvió. Solo (c) bloquea al invocador, y necesita el motivo literal para reportarlo. - El push no aplica en este modo. Aunque
.sdd-devkit/settings.jsontengagit.pushenaskoalways, en invocación delegada el/los commits quedan siempre en local — el invocador (work-integrate,pr-create) decide qué sigue y en qué momento hace push.commitConfirmationsí se sigue resolviendo igual que en invocación directa. - No deshacer el staging que trae el invocador. El paso 4 del flujo múltiple vacía el staging antes de cada grupo, y eso está bien cuando se parte de cero — pero un invocador puede haber preparado el índice a propósito. Dos casos reales: el renombrado del archivado (
git mv <changesPath>/<tipo>/<ID>-<slug> <archivedPath>/<tipo>/, stageado porpr-createen su Paso 5 antes de invocar este flujo, o por el skill que archiva cuando su commit directo se detuvo y el usuario pide cerrarlo con/git-commit) y elgit rmde los informes que hacepr-createen modo promoción. Antes del primergit reset, comprobargit diff --cached --name-only: si hay algo stageado que no viene de este flujo, conservarlo e incorporarlo como su propio grupo. Lo mismo vale para elgit restore --stageddel flujo estándar: no retirar del índice lo que preparó el invocador.- En el renombrado, conservarlo importa doblemente: un
git resetdeshace la detección de rename y deja un borrado más un archivo sin trackear, perdiendo el historial de cada archivo movido. Nunca revertir ungit mvrecibido.
- En el renombrado, conservarlo importa doblemente: un
- Los artefactos generados de las puertas son commiteables como cualquier otro cambio.
docs/audits/*.mdy loscriteria-coverage.mdvan con tipodocs; su borrado también (ver la tabla de tipos).
Los renombrados masivos de documentación van con tipo docs. El archivado de un US-XXX/WI-XXX es el caso canónico: docs(specs): archivar US-042 (normalmente un commit directo del skill que archiva; llega aquí solo si ese commit se detuvo o si el renombrado viene mezclado con otros cambios, como en pr-create). No es chore — mueve documentación, y el diff, si git detecta el rename, es solo la ruta. Ver ${PLUGIN_ROOT}/references/archive.md.
Los borrados tienen tipo propio. Un commit que solo elimina archivos se clasifica por lo que eran: docs si eran documentación o informes, chore si eran archivos auxiliares generados, refactor si era código cuya funcionalidad se movió, feat/fix si el borrado es el cambio de comportamiento. No usar chore por defecto para todo borrado.
Flujo: Commit estándar
- Inspeccionar estado y diff:
git status --porcelain git diff --staged # si hay staging git diff # si no hay staging git rev-parse --abbrev-ref HEAD - Ajustar staging: añadir lo que falte del cambio (
git add <ruta>), retirar lo no relacionado (git restore --staged <ruta>). - Inferir tipo, scope y descripción desde el diff.
- Decidir body (solo si aporta contexto no obvio) y footer (
BREAKING CHANGE,Closes #N). - Pasar la Validación. Si falla, detener.
- No preguntar. Un commit único no se confirma: el mensaje se infiere del diff, la Validación ya cubrió lo que puede hacer daño (rama protegida, formato) y el commit es reversible. Ejecutar directamente.
commitConfirmationno aplica aquí — solo gobierna la división en varios commits, que es la única decisión que el usuario no puede tomar después. - Ejecutar:
# Una línea git commit -m "<type>[scope]: <description>" # Multi-línea git commit -m "$(cat <<'EOF' <type>[scope]: <description> <optional body> <optional footer> EOF )" - Reportar SHA corto (
git rev-parse --short HEAD) y mensaje del commit. - Solo en invocación directa del usuario: resolver push según
git.push—alwaysejecutagit pushsin preguntar (git push -u origin <rama>si no hay upstream);askpregunta una sola vez y ejecuta si se confirma;neverno hace nada.
Flujo: Múltiples cambios lógicos
- Agrupar archivos por afinidad desde el diff (área, tipo de cambio, intención).
- Proponer la división: lista ordenada de commits con tipo/scope, archivos y descripción de cada uno.
- Si
commitConfirmation = always(o no haysettings.json), esperar una única confirmación de la división antes de tocar el staging. Esta es la única pregunta del skill, y solo existe porque repartir el trabajo en N commits es lo que el usuario no puede deshacer cómodamente después. Lo que se decide es cómo se reparte —los N commits propuestos o uno solo con todo—, no el detalle de cada mensaje: la lista ya lo muestra, y aprobarla lo aprueba entero. Si el usuario elige un solo commit, continuar por el flujo estándar desde el paso 2 con un mensaje que cubra el conjunto, sin volver a confirmar. Si pide un ajuste de la agrupación, aplicarlo y volver a mostrar la lista. SicommitConfirmation = never, omitir este paso: continuar directo al 4 con la agrupación decidida, sin mostrarla ni esperar confirmación. - Por cada grupo confirmado, en orden y sin volver a preguntar:
git resetpara vaciar staging (preserva el working tree; salvo que el índice traiga cambios preparados por el skill invocador — ver Invocación desde otro skill) →git add <archivos>del grupo → Validación →git commity registrar el SHA. El lote solo se interrumpe si la validación falla (header largo, rama sin confirmar): detener ahí, reportar el motivo y esperar al usuario. - Reportar la secuencia final de SHAs y mensajes en el orden ejecutado.
- Solo en invocación directa del usuario, una vez completado todo el lote: resolver push igual que en el paso 9 del flujo estándar — una sola vez para el lote completo, no por cada commit.
Flujo: Recuperación tras fallo de hook
- Leer el mensaje del hook y aplicar las correcciones en el working tree.
- Re-stagear los archivos corregidos (
git add <archivos>). - Crear un commit nuevo con el mismo mensaje acordado. No usar
--amendni--no-verifysalvo petición explícita. - Si el fallo es del propio hook (config rota, no del código): informar al usuario y esperar instrucciones.
Propuesta de división
Solo se pregunta cuando el diff se reparte en varios commits. Un commit único no se propone ni se
confirma: se infiere el mensaje, se pasa la validación y se ejecuta. Confirmar un mensaje que el usuario
puede corregir después con git commit --amend solo añade un turno; lo que sí merece una pregunta es la
división, porque rehacerla implica deshacer commits.
Esto vale también cuando el usuario elige "hacer uno solo" desde la propuesta de división: ya decidió, no se le vuelve a preguntar por el mensaje resultante.
Aplica con commitConfirmation = always (o sin settings.json) — ver Política de commit y push. Con commitConfirmation = never se omite por completo y se ejecuta directo.
Mostrar una sola vez por invocación y esperar confirmación con la herramienta de preguntas estructuradas. La confirmación cubre el lote completo: una vez aprobada la división, ejecutar la secuencia entera sin volver a preguntar por cada commit.
Mostrar la división completa, en el orden en que se ejecutarán:
Propuesta: <N> commits
1. <type>[scope]: <description>
- <archivo 1>
- <archivo 2>
2. <type>[scope]: <description>
- <archivo 3>
...
(body/footer solo donde aplique)
Opciones: [Confirmar los commits] / [Hacer uno solo] / [Cancelar]. No ofrecer una opción de "ajustar la agrupación": la herramienta de preguntas ya deja escribir una respuesta libre, y ahí es donde el usuario dice cómo quiere repartir los archivos. Si responde con un ajuste, aplicarlo y volver a mostrar la propuesta; si elige un solo commit, ejecutarlo directamente con un mensaje que cubra el conjunto, sin una segunda confirmación.
Validación antes de ejecutar
Gate obligatorio antes de cada git commit. Detenerse si algún punto falla.
-
Diff:
git statusygit diffrevisados; tipo, scope y descripción derivados del diff (no inventados). -
Aislamiento: un solo cambio lógico en el commit.
-
Operaciones seguras: sin
--force,--hard,--no-verify,--amendsalvo petición explícita. -
Rama: resuelta con
integrationBranchesde${PLUGIN_ROOT}/references/git.md, que es quien dice qué es una rama de integración en este repo:Rama actual Qué hacer commitPolicy: mergeComitear. No pedir confirmación extra: el repo ya autorizó ese uso al declararla así. commitPolicy: pull_requestDetenerse con ese motivo (estado (c) del contrato de salida). No es una confirmación que el usuario pueda saltarse: esa rama solo recibe trabajo vía PR. No está en la lista Rama de trabajo normal: comitear sin más. Sin integrationBranchesdeclaradaComportamiento heredado: si la rama parece de integración o despliegue ( main,master,develop,trunk,release/*,staging,uat,qa,produccion), pedir confirmación una sola vez por invocación — la rama no cambia entre los commits de un mismo flujo de múltiples cambios lógicos, así que la confirmación del primer commit del lote cubre a los demás. -
Formato: primera línea
<type>[scope]: <description>válida según convenciones y de 100 caracteres o menos; breaking change y footer de issue marcados si aplican. -
Confirmación: solo cuando hay división en varios commits y
commitConfirmation = always(o no haysettings.json): propuesta mostrada y confirmada, una sola vez por invocación — cubre todos los commits del lote, no se repregunta en cada iteración del paso 4 del flujo de múltiples cambios lógicos. Un commit único no requiere confirmación y este punto no aplica; tampoco concommitConfirmation = never. En todos los casos, el resto de la validación (aislamiento, rama, formato) sigue siendo obligatorio.
Si algo bloquea, informar:
⚠️ No es posible commitear todavía:
- <razón concreta>
Ejemplos
1 — Commit estándar. Entrada: «Corregí el bug del cupón vacío en checkout; diff solo en src/cart/checkout.ts.» → fix(cart): validate empty coupon before apply
2 — Cambios mezclados. Entrada: diff con README.md, docs/api.md y src/api/users.ts (ruta nueva). → Dos commits: docs: update API endpoint reference y feat(users): add endpoint to fetch user preferences.
3 — Breaking change. Entrada: el diff renombra el endpoint público /v1/users → /v2/users, rompiendo clientes. →
feat(api)!: rename users endpoint to v2
BREAKING CHANGE: `/v1/users` removed; clients must migrate to `/v2/users`.
4 — Información incompleta. Entrada: «Haz commit de lo que está en staging», cambios sin patrón claro entre módulos. → Preguntar la intención principal o proponer agrupación; no generar un mensaje genérico.
5 — Fallo de hook. Entrada: el hook de lint falla en src/utils.ts tras git commit. → Aplicar el formateo, git add src/utils.ts, commit nuevo con el mismo mensaje. Sin --amend ni --no-verify.
6 — commitConfirmation: never con push: always. Entrada: «Haz commit de todo», diff con tres cambios lógicos separados (checkout, tests y CI), .sdd-devkit/settings.json con git.commitConfirmation: "never" y git.push: "always". → Agrupar en tres commits y ejecutarlos sin mostrar la división ni esperar confirmación (eso es lo que apaga never), y a continuación ejecutar git push (con -u origin <rama> si no hay upstream) sin preguntar. Reportar SHAs, mensajes y confirmación del push. Con un solo cambio lógico el resultado sería el mismo con cualquier valor de commitConfirmation: un commit único nunca se confirma.
7 — Invocación delegada con push: always. Entrada: work-integrate invoca este skill con el árbol sucio, y .sdd-devkit/settings.json tiene git.push: "always". → El commit se ejecuta normalmente (sin confirmar el mensaje; commitConfirmation solo aplicaría si hubiera división en varios commits), pero no se hace push: el push no aplica en invocación delegada, sin importar la política. Se reporta el estado (limpio/parcial/detenido) y work-integrate decide qué sigue.
Anti-patterns
- Narrar el flujo interno: anunciar que se resuelve el idioma o la política, que se lee
settings.json, que se carga una referencia, o ir enumerando los pasos en voz alta. Al usuario se le comunica el resultado, las preguntas que el flujo exija y lo que quede pendiente — no la maquinaria. - Mezclar features, fixes y refactors sin relación en un mismo commit.
- Mensajes vagos:
update,fix stuff,changes,wip. - Headers de más de 100 caracteres: partir el detalle al body en vez de alargar la primera línea.
- Inventar tipo o scope cuando el diff no lo respalda.
- Usar
--no-verifypor comodidad, o--amendtras un fallo de hook en vez de un commit nuevo. - Force push a
main/mastero tocar la config global de git sin permiso. - Ejecutar la división en varios commits sin haber mostrado la propuesta y obtenido confirmación cuando
commitConfirmation = always(o no haysettings.json). - Pedir confirmación del mensaje de un commit único —o del mensaje resultante de elegir "hacer uno solo"—: el mensaje se infiere y se ejecuta. La única pregunta del skill es la división.
- Repreguntar por el detalle de cada commit del lote cuando el usuario ya confirmó la división.
- Hacer push en invocación delegada (por
work-integrateopr-create), sin importar el valor degit.push. - Hacer push sin haber resuelto primero la política de
git.push, o pushear conpush = never. - Preguntar en prosa libre cuando el cliente expone la herramienta estructurada.
- Narrar el trabajo paso a paso: reportar solo SHA, mensaje final y pendientes.
- Leer
git.mdolanguage.mdcomo prosa y razonar a mano sobre.sdd-devkit/settings.jsonen vez de ejecutar su bloque```!con Bash — el resultado puede coincidir por suerte, no por garantía.