Planos · agentic-dev-boilerplate

agentic-dev-core · doctrina de contexto

El backlog no se commitea: PBI como caché de Jira

.context/PBI/ espeja las épicas, historias y bugs de Jira en markdown local. Durante mucho tiempo ese espejo se commiteaba, y dos sesiones que re-sincronizaban en momentos distintos producían commits en conflicto del mismo texto generado. El modelo nuevo lo dice sin rodeos: Jira es la fuente de verdad, el disco es un caché. Este deck explica los tres tiers, la escalera de .gitignore que los protege, y las defensas que evitan que el caché mienta.

AGENTS.md §9 .context/PBI/README.md scripts/sync-jira-issues.ts bun run context:hydrate

01El problema: una base de datos duplicada dentro de git

Un espejo de Jira commiteado parece inofensivo hasta que dos sesiones trabajan a la vez. Cada una re-sincroniza en su momento, cada una commitea su versión del mismo archivo regenerado, y el merge de 3 vías sobre una reescritura completa no significa nada: git no puede fusionar dos fotos de la misma base de datos tomadas con minutos de diferencia. Jira ya ES la copia versionada, compartida y en la nube — commitearla duplica la base de datos dentro del repo y no compra nada.

Incidente
La clase de incidente real que este modelo cierra: un host stale. Tras una migración de sitio de Atlassian, una copia vieja de la URL suelta en el entorno apuntaba el sync al workspace anterior — y como el sync SOBREESCRIBE .context/PBI/, un host desactualizado corrompe el caché con contenido de otro sitio reportando éxito. Con el caché commiteado, esa corrupción entraba a git en silencio. Por eso el host vive solo en .agents/project.yamlissue_tracker.atlassian_url y el resolver avisa si una copia de entorno discrepa.

02El modelo: tres tiers, una pregunta

Cada path bajo .context/PBI/ es exactamente uno de tres tiers. Antes de crear cualquier archivo ahí, se decide el tier — no hay archivos "más o menos versionados".

Tier Fuente de verdad ¿En git? Se recupera con
[SYNC] Jira No (gitignorado) bun run context:hydrate
[COMMIT] Este repo (README.md + templates/) git checkout
[LOCAL] Nada durable No No se recupera — desechable por diseño
El test
¿Un PM podría leerlo y opinar? Va a Jira (campo o comentario). ¿Maneja un resume o un workflow entre sesiones? Va a .session/sprint-development/<KEY>/progress.md — el contrato de resume lee eso, nunca la copia del PBI. ¿Ninguna de las dos? Es [LOCAL] (context.md, progress.md, evidence/): vive solo en esta máquina y perderlo tiene que costar cero. Nada downstream puede depender de que un [LOCAL] exista.
Regla
Los [SYNC] están prohibidos de escribir a mano: el sync los sobreescribe en cada corrida y ningún archivo está protegido. El flujo es siempre autorar → push al campo de Jira → sincronizar → leer la copia materializada.

03La escalera de negación en .gitignore

El árbol entero se excluye y las dos excepciones commiteadas se re-incluyen justo después. La forma exacta importa: git no puede re-incluir un archivo cuyo directorio padre está excluido, así que colapsar la escalera a una sola línea .context/PBI/ descarta las excepciones en silencio.

