llm-dev-core 0.3.0__tar.gz

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.
Files changed (49) hide show
  1. llm_dev_core-0.3.0/.github/workflows/ci.yml +21 -0
  2. llm_dev_core-0.3.0/.github/workflows/publish.yml +22 -0
  3. llm_dev_core-0.3.0/.gitignore +27 -0
  4. llm_dev_core-0.3.0/.python-version +1 -0
  5. llm_dev_core-0.3.0/ARCHITECTURE.md +368 -0
  6. llm_dev_core-0.3.0/CHANGELOG.md +35 -0
  7. llm_dev_core-0.3.0/Makefile +14 -0
  8. llm_dev_core-0.3.0/PKG-INFO +9 -0
  9. llm_dev_core-0.3.0/README.md +20 -0
  10. llm_dev_core-0.3.0/docs/decisions/0000-stack.md +30 -0
  11. llm_dev_core-0.3.0/docs/decisions/0001-llm-client-contract.md +138 -0
  12. llm_dev_core-0.3.0/docs/decisions/0002-schema-validate-contract.md +77 -0
  13. llm_dev_core-0.3.0/docs/metrics/README.md +7 -0
  14. llm_dev_core-0.3.0/docs/runbooks/README.md +13 -0
  15. llm_dev_core-0.3.0/packages/llm-client/pyproject.toml +16 -0
  16. llm_dev_core-0.3.0/packages/llm-client/scripts/record_tape_opencode.py +53 -0
  17. llm_dev_core-0.3.0/packages/llm-client/src/llm_client/__init__.py +18 -0
  18. llm_dev_core-0.3.0/packages/llm-client/src/llm_client/cache.py +44 -0
  19. llm_dev_core-0.3.0/packages/llm-client/src/llm_client/client.py +246 -0
  20. llm_dev_core-0.3.0/packages/llm-client/src/llm_client/models.py +43 -0
  21. llm_dev_core-0.3.0/packages/llm-client/src/llm_client/pricing.py +22 -0
  22. llm_dev_core-0.3.0/packages/llm-client/src/llm_client/provider.py +28 -0
  23. llm_dev_core-0.3.0/packages/llm-client/src/llm_client/providers/__init__.py +4 -0
  24. llm_dev_core-0.3.0/packages/llm-client/src/llm_client/providers/openai_compat.py +86 -0
  25. llm_dev_core-0.3.0/packages/llm-client/src/llm_client/providers/opencode_cli.py +73 -0
  26. llm_dev_core-0.3.0/packages/llm-client/src/llm_client/replay.py +68 -0
  27. llm_dev_core-0.3.0/packages/llm-client/src/llm_client/span.py +33 -0
  28. llm_dev_core-0.3.0/packages/llm-client/tests/__init__.py +0 -0
  29. llm_dev_core-0.3.0/packages/llm-client/tests/cassettes/complete-opencode--big-pickle-2.jsonl +1 -0
  30. llm_dev_core-0.3.0/packages/llm-client/tests/conftest.py +51 -0
  31. llm_dev_core-0.3.0/packages/llm-client/tests/test_openai_compat.py +81 -0
  32. llm_dev_core-0.3.0/packages/llm-client/tests/test_pricing_cache.py +55 -0
  33. llm_dev_core-0.3.0/packages/llm-client/tests/test_real_tape.py +50 -0
  34. llm_dev_core-0.3.0/packages/llm-client/tests/test_replay.py +53 -0
  35. llm_dev_core-0.3.0/packages/llm-client/tests/test_span.py +162 -0
  36. llm_dev_core-0.3.0/packages/schema-validate/pyproject.toml +19 -0
  37. llm_dev_core-0.3.0/packages/schema-validate/src/schema_validate/__init__.py +13 -0
  38. llm_dev_core-0.3.0/packages/schema-validate/src/schema_validate/registry.py +44 -0
  39. llm_dev_core-0.3.0/packages/schema-validate/src/schema_validate/validate.py +67 -0
  40. llm_dev_core-0.3.0/packages/schema-validate/tests/test_schema_validate.py +164 -0
  41. llm_dev_core-0.3.0/pyproject.toml +30 -0
  42. llm_dev_core-0.3.0/scripts/check_consumers.py +240 -0
  43. llm_dev_core-0.3.0/scripts/collect_metrics.py +28 -0
  44. llm_dev_core-0.3.0/scripts/compat_check.sh +27 -0
  45. llm_dev_core-0.3.0/templates/consumer-readme.md +31 -0
  46. llm_dev_core-0.3.0/templates/core-consumer.yml +21 -0
  47. llm_dev_core-0.3.0/templates/eval-dataset.jsonl +1 -0
  48. llm_dev_core-0.3.0/templates/prompt.md +14 -0
  49. llm_dev_core-0.3.0/templates/retrofit-issue.md +23 -0
