Guía para contribuidores y desarrolladores que quieran trabajar en licit.
Requisitos previos
- Python 3.12+ (obligatorio; el proyecto usa
StrEnumy otras features de 3.12) - Git (para tests de detección de proyecto)
- pip (viene con Python)
Verifica tu versión:
python3.12 --version
# Python 3.12.x
Setup del entorno
# Clonar el repositorio
git clone https://github.com/Diego303/licit-cli.git
cd licit-cli
# Instalar en modo desarrollo con dependencias de dev
python3.12 -m pip install -e ".[dev]"
# Verificar la instalación
licit --version
# licit, version 0.1.0
Dependencias de desarrollo
| Paquete | Versión | Propósito |
|---|---|---|
| pytest | 8.0+ | Framework de testing |
| pytest-cov | 5.0+ | Cobertura de código |
| ruff | 0.4+ | Linter y formatter |
| mypy | 1.9+ | Type checking estricto |
Comandos de desarrollo
# Tests
python3.12 -m pytest tests/ -q # Ejecutar todos los tests
python3.12 -m pytest tests/ -q -x # Parar en el primer fallo
python3.12 -m pytest tests/test_cli.py -q # Solo tests de CLI
python3.12 -m pytest tests/ -q --tb=short # Tracebacks cortos
python3.12 -m pytest tests/ --cov=licit # Con cobertura
# Linting
python3.12 -m ruff check src/licit/ # Verificar errores
python3.12 -m ruff check src/licit/ --fix # Auto-corregir
# Type checking
python3.12 -m mypy src/licit/ --strict # Verificar tipos (modo estricto)
# CLI
python3.12 -m licit --help # Ayuda general
python3.12 -m licit init # Probar init
python3.12 -m licit status # Probar status
Estructura del código
src/licit/
├── __init__.py # __version__ = "0.1.0"
├── __main__.py # Entry point: python -m licit
├── py.typed # PEP 561 marker
├── cli.py # Todos los comandos Click
├── config/
│ ├── schema.py # Modelos Pydantic v2 (LicitConfig, etc.)
│ ├── loader.py # load_config(), save_config()
│ └── defaults.py # DEFAULTS, CONFIG_FILENAME, DATA_DIR
├── core/
│ ├── models.py # Enums + dataclasses de dominio
│ ├── project.py # ProjectDetector
│ └── evidence.py # EvidenceCollector + EvidenceBundle
├── logging/
│ └── setup.py # setup_logging(verbose)
├── provenance/ # Fase 2
├── changelog/ # Fase 3
├── frameworks/ # Fases 4-5
├── connectors/ # Fase 7
└── reports/ # Fase 6
Convenciones de código
1. Pydantic solo para configuración
# Correcto — config usa Pydantic
class ProvenanceConfig(BaseModel):
enabled: bool = True
# Correcto — dominio usa dataclasses
@dataclass
class ProvenanceRecord:
file_path: str
source: str
2. StrEnum para enums
# Correcto — Python 3.12+
class ComplianceStatus(StrEnum):
COMPLIANT = "compliant"
# Incorrecto — ruff UP042
class ComplianceStatus(str, Enum):
COMPLIANT = "compliant"
3. Protocoles para interfaces
# Correcto — typing.Protocol
class Evaluator(Protocol):
def evaluate(self, evidence: EvidenceBundle) -> list[ControlResult]: ...
# Incorrecto — ABC
class Evaluator(ABC):
@abstractmethod
def evaluate(self, evidence: EvidenceBundle) -> list[ControlResult]: ...
4. structlog para logging
import structlog
logger = structlog.get_logger()
# Correcto — eventos + datos estructurados
logger.info("config_loaded", path=str(config_path), framework="eu-ai-act")
# Incorrecto — mensajes de texto libre
logger.info(f"Config loaded from {config_path} for framework eu-ai-act")
5. Lazy imports para módulos futuros
Cuando un comando necesita un módulo que aún no existe:
@main.command()
def trace() -> None:
"""Track code provenance."""
try:
from licit.provenance.tracker import ( # type: ignore[import-not-found]
ProvenanceTracker,
)
except ImportError:
click.echo("Provenance tracking not yet implemented.")
raise SystemExit(1)
6. Ruff y mypy
- ruff con reglas:
E(errores),F(f-strings),I(imports),UP(upgrades) - mypy en modo
--strict - Línea máxima: 100 caracteres
- Target: Python 3.12
Testing
Estructura de tests
tests/
├── conftest.py # Fixtures compartidos
├── test_cli.py # 13 tests
├── test_config/
│ ├── test_schema.py # 7 tests
│ └── test_loader.py # 9 tests
└── test_core/
├── test_project.py # 12 tests
└── test_evidence.py # 11 tests
Fixtures disponibles (conftest.py)
# Proyecto temporal con pyproject.toml
def tmp_project(tmp_path) -> Path: ...
# Proyecto temporal con git inicializado
def git_project(tmp_path) -> Path: ...
# Factory de ProjectContext
def make_context(root_dir, name, languages, ...) -> ProjectContext: ...
# Factory de EvidenceBundle
def make_evidence(has_provenance, has_fria, ...) -> EvidenceBundle: ...
Supresión de logs en tests
Los tests configuran structlog a nivel CRITICAL para evitar ruido:
# tests/conftest.py
structlog.configure(
wrapper_class=structlog.make_filtering_bound_logger(logging.CRITICAL),
cache_logger_on_first_use=False,
)
Escribir un nuevo test
# tests/test_core/test_mi_modulo.py
def test_mi_feature(tmp_project: Path) -> None:
"""Descripción clara de qué se testea."""
# Arrange
(tmp_project / "CLAUDE.md").write_text("# Agent config")
# Act
resultado = mi_funcion(str(tmp_project))
# Assert
assert resultado.valor_esperado == "algo"
Tests con Click CLI
from click.testing import CliRunner
from licit.cli import main
def test_mi_comando(tmp_path: Path) -> None:
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=tmp_path):
result = runner.invoke(main, ["mi-comando", "--flag"])
assert result.exit_code == 0
assert "texto esperado" in result.output
Añadir un nuevo comando CLI
- Define el comando en
src/licit/cli.py:
@main.command()
@click.pass_context
def mi_comando(ctx: click.Context) -> None:
"""Descripción del comando."""
config = ctx.obj["config"]
# ... implementación ...
click.echo("Done.")
- Añade tests en
tests/test_cli.py:
def test_mi_comando(tmp_path: Path) -> None:
runner = CliRunner()
with runner.isolated_filesystem(temp_dir=tmp_path):
result = runner.invoke(main, ["mi-comando"])
assert result.exit_code == 0
- Verifica:
python3.12 -m pytest tests/test_cli.py -q
python3.12 -m ruff check src/licit/cli.py
python3.12 -m mypy src/licit/cli.py --strict
Añadir un nuevo modelo de configuración
- Define el modelo en
src/licit/config/schema.py:
class MiConfig(BaseModel):
enabled: bool = True
mi_campo: str = "default"
- Añádelo a
LicitConfig:
class LicitConfig(BaseModel):
# ... campos existentes ...
mi_config: MiConfig = Field(default_factory=MiConfig)
- Añade tests en
tests/test_config/test_schema.py.
Flujo de trabajo recomendado
1. Crear branch feature
git checkout -b feat/mi-feature
2. Implementar
- Código en src/licit/
- Tests en tests/
3. Verificar
python3.12 -m pytest tests/ -q # 52+ tests passing
python3.12 -m ruff check src/licit/ # All checks passed
python3.12 -m mypy src/licit/ --strict # No issues found
4. Commit y PR
git add src/licit/ tests/
git commit -m "feat: mi nueva feature"
Fases de implementación pendientes
| Fase | Módulos a implementar | Directorio |
|---|---|---|
| 2 | git_analyzer.py, heuristics.py, store.py, tracker.py, attestation.py, session_readers/ | provenance/ |
| 3 | watcher.py, differ.py, classifier.py, renderer.py | changelog/ |
| 4 | requirements.py, evaluator.py, fria.py, annex_iv.py, templates/ | frameworks/eu_ai_act/ |
| 5 | requirements.py, evaluator.py, templates/ | frameworks/owasp_agentic/ |
| 6 | unified.py, gap_analyzer.py, markdown.py, json_fmt.py, html.py | reports/ |
| 7 | base.py, architect.py, vigil.py | connectors/ |
Cada fase tiene su sección detallada en el plan de implementación.