# ===== PBI: caché respaldado por Jira (Jira es la fuente de verdad) =====
.context/PBI/*          ← se excluye el CONTENIDO (/*), no el directorio
!.context/PBI/README.md  ← re-incluido: reglas de tiers [COMMIT]
!.context/PBI/templates/ ← re-incluido: esqueletos [COMMIT]

.context/PBI/            ← MAL: excluye el directorio y las negaciones mueren

Los context.md / progress.md / evidence/ de cada historia quedan deliberadamente sin negar: son [LOCAL] y se quedan en el disco de quien los hizo.

Verificar
Cualquier cambio a la escalera se comprueba con git check-ignore -v: sobre .context/PBI/README.md no debe reportar ignore, y sobre un stories/.../story.md sí debe. Dos comandos, cero fe.

04Clon frío e hidratación

Un clon fresco trae un .context/PBI/ casi vacío — solo el README y templates/. Ese es el estado intencional, no un checkout roto. El caché completo se reconstruye con un comando:

bun run context:hydrate   # = jira:sync-issues pull --include-comments

05Dos altitudes de plan, un solo flujo

Los planes de implementación viven en Jira como custom fields — así el bucle de desarrollo sobrevive resets de sesión y pérdidas de contexto local. Hay exactamente dos altitudes:

Campo Vive en Altitud Se materializa como
feature_implementation_plan el Epic macro — plan técnico de la feature entera; lo escribe el precheck de épica de /sprint-development epics/EPIC-PROJ-20-.../feature-implementation-plan.md
spec_implementation_plan la Story micro — la tajada por historia del plan padre stories/STORY-PROJ-42-.../implementation-plan.md
1 · autorar

La IA escribe el plan

En la etapa de planning, nunca directo a un archivo [SYNC].

2 · push

Al campo de Jira

Epic o Story según la altitud. El plan queda donde sobrevive.

3 · sync

jira:sync-issues

Materializa el .md local desde el campo.

4 · leer

La copia sincronizada

Todo lo que sigue lee el caché, jamás un borrador local.

Fallback
Si la instancia no tiene el custom field, el contenido va como comentario estructurado de Jira (## <label>, según .agents/jira-required.yamlfallback:) y el sync emite un stub apuntador para ese .md. Nunca se bloquea por un campo ausente.

06Defensas del workspace compartido

El mismo proyecto de Jira lo comparten el lado dev y el boilerplate QA hermano. El sync trae cuatro defensas para que los artefactos de un lado no contaminen el árbol del otro:

Filtro QA-Artifact

Épicas de proceso QA, fuera de epics/

Un Epic con el label QA-Artifact es un bucket de artefactos QA, no un módulo del producto: se archiva bajo qa-artifacts/. Tres señales en orden de confianza: el label (autoritativo), las keys cacheadas en qa.qa_epics.*.key, y el prefijo de nombre QA  como último recurso.

Guardas de altitud

Un STP nunca se disfraza de ATP

La escalera QA pone ATP: / ATR: / ATS: a altitud de Story; FTP: (feature), STP: / STR: (sprint) y MTP: (master) viven más arriba. Un link a un artefacto de altitud mayor se nombra en un INFO y jamás se materializa dentro de la carpeta de la Story.

Pull declarativo

El alcance vive en yaml, no en el script

Qué work types barre pull lo declara .agents/jira-required.yaml (sync: default). El default de fábrica resuelve a Bug solamente — Defect, Improvement o Tech Story son tipos custom y se piden con --types. Los tipos presentes en Jira que la corrida no tocará se nombran, para que una carpeta vacía se lea como configuración y no como bug.

Huérfanos visibles

_orphans/ es una lista de trabajo

Una Story sin Epic padre cae en epics/_orphans/ en vez de fallar. Un Test cuya única casa es membership interna de Xray (GraphQL-only, este repo no lleva cliente Xray) queda huérfano a la vista: el fix es re-linkearlo en Jira. Los Defects sin padre coverable se reportan con sus keys para re-linkear.

07Qué vive un proyecto consumidor

Un proyecto scaffoldeado antes de esta regla todavía trackea archivos [SYNC] en git. Al correr bun run up, un hook post-apply lista lo que git trackea bajo .context/PBI/, resta el allowlist commiteado (README.md + templates/**) y — si sobra algo — persiste un prompt de migración para el agente de IA del consumidor. El hook jamás toca el índice de git: destrackear es trabajo destructivo-adyacente que el agente hace con un punto de recuperación puesto.

  1. Tag de recuperación total, antes de destrackear nada — todo lo que sigue depende de este punto. git tag pbi-pre-cache-migration
  2. Destrackear exactamente los paths fuera del allowlist (los archivos quedan en disco). git rm -r --cached -- <paths fuera del allowlist>
  3. Commitear el destrackeo. git commit -m "chore: untrack .context/PBI cache (Jira is the source of truth)"
  4. Reconstruir el caché desde Jira (credenciales ATLASSIAN_* en .env). bun run jira:sync-issues pull
  5. Diffear el tag contra el caché reconstruido y empujar a Jira cualquier contenido que solo existía en git y nunca llegó a Jira — el sync sobreescribe con la verdad de Jira, así que eso ahora solo es visible por el tag. Recién después de este paso la migración puede reportarse como hecha. git diff pbi-pre-cache-migration -- .context/PBI
Entrega atómica

La escalera llega entera o no llega

El sync de .gitignore de bun run up agrupa una corrida de líneas con prefijo común que contiene una negación como un grupo todo-o-nada: una escalera de re-include aplicada a medias es peor que no aplicarla. El picker interactivo la ofrece como una sola decisión.

repoOnlyPaths

Lo del mantenedor nunca viaja

El updater filtra los paths solo-del-boilerplate antes de ofrecer nada: .github/workflows/pages.yml y ci.yml, los mapas business-*-map.md generados, y los catálogos jira-fields.json / jira-workflows.json por instancia. Un consumidor regenera los suyos; el update solo toca framework.