@@ -0,0 +1,21 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ validate:
13
+ name: validate (invariantes)
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v6
18
+ with:
19
+ python-version-file: .python-version
20
+ - run: uv sync
21
+ - run: make validate
@@ -0,0 +1,22 @@
1
+ name: publish
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ permissions:
8
+ contents: read
9
+ id-token: write # obligatorio para trusted publishing (OIDC, sin token guardado)
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v6
17
+ with:
18
+ python-version-file: .python-version
19
+ - run: uv sync
20
+ - run: make validate
21
+ - run: uv build
22
+ - run: uv publish
@@ -0,0 +1,27 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .mypy_cache/
13
+
14
+ # Env
15
+ .env
16
+ .env.*
17
+
18
+ # Tooling
19
+ *.log
20
+ .coverage
21
+ htmlcov/
22
+
23
+ # OS
24
+ .DS_Store
25
+
26
+ # uv
27
+ uv.lock
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,368 @@
1
+ # ARCHITECTURE.md — llm-dev-core
2
+
3
+ > **Estado:** v0.3 (seed revisado) · **Última revisión:** 2026-09-21
4
+ > **Audiencia:** yo en 52 semanas, cualquiera que revise este repo, y entrevistadores técnicos.
5
+
6
+ ---
7
+
8
+ ## 1. La idea
9
+
10
+ **52 proyectos en 52 semanas no son 52 repos: son un solo sistema acumulativo.**
11
+
12
+ La tentación del desarrollador asistido por LLM es acumular demos desconectadas: cada una con su propio wrapper del API, su propio manejo de errores y su propio logging. Eso produce *volumen*, no *capacidad*. El año 2026 está lleno de portfolios así, y todos dicen lo mismo: "sé llamar a una API".
13
+
14
+ Este repo existe para hacer lo contrario: **cada semana se construye un proyecto nuevo consumiendo infraestructura de semanas anteriores**. La complejidad no se suma, se *reutiliza*. Y la reutilización no es un ahorro de tiempo: es la prueba de que las abstracciones son correctas. Una abstracción que solo se usa una vez es una hipótesis; una que se usa 30 veces es un diseño.
15
+
16
+ El core es la acumulación física de esa idea.
17
+
18
+ ## 2. El objetivo
19
+
20
+ Construir, en 52 semanas, un paquete publicado y versionado que sea la base verificable de 52 proyectos reales en GitHub, demostrando no que puedo *generar código con un LLM*, sino que puedo **dirigir, acumular, versionar y mantener** código asistido por LLM.
21
+
22
+ El calendario es un grafo de dependencias, no un reloj: la semana es un *número de secuencia*. Si una semana no existe (vida, enfermedad, una entrevista real), el número se salta y lo que se preserva es la cadena de acumulación, no la fecha.
23
+
24
+ | Métrica | Meta |
25
+ |---|---|
26
+ | Proyectos consumidores publicados | 52 |
27
+ | Superficie nueva por proyecto (semanas de composición) | ≤ 30% (≥ 70% reutilización) |
28
+ | Llamadas a LLM que pasan por `llm-client` | 100%, sin excepciones |
29
+ | Prompts con metadatos desde sem. 2; en `prompt-registry` desde sem. 13 | 100% de los prompts en producción |
30
+ | Prompts nuevos con eval desde la sem. 5 | 100% |
31
+ | Evals en CI desde la semana 5 | en todo consumidor |
32
+ | Retrofittings documentados | ≥ 5 |
33
+ | Releases | v1.0 (sem. 13) → v2.0 (sem. 26) → v3.0 (sem. 39) → v4.0 (sem. 52, solo si hay consumidores que lo justifiquen; si no, consolidación) |
34
+ | Compat check (core vs. consumidores) | desde v1.0, en cada merge |
35
+ | Página de evidencia auto-generada | viva desde v1.0 |
36
+
37
+ Si al final del año el core no tiene consumidores reales más allá de mí, habré construido 52 demos y este documento será una mentira elegante. La meta es que ningún proyecto del año pueda existir sin él.
38
+
39
+ ### 2.1 Cómo se verificarán las métricas
40
+
41
+ Las métricas de este proyecto no son aspiracionales. Cada una tiene una fuente de verdad y, cuando es posible, una verificación automática que rompe el build.
42
+
43
+ | Métrica | Fuente de verdad | Verificación |
44
+ |---|---|---|
45
+ | Proyectos consumidores publicados | Repos semanales | Topic `llm-dev-consumer` + `core-consumer.yml` |
46
+ | Superficie nueva ≤ 30% | Manifest del consumidor | `new_surface` y `reused_modules` declarados y revisados |
47
+ | Llamadas LLM vía `llm-client` | Código | Lint anti-imports directos: el build falla si un repo importa el SDK fuera de `llm-client` |
48
+ | Prompts en el registry | Archivos de prompts | Lint anti-prompts inline + frontmatter obligatorio desde sem. 2 |
49
+ | Evals en CI desde sem. 5 | CI del consumidor | Job `eval-smoke` obligatorio en la plantilla |
50
+ | Retrofittings documentados | Issues/PRs | Label `retrofit` + plantilla antes/después |
51
+ | Compat check | CI del core | Workflow `compat-last-5` en cada merge a `main` |
52
+
53
+ Cada consumidor declara en la raíz de su repo un manifest que es a la vez contrato e inventario:
54
+
55
+ ```yaml
56
+ # core-consumer.yml
57
+ week: 14
58
+ name: contract-reviewer
59
+ core_version: 1.3.0
60
+ reused_modules:
61
+ - llm-client
62
+ - schema-validate
63
+ - prompt-registry
64
+ - web-api-base
65
+ - test-kit
66
+ new_surface:
67
+ - multi-tenant contract workspace
68
+ - clause versioning
69
+ estimated_new_surface: 25%
70
+ ```
71
+
72
+ El porcentaje es estimado; no puede ser perfectamente automático. Pero el acto de declararlo obliga a pensar en reutilización, y junto al lint anti-imports hace que la métrica sea auditable, no prometida.
73
+
74
+ El 70/30 solo aplica a **semanas de composición**. Las **semanas-seed** (donde nace un módulo del core) se miden por otra vara: el módulo extraído, su contrato y su primer consumidor. Si no se distinguen, las semanas 2–7 parecen "mal planteadas" bajo el principio 4, cuando son la excepción explícita y deseada.
75
+
76
+ ## 3. Principios no negociables
77
+
78
+ Los principios son **invariantes ejecutables**, no aspiraciones: cada uno tiene un lint o un job de CI que lo hace cumplir (ver §2.1). Lo no verificado no es un principio, es una anécdota.
79
+
80
+ 1. **Toda llamada a un LLM pasa por `llm-client`.** Sin excepciones, ni siquiera en spikes. Lo no medido no se refactoriza. *Verificación:* el build falla si hay llamadas al SDK del proveedor fuera de `llm-client`.
81
+ 2. **Todo prompt vive versionado con metadatos.** Los prompts son código: se versionan, se evalúan y se revierten como cualquier otro cambio. Desde sem. 2 en `/prompts` (frontmatter obligatorio); desde sem. 13 en el paquete `prompt-registry`. *Verificación:* lint de frontmatter y anti-prompts inline.
82
+ 3. **La salida de un LLM es input no confiable y acción no confiable.** Todo pasa por `schema-validate` antes de tocarse; las herramientas de agentes operan con allowlist, sandbox y confirmación explícita (§3.1).
83
+ 4. **No existe "proyecto desde cero".** Si una semana de composición propone algo que no reutiliza ≥70% del core, la semana está mal planteada: se recorta alcance, nunca calidad. Las semanas-seed son la excepción explícita: su alcance es el módulo nuevo más su primer consumidor, y se declaran como tales en el manifest.
84
+ 5. **El excedente no se corta, se traslada.** Si una semana se desborda, el sobrante es la primera tarea de la siguiente, como mejora al core. El calendario se mantiene; la deuda queda visible.
85
+ 6. **El core nace de los consumidores, no antes.** Nada entra al core por elegancia: entra porque dos proyectos lo necesitaron, porque la semana 13/26/39/52 lo consolidó, o porque la regla del tercer uso (D12) lo declaró.
86
+
87
+ ### 3.1 Seguridad operativa
88
+
89
+ Muchos proyectos LLM fallan exactamente aquí: prompts con datos reales, agentes con permisos excesivos, scrapers sin control, costos sin techo.
90
+
91
+ 1. **Los secretos y datos sensibles no entran en prompts sin redacción.** El core ofrece utilidades de máscara para emails, tokens, claves, teléfonos y datos personales.
92
+ 2. **La salida del LLM es acción no confiable.** Las herramientas de agentes operan con permisos mínimos, allowlist y sandbox; las acciones irreversibles requieren confirmación explícita.
93
+ 3. **Los proveedores LLM son procesadores externos.** Cada consumidor declara qué datos envía, con qué retención y bajo qué configuración de privacidad.
94
+ 4. **Scraping y acceso externo respetan límites.** Rate limiting, caché, `robots.txt` cuando aplique y respeto a los términos de servicio.
95
+ 5. **Toda integración con GitHub o APIs externas usa permisos mínimos.** Nada de tokens con alcance excesivo para demos.
96
+ 6. **El costo de llamadas y evals tiene techo.** Presupuesto declarado por consumidor (§8.1); cualquier llamada que exceda su cap se registra como anomalía, no como gasto normal.
97
+
98
+ ## 4. Argumentación de las decisiones
99
+
100
+ **D1 — Un core publicado, no 52 repos independientes.** El formato natural del challenge es 1 repo por semana; eso es lo que casi todos hacen, y produce volumen sin capacidad. La decisión: un paquete `llm-dev-core` versionado del que los 52 repos dependen como librería, publicado en un registry público (PyPI) — de otra forma los repos no son instalables por un tercero y la historia de evidencia se rompe. Cualquiera pega un prompt en un README; muy pocos muestran un changelog que dice *"v2.0: breaking change — cost log por token; consumidores migrados: repos 14, 21, 25, 34"*. Mantener código con usuarios —aunque el único usuario sea mi yo del pasado— es la habilidad que separa dirección de copia-pega. *Trade-off aceptado:* acoplamiento; se mitiga con D8, no se niega.
101
+
102
+ **D2 — Monorepo para el core, repos separados para los consumidores.** Los módulos del core viven en un solo repo (cambios atómicos entre módulos, una sola CI); cada proyecto semanal es un repo propio con `depends on core ^x.y`. Los 52 repos existen como testigos públicos independientes, con su demo y su "Recicla de"; el core existe como ingeniería. Confundir ambos en un mega-repo destruiría las dos propiedades. *Trade-off aceptado:* consumidores congelados en versiones viejas; es semver funcionando.
103
+
104
+ **D3 — Semver honesto: `0.x` hasta la semana 13.** Durante el Q1 el core es `0.x`: "todo puede romperse". En la semana 13, con 12 consumidores reales detrás, se congela **v1.0** y empiezan las garantías. Prometer estabilidad sin usuarios es teatro; esperar a que 12 proyectos sufran mis abstracciones antes de fijar contratos es el orden correcto. Por la misma regla, cada hito (26, 39, 52) solo publica major si hay consumidores que lo justifiquen; si no, es un hito de consolidación sin release.
105
+
106
+ **D4 — `llm-client` con telemetría desde el día 1 (sem. 2).** El primer módulo del core —nacido en un CLI trivial de commits— ya incluye retry con backoff, streaming, JSON mode y **log estructurado por llamada**. En aplicaciones LLM, el costo no es una métrica de negocio secundaria: es parte del runtime. Si no se observa desde el día 1, el sistema no es operable.
107
+
108
+ Lo que no se retrofittea gratis no es el sink de datos: es el **formato del span**. El schema es **contrato público de `llm-client` desde la semana 2**, no propiedad de `cost-obs` ni de nadie. Si 37 semanas de spans viven en JSONL heterogéneos por repo, la semana 39 hereda un problema de datos, no un dataset. Campos mínimos:
109
+
110
+ ```text
111
+ trace_id · consumer_repo · prompt_id · prompt_version · model_alias
112
+ model · provider · tokens_input · tokens_output · latency_ms · cost_usd
113
+ retry_count · cache_hit · status · call_skipped · retry_unnecessary
114
+ ```
115
+
116
+ `call_skipped` y `retry_unnecessary` capturan la pregunta que importa en la semana 39: no cuánto costó la llamada, sino **si se necesitaba**. El log de costo dice cuánto gastaste; el log de utilidad dice cuánto tiraste.
117
+
118
+ Igual de no-retrofitteable: **record/replay**. `llm-client` nace con una interfaz de proveedor que incluye un backend de replay (cassettes grabados). Es lo que hace posible evals deterministas en CI de 52 repos, compatible check sin golpear APIs vivas y pipelines sin API keys. `test-kit` lo implementa en la sem. 5; el compat check lo consume en la 13.
119
+
120
+ *Trade-off:* ~ms de overhead por llamada, irrelevante frente a 11 meses de telemetría propia.
121
+
122
+ **D5 — `schema-validate`: el LLM como input no confiable (sem. 3).** Toda salida se valida contra un esquema (Pydantic) con una pasada de reparación automática (re-prompt con el error de validación) antes de entrar al dominio. En apps tradicionales el input del usuario es el enemigo; en apps LLM, *la respuesta del modelo* lo es. JSON mode no garantiza cumplimiento de esquema, solo formato.
123
+
124
+ La reparación tiene **presupuesto**: máximo 2 intentos y un cap de costo por mensaje. Sin tope, el re-prompt es una quemadora de dinero en casos patológicos. El efecto secundario es una métrica gratis: la **tasa de reparación por prompt** (qué fracción de primeras respuestas no validó). Ese número es señal de calidad de prompt y alimenta directamente a `prompt-registry`. *Trade-off:* latencia extra en reparación; menor que el costo de un objeto inválido propagado.
125
+
126
+ **D6 — `prompt-registry`: fuente única de verdad, en fases.** "Mejoré el prompt y funciona mejor" no es ingeniería: es superstición. Un prompt solo es mejorable si tiene un dataset que lo acorrala. Y la semana 48 (marketplace) solo es posible si los prompts ya eran ciudadanos de primera clase. El registry no nace completo — si intentara nacer entero en la semana 2, sería un paquete sin usuarios — así que evoluciona.
127
+
128
+ ### D6.1 — `prompt-registry` evoluciona en tres fases
129
+
130
+ 1. **Fase 1 — Convención (semanas 2–12).** Los prompts viven en `/prompts` dentro de cada consumidor, con frontmatter mínimo obligatorio:
131
+
132
+ ```yaml
133
+ id: commit-message-generator
134
+ version: 0.3.0
135
+ owner: llm-dev-core
136
+ model_family: gpt-4o-mini
137
+ schema: CommitMessage
138
+ eval: evals/commit-message-generator.jsonl
139
+ status: experimental
140
+ ```
141
+
142
+ 2. **Fase 2 — Paquete mínimo (semanas 13–34).** `prompt-registry` se extrae como paquete del core: carga por ID, valida metadatos, impide prompts inline y pinning de versiones. La métrica "100% en `prompt-registry`" se cuenta como *metadatos desde sem. 2* y como *manejado por el paquete desde sem. 13* — no es una contradicción, es la fase en la que vive el sistema.
143
+
144
+ 3. **Fase 3 — Registry formal (semana 35+).** Incorpora datasets de eval, regresión, métricas de calidad, historial de cambios y preparación para marketplace.
145
+
146
+ **Regla desde la semana 5:** todo prompt *nuevo* tiene ≥1 eval asociado (exigido por CI). Los prompts de los consumidores 2–4 (anteriores a `test-kit`) se retrofitean en la semana 6 como **retrofit documentado #0** — deuda reconocida, no heredada.
147
+
148
+ Dos problemas que la Fase 3 hereda y que se anticipan desde el diseño de `test-kit`: (a) cuando un prompt lo comparten 3 consumidores, su dataset tiene un dueño declarado — o la fase 35 reescribe el formato; (b) dataset congelado + modelo que deriva = re-baselining como operación documentada (runbook), porque si no, cada actualización del proveedor parece una regresión propia.
149
+
150
+ *Trade-off:* fricción al añadir un prompt (hay que escribir sus evals) — es la fricción correcta, está donde debe estar.
151
+
152
+ **D7 — `test-kit`: en desarrollo LLM, los tests son datasets (sem. 5).** La semana 5 no genera "tests unitarios": construye el formato de dataset (input → salida esperada → criterio de evaluación) que todos usarán. El comportamiento LLM tiene distribuciones, no assertions; sin evals versionados, ningún cambio de prompt o modelo es seguro, y este año es un refactor continuo. Los evals son el suelo.
153
+
154
+ Dos capas por encima del formato:
155
+
156
+ - **¿Quién evalúa al evaluador?** Un LLM-as-judge con bias sistemático (respuestas largas = mejor score) convergería los prompts hacia verbosidad. Hay una capa de calibración: un dataset pequeño (~20 casos) etiquetado **a mano** sirve de ground truth contra el que se calibra al evaluador. Sin eso, se mide consistencia, no calidad.
157
+ - **Determinismo por record/replay.** Los evals corren sobre cassettes; no golpean APIs vivas en CI. El evaluador LLM no es la única puerta de merge: convive con aserciones deterministas sobre los runs grabados (D4).
158
+
159
+ `eval-smoke` (diario, barato) y `eval-full` (releases, cambios de prompt o modelo) se definen en §8.1. *Trade-off:* ruido y costo del evaluador; se mitiga con umbrales sobre datasets congelados y presupuestos.
160
+
161
+ **D8 — Compat check: el core se prueba contra sus consumidores (desde v1.0).** El CI corre la suite completa contra consumidores en cada merge a `main`. El riesgo real no es romper el core: es romper a quienes lo usan. Es la respuesta honesta al acoplamiento de D1, y convierte "uso GitHub Actions" en "diseño de sistemas".
162
+
163
+ "Los últimos 5" son los menos representativos: los repos en riesgo de romperse son los de pins más viejos, que nunca entrarían al check. La muestra **se estratifica: 3 consumidores recientes + 2 con pins antiguos**. Los secrets se resuelven con los cassettes de D4 (record/replay), nunca con API keys vivas en el CI. La política y el protocolo de degradación están en §6.1. *Trade-off:* CI más lento; es el precio de tener usuarios.
164
+
165
+ **D9 — Regla 70/30 de superficie nueva.** Ninguna semana de composición acepta más de un concepto nuevo; el resto es composición del core. El calendario solo es realista si la novedad es acotada: el SaaS de contratos (sem. 40) cabe en una semana no por pequeño, sino porque auth, validación, generación, PDF y costos ya existen; lo nuevo es la composición multi-tenant. Romper esta regla es romper el calendario. Las semanas-seed están fuera de esta vara (ver §2.1).
166
+
167
+ **D10 — Hitos de consolidación en semanas 13, 26, 39 y 52.** Cada 13 semanas no hay proyecto nuevo: hay extracción, versionado y documentación del core. Sin hitos forzados, el core se pudre en "ya lo extraeré luego". La 13 extrae lo que 12 consumidores ya necesitaron; la 26 lo endurece con la auditoría de seguridad; la 39 lo instrumenta; la 52 decide si merece v4.0 o es el cierre. *Trade-off:* 4 semanas sin demo nueva. El core es el producto; las demos son su marketing.
168
+
169
+ **D11 — Stack único: Python 3.12+ (además, la ADR-0).** El core tiene un único runtime principal; mantener dos lenguajes en el core significa duplicar todo: cliente, validación, evals, CI, packaging, errores y documentación. *Decisión:* Python 3.12+, Pydantic (validación), FastAPI (APIs), pytest (tests), uv (tooling) y logging estructurado (observabilidad). Pydantic es la implementación natural de "la salida del LLM es input no confiable". Los consumidores pueden tener frontends u otros componentes en otras tecnologías, pero toda lógica LLM, validación, prompts, evals y observabilidad pasan por el core en el stack oficial. ADR expandido en `docs/decisions/0000-stack.md`. *Trade-off aceptado:* quien quiera usar el core desde otro runtime queda fuera; es el precio de no mantener dos ecosistemas. Es la decisión más barata del documento y la única con deadline inmediato: bloquea la semana 2.
170
+
171
+ **D12 — Regla del tercer uso.** "Que se compongan en el consumidor" es correcto al comienzo e incorrecto cuando el patrón se repite. Si `agent-loop` y `vector-core` se componen en consumidores 22, 25 y 31 con el mismo patrón, eso no es composición: es un módulo faltante. La regla: en el **tercer uso** del mismo patrón, el patrón es candidato a módulo propio. Ni antes (se extrae una hipótesis), ni después (se acepta deuda como diseño).
172
+
173
+ ## 5. Estructura
174
+
175
+ ```
176
+ llm-dev-core/
177
+ ├── packages/
178
+ │ ├── llm-client/ # C1 [seed sem. 2]
179
+ │ ├── schema-validate/ # C2 [seed sem. 3]
180
+ │ ├── secure-base/ # C3 [seed sem. 4]
181
+ │ ├── test-kit/ # C4 [seed sem. 5]
182
+ │ ├── web-api-base/ # C5 [seed sem. 6]
183
+ │ ├── ci-pack/ # C6 [seed sem. 7]
184
+ │ ├── cache-ratelimit/ # C7 [seed sem. 8]
185
+ │ ├── bot-base/ # C8 [seed sem. 9]
186
+ │ ├── parser-io/ # C9 [seed sem. 10]
187
+ │ ├── docs-gen/ # C10 [seed sem. 12]
188
+ │ ├── vector-core/ # C11 [seed sem. 16]
189
+ │ ├── diff-engine/ # C12 [seed sem. 19]
190
+ │ ├── agent-loop/ # C13 [seed sem. 21]
191
+ │ ├── auth-base/ # C14 [seed sem. 25]
192
+ │ ├── gh-app/ # C15 [seed sem. 27]
193
+ │ ├── scraper/ # C16 [seed sem. 28]
194
+ │ ├── prompt-registry/ # C17 [seed sem. 35]
195
+ │ └── cost-obs/ # C18 [seed sem. 39]
196
+ ├── scripts/
197
+ │ ├── check_consumers.py # invariantes ejecutables: manifiestos y prompts (`make validate`)
198
+ │ ├── collect_metrics.py # página de evidencia (§10.1)
199
+ │ └── compat_check.sh # workflow compat-last-5
200
+ ├── templates/
201
+ │ ├── consumer-readme.md
202
+ │ ├── core-consumer.yml
203
+ │ ├── prompt.md
204
+ │ ├── eval-dataset.jsonl
205
+ │ └── retrofit-issue.md
206
+ ├── docs/
207
+ │ ├── decisions/ # un ADR por decisión (0000-stack, 0001-D1, …)
208
+ │ ├── metrics/ # datos crudos de la página de evidencia
209
+ │ └── runbooks/ # degradación, re-baseline, expulsión de módulo
210
+ ├── ARCHITECTURE.md
211
+ └── CHANGELOG.md # desde v1.0 (sem. 13)
212
+ ```
213
+
214
+ **Reglas de dependencia:** `llm-client` es la hoja (no depende de nadie) y expone la reparación con presupuesto; `schema-validate` se compone con ella **solo vía el hook `validator`** (nunca importada por el cliente — ver ADR-2). `prompt-registry` y `test-kit` dependen de `llm-client`, nunca al revés. `agent-loop` no depende de `vector-core` — que se compongan en el consumidor hasta que la regla del tercer uso (D12) diga lo contrario. `cost-obs` lee los spans de `llm-client`; ningún módulo depende de `cost-obs`.
215
+
216
+ La semana 2 no empieza desde cero: clona la plantilla de consumidor (`templates/`), no un repo vacío.
217
+
218
+ ### 5.1 Semana mínima viable
219
+
220
+ 52 semanas es ambicioso, y la presión del calendario produce exactamente lo que este documento prohíbe: el corte silencioso. Si una semana no alcanza para el proyecto completo, se entrega un **consumidor mínimo válido**:
221
+
222
+ - repo público,
223
+ - dependencia versionada de `llm-dev-core`,
224
+ - una funcionalidad demostrable,
225
+ - un prompt versionado con frontmatter,
226
+ - un eval básico,
227
+ - README con "Recicla de",
228
+ - issue de excedente para la semana siguiente.
229
+
230
+ Esto preserva la cadena de acumulación y evita el abandono silencioso. **Máximo 3 semanas así en el año** — no es una puerta de escape, es la elasticidad que evita que una semana mala quiebre el sistema. Una 4.ª semana mínima se marca como anomalía, no como excepción.
231
+
232
+ ## 6. Ciclo de vida de un módulo
233
+
234
+ ```
235
+ experimental (en un consumidor) → propuesto (PR al core) → seed (0.x)
236
+ → consolidado (≥2 consumidores) → estable (v1.0+, garantías semver)
237
+ → mejorado (minor) → deprecado (major, con migración documentada)
238
+ ```
239
+
240
+ **Criterio de admisión:** un módulo es admisible en el core si su razón de existir es *alimentar o consumir el pipeline LLM*. Es lo que mantiene al scraper dentro (scrapear → extraer → validar es pipeline LLM) y a un CSV-helper fuera. Sin esta frontera, la semana 28 inicia el deslizamiento hacia el cementerio de utilidades que este documento teme.
241
+
242
+ Un módulo seed sin segundo consumidor en 13 semanas se revisa a fondo o se expulsa: el core no es un cementerio de utilidades que solo usé una vez. La expulsión es un runbook, no una tragedia: se depreca con migración documentada, como cualquier breaking change.
243
+
244
+ ### 6.1 Política de compatibilidad
245
+
246
+ El core existe para ser usado, y usarlo implica proteger a sus consumidores.
247
+
248
+ - Cada consumidor declara la versión mayor de `llm-dev-core` que usa (en `core-consumer.yml`).
249
+ - El CI del core ejecuta un compat check contra **5 consumidores no deprecados: 3 recientes + 2 con pins antiguos**.
250
+ - Un merge a `main` no puede romper a esos 5 consumidores.
251
+ - Si un consumidor queda obsoleto, se marca como deprecado con issue y fecha.
252
+ - Todo breaking change requiere:
253
+ - entrada de changelog,
254
+ - guía de migración,
255
+ - al menos un consumidor migrado como referencia.
256
+ - Versiones soportadas: la major actual recibe features, fixes y mejoras; la major anterior, solo parches críticos durante un ciclo limitado.
257
+
258
+ **Protocolo de degradación** (¿qué pasa si `llm-client` v2.3 rompe a los consumidores 30–35?):
259
+
260
+ ```
261
+ merge a main → compat-last-5 → FAIL
262
+ 1. revert del merge: el core nunca queda roto en `main`
263
+ 2. hotfix branch del defecto; los consumidores pineados siguen en su versión
264
+ 3. si el fix es breaking para la major, se envía como minor con guía de migración
265
+ ```
266
+
267
+ D8 detecta el problema; este protocolo es la respuesta. Un consumidor congelado en v2.2 sigue funcionando mientras su versión esté en soporte — ese es el valor de los pins, y lo que evita que la cadena dependa del último `main` en cada minuto.
268
+
269
+ ## 7. Definition of Done
270
+
271
+ Hay dos DoD, una por tipo de semana. Una semana no puede ser las dos cosas: si extraes un módulo, tu superficie nueva es el módulo, no el 30% de un proyecto.
272
+
273
+ ### DoD — semana-seed (nace un módulo del core)
274
+
275
+ - [ ] El módulo nace dentro de un consumidor real (nunca como repositorio de utilidades).
276
+ - [ ] Contrato público escrito: API, schema de span, ejemplo de uso.
277
+ - [ ] Primera versión publicada en el registry público.
278
+ - [ ] Un consumidor real lo usa (el de la propia semana, por defecto).
279
+ - [ ] Changelog entry, y ADR si cambió una decisión.
280
+
281
+ ### DoD — semana de composición (consumidor regular)
282
+
283
+ - [ ] Declara `llm-dev-core` como dependencia versionada (nunca código copiado).
284
+ - [ ] `core-consumer.yml` presente con `reused_modules`, `new_surface` y `estimated_new_surface`.
285
+ - [ ] 100% de llamadas LLM por `llm-client` (lint anti-imports en verde); 100% de prompts con frontmatter en `/prompts` (lint en verde).
286
+ - [ ] README con: problema, demo, arquitectura, **"Recicla de"**, limitaciones, roadmap.
287
+ - [ ] Salida LLM validada por `schema-validate`.
288
+ - [ ] ≥ 1 eval del prompt principal en CI (vía `test-kit`); job `eval-smoke` en verde.
289
+ - [ ] Si tocó el core: PR con changelog entry.
290
+ - [ ] Si el core mejoró después: issue de retrofit documentado (label `retrofit`).
291
+
292
+ ### DoD — común a ambas
293
+
294
+ - [ ] Sin datos sensibles sin redacción en prompts (§3.1).
295
+ - [ ] Lints de invariantes (§3) en verde.
296
+ - [ ] Si la semana no llega: aplicar §5.1, nunca cortar en silencio.
297
+
298
+ ## 8. Riesgos y mitigaciones
299
+
300
+ | Riesgo | Mitigación |
301
+ |---|---|
302
+ | Romper el core rompe N repos | Compat check estratificado (D8) + semver + pins + protocolo de degradación (§6.1) |
303
+ | Repos delgados percibidos como repetitivos | "Recicla de" visible + demos distintas |
304
+ | Reutilizar = copiar-pegar disfrazado | Prohibido copiar: solo importar el paquete (lo hace cumplir el lint anti-imports) |
305
+ | Desbordamiento semanal corrompe el calendario | Regla 5: excedente al core, nunca corte silencioso |
306
+ | Semana que no existe (vida, enfermedad, entrevista real) | Semana = secuencia, no fecha; §5.1 con máximo 3 usos al año |
307
+ | Prompts que "funcionan" sin evals | CI falla si un prompt de producción no tiene dataset (desde sem. 5) |
308
+ | Evals caros, lentos o flaky | §8.1: presupuestos, smoke vs full, datasets congelados, record/replay |
309
+ | Bias del evaluador LLM (consistencia ≠ calidad) | Calibración sobre ground truth etiquetado a mano (D7) |
310
+ | El mantenedor es un único punto de fallo | ADRs, runbooks, changelog y página de evidencia: el conocimiento vive en el repo, no en mi cabeza |
311
+ | Drift del proveedor (modelos deprecados, pricing) | Los proveedores se abstraen detrás de `llm-client`; el span registra modelo y costo reales |
312
+ | El año termina siendo 52 demos | Las métricas de §2.1 y la página de evidencia son la vara; si falla, este archivo se reescribe con lo aprendido |
313
+
314
+ ### 8.1 Costo y evals
315
+
316
+ Los evals son infraestructura, no un lujo. Pero cuestan dinero y tiempo, así que se administran.
317
+
318
+ Un eval no es un test que dice "el LLM respondió bonito". Un eval es un **dataset con criterio de éxito, costo conocido y umbral de regresión**.
319
+
320
+ - Cada consumidor declara un presupuesto máximo de evals en `core-consumer.yml`.
321
+ - El CI diario corre un `eval-smoke`: dataset pequeño, costo acotado, rápido, umbral claro.
322
+ - El `eval-full` corre en: releases del core, cambios de prompt, cambios de modelo, o semanalmente.
323
+ - Los cambios de prompt corren evals on-change; el resto de la semana depende del smoke. El evaluador LLM no es la única puerta de merge.
324
+ - Ningún cambio de modelo se acepta sin una corrida de regresión.
325
+ - Los datasets de eval se versionan igual que el código; los umbrales corren sobre datasets congelados.
326
+ - Re-baselining de un dataset es una operación documentada (runbook), no un ajuste silencioso.
327
+ - Se permite cacheo de respuestas solo cuando el eval es determinístico o está explícitamente marcado como no sensible a variabilidad.
328
+ - El costo de evals corre por record/replay (D4): en CI no se gastan llamadas en lo que ya está grabado.
329
+
330
+ La regla es simple: si no puedes pagar el eval de forma repetible, el eval está mal dimensionado.
331
+
332
+ ## 9. Qué NO es este proyecto
333
+
334
+ - **No es un framework universal.** Publicado, pero opiniado y egoísta: sirve a mis 52 proyectos primero.
335
+ - **No es "production-ready" por decreto.** La estabilidad se gana por uso (D3), no por etiqueta.
336
+ - **No persigue cobertura de features.** Persigue un historial de decisiones defendibles. Diez módulos con argumentación honesta valen más que treinta con marketing.
337
+ - **No es un cementerio de utilidades.** Nada entra sin un consumidor que lo avale (§6) y nada queda sin un segundo consumidor que lo justifique.
338
+ - **No es una demo de prompts.** Es una demo de *dirección*: el LLM genera código bajo estas restricciones, y el sistema se sostiene porque las restricciones son buenas.
339
+
340
+ ## 10. Estado actual
341
+
342
+ - **Versión:** v0.3
343
+ - **Módulos existentes:** `packages/llm-client` v0.1.0 (seed sem. 2) — `complete`/`stream`, retry+backoff+jitter, reparación con tope, cap de costo, cache, span de 20 campos, record/replay nativo, transportes `opencode` (sesión local sin API key) y `openai-compatible` (HTTP). `packages/schema-validate` v0.1.0 (seed sem. 3) — validación Pydantic de la salida LLM (ADR-2): JSON con/sin caretas, errores campo a campo, registro `SchemaId → modelo`, `parsed` en `ValidationResult`; compone con `llm-client` vía el hook `validator`.
344
+ - **Consumidores:** `Proyectos/commit-cli` (sem. 2) — CLI que propone mensajes de commit desde `git diff`; `Proyectos/release-scribe` (sem. 3) — release notes JSON validadas por `schema-validate` desde `git log`. Ambos dependen de la dist unificada `llm-dev-core` (editable). La métrica de reutilización real se medirá en §10.1 desde el corte de semanal.
345
+ - **Empaquetado:** una sola dist `llm-dev-core`, publicable vía `uv build`/`uv publish` con `llm_client` y `schema_validate` top-level (D1). Pendiente la primera publicación real en PyPI (se cierra en la sem. 3 con v0.3).
346
+ - **Enforcement:** ADR-0, ADR-1 (`llm-client-contract`) y ADR-2 (`schema-validate-contract`) en `docs/decisions/`, plantillas en `templates/`, `make validate` (estructura + tests) y `make validate-consumer CONSUMER=../<repo>`.
347
+ - **Pasos por semana según el plan:** se mantiene el calendario semanal (estructura C1–C18 de §5, armonizada en la sem. 3): `schema-validate` (sem. 3), `secure-base` (sem. 4), `test-kit` (sem. 5), `web-api-base` (sem. 6), `ci-pack` (sem. 7), `cache-ratelimit` (sem. 8), `bot-base` (sem. 9), `parser-io` (sem. 10), `docs-gen` (sem. 12), ... hasta `cost-obs` (sem. 39).
348
+ - **Próximo hito:** v1.0 en la semana 13, con 12 consumidores reales detrás.
349
+
350
+ ### 10.1 La página de evidencia
351
+
352
+ El entregable del año no son 52 repos: es un **URL**. Un dashboard auto-generado por `ci-pack` (`scripts/collect_metrics.py`) en cada merge, que convierte las promesas de §2 en datos:
353
+
354
+ - % de reutilización por semana (desde los `core-consumer.yml`),
355
+ - costo por proyecto y acumulado (desde los spans de `llm-client`),
356
+ - cobertura de evals (prompts con dataset / prompts totales),
357
+ - matriz repo × versión-de-core,
358
+ - retrofittings cerrados vs. reportados.
359
+
360
+ Las afirmaciones de un README son promesas; los gráficos de ese URL son evidencia. Un entrevistador que quiera ver cómo se mantiene esto no necesita creerme en palabra: necesita un enlace.
361
+
362
+ ---
363
+
364
+ *"La abstracción que solo se usa una vez es una hipótesis; la que se usa treinta, un diseño."*
365
+
366
+ ---
367
+
368
+ Notas finales sobre el documento: las decisiones (D1–D12, con D11 como ADR-0) están redactadas en formato de ADR comprimido — cada una se expande en `docs/decisions/` con fecha y estado (aceptada/superseded), el formato que los entrevistadores staff reconocen al instante. Los puntos flacos que señaló la revisión (verificación, políticas, protocolos de fallo, stack, seguridad) están resueltos como capas de enforcement dentro del documento: §2.1, §3, §3.1, §5.1, §6.1, §8.1 y la página de evidencia (§10.1). Y la frase del cierre, además de epígrafe, es la descripción del repo en GitHub.
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ La versión es la del paquete `llm-dev-core`. El historial comienza con **v1.0 (semana 13)** — hasta entonces el core es `0.x` y puede romperse sin aviso (D3).
4
+
5
+ ## v0.3 — 2026-09-21 (schema-validate seed + dist unificada)
6
+
7
+ - Semana 3: nace `packages/schema-validate` v0.1.0 dentro de un consumidor real (`release-scribe`).
8
+ - Contrato ADR-2: validación Pydantic de la salida LLM (JSON con/sin caretas, errores campo a campo),
9
+ registro `SchemaId → modelo`, `parsed` poblando `CompletionResult.parsed`.
10
+ - Reparación: el lazo/presupuesto/cap sigue en `llm-client` (hoja); `schema-validate` alimenta los
11
+ errores vía el hook `validator`. Ajuste menor ADR-1: `ValidationResult.parsed`.
12
+ - Tape real grabado de `opencode run` sin API key (replica validada con `parsed`).
13
+ - **Empaquetado unificado (D1):** el core pasa a publicarse como una sola dist `llm-dev-core` (0.3.0)
14
+ con `llm_client` y `schema_validate` top-level; los consumidores dependen de `llm-dev-core ^0.3`, nunca
15
+ de módulos sueltos. `commit-cli` migra a la dist unificada.
16
+ - Segundo consumidor: `Proyectos/release-scribe` (sem. 3) — release notes JSON validadas desde `git log`;
17
+ reutiliza `llm-client` + `schema-validate`.
18
+ - Calendario armonizado (§5 vs. §10): `schema-validate` (3), `secure-base` (4), `test-kit` (5),
19
+ `web-api-base` (6), `ci-pack` (7), `cache-ratelimit` (8), `bot-base` (9), … hasta `cost-obs` (39).
20
+ Deuda aprobada en el review: la sem-2 quería publicar ya en PyPI; al unificar el empaquetado, la primera
21
+ publicación real pasa a ser esta v0.3.
22
+
23
+ ## v0.2 — 2026-09-20 (llm-client seed)
24
+
25
+ - Semana 2: nace `packages/llm-client` v0.1.0 dentro de un consumidor real (`commit-cli`).
26
+ - Contrato ADR-1 aceptada: `complete`/`stream`, retry+backoff+jitter, reparación con tope, cap de costo, cache, span de 20 campos.
27
+ - Record/replay nativo: cassettes sin API key (`ReplayProvider`); tape real grabado con el transport `opencode` (sesión local, sin key).
28
+ - Transportes: `opencode` (`opencode run --format json`) y `openai-compatible` (HTTP).
29
+ - Workspace uv activado (`members = ["packages/*"]`); grupo dev de pytest. `make validate` ahora también corre los tests.
30
+ - Primer consumidor: `Proyectos/commit-cli` (único módulo del core reutilizado a la fecha: `llm-client`). La métrica de reutilización real se medirá en §10.1 desde el corte de semanal.
31
+
32
+ ## v0.1 — 2026-09-20 (seed)
33
+
34
+ - Semana 1: documento `ARCHITECTURE.md`, ADR-0 (stack Python 3.12+), contrato mínimo de `llm-client`, plantillas de consumidor.
35
+ - Enforcement base (§3): `scripts/check_consumers.py` implementado (modo `--self` y de consumidor), `make validate` y workflow `ci.yml`. Pendiente hasta sem. 6: lint anti-imports, evals en CI y página de evidencia (ci-pack).
@@ -0,0 +1,14 @@
1
+ PY := uv run python
2
+ PYTEST := uv run pytest
3
+ CHECK := scripts/check_consumers.py
4
+
5
+ .PHONY: validate validate-consumer
6
+
7
+ validate: ## Invariantes del core: estructura, plantillas, ADR-0, sintaxis
8
+ bash -n scripts/compat_check.sh
9
+ $(PY) $(CHECK) --self
10
+ $(PYTEST) -q
11
+
12
+ validate-consumer: ## Invariantes de un repo consumidor: make validate-consumer CONSUMER=../mi-repo
13
+ @test -n "$(CONSUMER)" || { echo "uso: make validate-consumer CONSUMER=<ruta>"; exit 2; }
14
+ $(PY) $(CHECK) $(CONSUMER)
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.5
2
+ Name: llm-dev-core
3
+ Version: 0.3.0
4
+ Summary: Núcleo versionado del que dependen 52 proyectos en 52 semanas: un solo sistema acumulativo.
5
+ Author-email: binahco <binahco.sas@gmail.com>
6
+ Requires-Python: >=3.12
7
+ Requires-Dist: httpx>=0.27
8
+ Requires-Dist: pydantic>=2.7
9
+ Requires-Dist: pyyaml<7,>=6.0
@@ -0,0 +1,20 @@
1
+ # llm-dev-core
2
+
3
+ > La abstracción que solo se usa una vez es una hipótesis; la que se usa treinta, un diseño.
4
+
5
+ Núcleo versionado y publicado del que dependen 52 proyectos en 52 semanas: un solo sistema acumulativo, no 52 demos desconectadas.
6
+
7
+ Toda llamada a un LLM pasa por `llm-client`, toda salida no confiable pasa por `schema-validate`, todo prompt vive en `prompt-registry`. Ver `ARCHITECTURE.md` para la tesis, las decisiones (D1–D12) y las métricas verificables.
8
+
9
+ - **Estado:** v0.3 · Semana 3
10
+ - **Stack:** Python 3.12+ (ADR-0, `docs/decisions/0000-stack.md`)
11
+ - **Estructura:** `packages/` (módulos), `templates/` (consumidor clonable), `scripts/` (verificación), `docs/`
12
+ - **Empaquetado:** una sola dist `llm-dev-core` (D1): `llm_client` y `schema_validate` top-level; los consumidores dependen de `llm-dev-core ^0.x`, nunca de módulos sueltos.
13
+
14
+ ## Desarrollo
15
+
16
+ ```bash
17
+ uv sync # instala el workspace
18
+ make validate # invariantes: estructura, plantillas, ADR-0, sintaxis
19
+ uv run pytest # tests (aún ninguno hasta sem. 2)
20
+ ```
@@ -0,0 +1,30 @@
1
+ # ADR-0 — Stack único: Python 3.12+
2
+
3
+ - **Fecha:** 2026-09-20
4
+ - **Estado:** aceptada
5
+ - **Origen:** D11 en `ARCHITECTURE.md` (la ADR-0 del proyecto)
6
+
7
+ ## Contexto
8
+
9
+ El core tendrá módulos de cliente LLM, validación de esquemas, evals, CI, observabilidad y más. Mantener dos runtimes principales en el core significa duplicar cada pieza: cliente, validación, evals, packaging, errores y documentación. El bilingüismo accidental es una deuda que el proyecto no necesita.
10
+
11
+ El ecosistema LLM tiene su centro de gravedad en Python: las SDKs de los proveedores, las librerías de observabilidad y la práctica de evals están maduras en él. Pydantic modela con precisión el invariante central del proyecto (la salida de un LLM es input no confiable), y el tooling para publicar un paquete versionado en PyPI es trivial.
12
+
13
+ ## Decisión
14
+
15
+ El core se construye sobre un único runtime principal:
16
+
17
+ - **Python 3.12+**
18
+ - **Pydantic** para validación (incl. `schema-validate`)
19
+ - **FastAPI** para APIs (`web-api-base`)
20
+ - **pytest** para tests (`test-kit`)
21
+ - **uv** para tooling y publicación
22
+ - **Logging estructurado** (JSON) para observabilidad (`cost-obs`)
23
+
24
+ Los consumidores pueden tener frontends u otros componentes en cualquier otra tecnología. Toda lógica LLM, validación, prompts, evals y observabilidad pasa por el core en el stack oficial.
25
+
26
+ ## Consecuencias
27
+
28
+ - **Positivas:** una sola cultura de código, una sola guía de estilo, evals y CI compartidos sin traducción.
29
+ - **Negativas:** quien quiera usar el core desde otro runtime (TS, etc.) queda fuera. Es el precio de no mantener dos ecosistemas; se acepta.
30
+ - El contrato de `llm-client` (ADR-1) y las plantillas se escriben ya en este stack.