Skip to content
Conduit

mcp server

ccdd-gate

Gates deterministas + orquestador para mantener honesto el codigo escrito por IA. MCP + task-contracts (CCDD).

MauricioPerera/ccdd-gatecommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "ccdd-gate": {
      "command": "uvx",
      "args": [
        "ccdd-gate"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 27, 2026

ccdd-gate

PyPI tests License: MIT

🌐 Landing page — un recorrido visual del método (toggle EN/ES/PT).

Gates deterministas que mantienen honesto al código escrito por IA. Un modelo pequeño implementa; un árbitro que no se puede sobornar decide si pasó.

No es otro loop "dale al modelo grande hasta que diga que está listo". La condición de parada no es el juicio del modelo —es un veredicto determinista: complejidad ≤ budget firmado + property-tests congelados que el implementador no puede ablandar. Mismo input, mismo veredicto, corrida a corrida.

El mismo principio cubre dos pilares:

  • Código (gate de complejidad/tests): verifica que la función implementada respeta el budget y pasa los property-tests congelados. 100% determinista.
  • Comportamiento de agentes (pilar de evals): para la salida NO determinista de un agente (texto/JSON), un dataset congelado + checks deterministas (schema, contención, citas/groundedness anti-alucinación, PII, trayectoria) deciden PASS/FAIL sin LLM (Tier 1); un juez LLM acotado y auditado contra un golden set es Tier 2 opt-in. Ver Pilar de evals.

Construido sobre CCDD (incluido aquí como ccdd.py, MIT). El sustrato determinista no llama a ningún LLM: el cerebro es el agente anfitrión (Claude Code, Cursor, etc.) que invoca estas herramientas.

Instalación (PyPI)

ccdd-gate está publicado en PyPI — esta es la vía recomendada para usuarios (sin clonar, sin rutas absolutas):

pip install ccdd-gate
# o: pipx install ccdd-gate · uvx ccdd-gate

Quedan disponibles los CLIs ccdd-lint, ccdd-gate, ccdd-measure y el servidor MCP ccdd-mcp (todos en el PATH).

Registrar el MCP en un cliente (Claude Code / Desktop)

{ "mcpServers": { "ccdd": { "command": "ccdd-mcp" } } }

El modo desde el repo (python runners/<x>.py, pip install -e .) sigue funcionando para desarrollo — ver Quickstart e Instalación como paquete.


La idea en una frase

El modelo grande planifica y audita; el modelo pequeño (local/barato) implementa; un gate determinista decide. El grande solo entra a autorar la tarea, auditar los tests y rescatar cuando el chico se atasca. La inteligencia difusa va en el LLM; el control y el veredicto van en código que no se puede engañar.

Qué hay acá

PiezaQué haceLLM
runners/metrics_backends.pyCapa neutral compartida (umbrales + severity + lint_results) y registro get_backend(language|extension) para backends por lenguajeno
runners/metrics.pyBackend Python: métricas de complejidad por AST (ciclomática, anidamiento, params, longitud)no
runners/metrics_treesitter.pyBackend universal vía tree-sitter (TS/TSX/JS/Rust/Go/Java/C#/PHP/Ruby/Kotlin/C/Swift/C++) — dep opcional; sin ella, solo Pythonno
runners/pre_complexity_runner.pyOrquestador L3 para el contrato pre-complexity-agentno
runners/pre_complexity_helpers.pyData-helpers para inyectar contexto de diseño y negociono
runners/complexity_runner.pyOrquestador L3 para el contrato complexity-agentno
runners/complexity_gate.pyGate determinista; CLI o hook PostToolUse de Claude Codeno
runners/tc_lint.pyLinter del task-contract (anti-desvarío del autor)no
runners/task_gate.pyVeredicto unificado: tc_lint + tests congelados + complejidad≤budget + anotaciones + firma implementada (gate-signature) + deps opt-in (gate-deps) + aprobación; kind:group compone hijas + test de integraciónno
runners/deps_check.pyunauthorized_imports: imports top-level de terceros NO permitidos (núcleo del enforcement de deps_allowed / anti-slopsquatting). AST purono
runners/sig_check.pysignature_mismatch: la firma IMPLEMENTADA vs la del contrato (nombre + nombres de params en orden); caza el drift de firma. AST purono
runners/purity_check.pyimpure_operations: operaciones impuras en el cuerpo (print/open/eval/exec/import/input, global/nonlocal, import interno). AST purono
runners/mutdef_check.pymutable_defaults: params con default mutable ([]/{}/set()/list()/dict()). AST purono
runners/bareexcept_check.pybare_except_lines: líneas de except: desnudo (sin tipo). AST purono
runners/assert_check.pyassert_lines: líneas de assert (desaparecen con python -O). AST purono
runners/nonecmp_check.pynone_eq_lines: comparaciones con None por ==/!= (en vez de is/is not). AST purono
runners/coverage_check.pyfunction_lines: líneas del cuerpo que la ejecución debería cubrir (primitivo, sin etapa). AST purono
runners/rules_gate.pyAplica los checks deterministas project-wide por glob desde un rules.yaml (idea declarativa estilo autorules, árbitro AST sin LLM)no
runners/linter_gate.pyEnvuelve linters externos deterministas como checks opt-in desde un linters.yaml (hermano de rules_gate, pero el veredicto lo emite un lexterno pineado, no AST propio); hoy solo adaptador ruff (dep opcional, no del paquete)no
runners/audit_composition.pyAuditor project-wide: composición sin gatear (función importa a otra sin kind:group); distingue deuda de FORMA vs de COMPORTAMIENTOno
runners/audit_orphan_targets.pyAuditor project-wide: .py de implementación que no son target de ningún contrato (código fuera del flujo gate); exime datos purosno
runners/audit_annotations.pyAuditor project-wide: nombres en anotaciones sin importar/definir; caza bugs de portabilidad que lazy annotations (PEP 649) enmascarano
runners/mutation_audit.pyMide la fuerza del oráculo vía mutation testing determinista (mutaciones fijas → corre los tests congelados por mutante); superviviente = test débilno
runners/approve_tests.pyFirma humana de los tests (tests_sha256), a prueba de manipulaciónno
runners/orchestrator.pyLoop stateless: pequeño implementa → tests fallan/complejidad falla → reintenta (sin memoria) → escala al grandesí (worker)
runners/test_audit.pyAuditoría advisory de los tests contra el contratosí (advisory)
runners/measure.pyHarness de medición: tokens/intentos/escalados, costo vs loop grandeno
runners/complexity_mcp.pyServidor MCP (stdio JSON-RPC) que expone el sustratono
runners/mcp_smoke.pySmoke test del MCPno
runners/guardrails_lang.yamlGuardrails específicos por lenguajeno
runners/eval_gate.pyPilar de evals — Tier 1: veredicto determinista sobre output NO determinista (agentes). Dataset congelado + checks (schema, contención, citas/groundedness, PII, trayectoria)no
runners/eval_checks.pyCheckers deterministas Tier 1 (una función por check; anti-alucinación de fuentes y evaluación de trayectoria)no
runners/approve_eval_cases.pyFirma humana del dataset de evals (cases_sha256), a prueba de manipulaciónno
runners/eval_judge.pyTier 2 (opt-in): juez LLM acotado (modelo pinneado, temp 0) — el único módulo del pilar que llama a un LLMsí (opt-in)
runners/judge_audit.pyFuerza/deriva del juez: acuerdo vs golden set humano (análogo a mutation_audit); si baja del umbral, falla el JUEZsí (opt-in)
contracts/Rubrics firmados: pre-complexity-agent, complexity-agent, task-author-agent, eval-agent (juez Tier 2)—

Quickstart

pip install -r requirements.txt

# 1) Gate determinista sobre un task-contract de ejemplo (PASS)
python runners/task_gate.py examples/sandbox/task.md

# 2) Tests congelados (deterministas, sin LLM): gate + gobernanza L2 (Ed25519) + reglas tc_lint
python -m unittest tests.test_gates tests.test_l2_governance tests.test_tc_lint_rules -v

# 3) Linter de un task-contract
python runners/tc_lint.py examples/sandbox/task.md

Como hook de auto-validación en Claude Code

settings.json → hooks.PostToolUse con matcher Write|Edit:

{ "command": "python runners/complexity_gate.py" }

Cada archivo que el agente escribe se mide con el backend de su lenguaje (por extensión, o --language en CLI); si una métrica entra en CRÍTICA (umbral firmado), el hook bloquea y pide refactor. Determinista, sin tokens. Una extensión sin backend registrado es un no-op anunciado (aviso por stderr, exit 0), nunca un fallo silencioso. Python siempre; el resto de lenguajes requiere la dep opcional tree-sitter (ver «Conformancia multi-lenguaje»).

Instalación como paquete (console scripts)

Para usar ccdd-gate sin clonar ni rutas absolutas, instálalo como distribución (deja los scripts ccdd-mcp, ccdd-lint, ccdd-gate, ccdd-measure en el PATH):

pipx install ccdd-gate        # o: uvx ccdd-gate · o: pip install ccdd-gate

El modo desde el repo (python runners/<x>.py) sigue funcionando para desarrollo. Nota: los datos de gobernanza (rubric/attestations bajo contracts/, schema en la raíz) se resuelven en el layout del repo / instalación editable (pip install -e .); el núcleo determinista (medir/lintar/gate/MCP) funciona en cualquier modo.

Como MCP

Con el paquete instalado, el .mcp.json no necesita clonar el repo ni cwd:

{ "mcpServers": { "ccdd": { "command": "ccdd-mcp" } } }

Desde el repo, copiá .mcp.json.example a .mcp.json. Tools (sin LLM):

  • measure_complexity(code) — métricas AST reales por función.
  • complexity_rubric(agent) — el criterio gobernado (system/policies/thresholds) firmado.
  • scan_guardrails(code, agent?, language?, filename?) — guardrails deterministas: secretos (texto-puro, igual en todo lenguaje), anidamiento (estructural, vía el backend del lenguaje), dsv_check (anti-alucinación por "drift", exige coincidencia exacta con HEAD local) y específicos por lenguaje opt-in (runners/guardrails_lang.yaml, p. ej. no-eval). agent evalúa contra ese contrato. Sin language, Python.
  • lint_task_contract(contract_text, test_code?) - valida un task-contract (anti-desvarío del modelo grande).
  • scan_dependencies(code, deps_allowed?, local_roots?) - imports top-level de terceros NO permitidos (enforcement de deps_allowed / anti-slopsquatting). Determinista, sin LLM. local_roots (lista de dirs, opcional) exime los imports que resuelvan a un módulo/paquete local bajo alguno de esos roots (mismo mecanismo que el gate 4); sin el campo, ningún módulo local se exime.
  • check_signature(source, fn_name, expected_signature, language?) - "" si la firma implementada coincide con la esperada (nombre + nombres de params en orden), o el desajuste. Python usa AST nativo; otros lenguajes usan tree-sitter si la gramática está disponible (TS/TSX/JS/Rust/Go/Java/C#/PHP/Ruby/Kotlin/C/Swift/C++; Kotlin y C degradan a aridad genérica en la firma, ver "Conformancia multi-lenguaje"). Default language: python. Determinista, sin LLM.
  • check_purity(source, fn_name, target_line?) - operaciones impuras del cuerpo (gate-purity). Sin LLM.
  • check_mutable_defaults(source, fn_name, target_line?) - params con default mutable (gate-mutdef). Sin LLM.
  • check_bare_except(source, fn_name, target_line?) - líneas de except: desnudo (gate-bareexcept). Sin LLM.
  • check_asserts(source, fn_name, target_line?) - líneas de assert (gate-assert). Sin LLM.
  • check_none_cmp(source, fn_name, target_line?) - comparaciones con None por ==/!= (gate-nonecmp). Sin LLM.
  • run_rules_gate(rules_path?, root?) - aplica los checks deterministas project-wide por glob desde un rules.yaml. Sin LLM.
  • run_linter_gate(linters_path?, root?) - envuelve linters externos deterministas como checks opt-in desde un linters.yaml (lista de {tool, version, files?, args?, required?}); version pineada, sin LLM. Hoy solo adaptador ruff (dep opcional, no del paquete).
  • run_integration_gate(task_path) - veredicto PASS/FAIL unificado de un contrato YA EN DISCO (lint + aprobación de tests + tests congelados + complejidad ≤ budget), idéntico a la CLI task_gate.py. Para kind:group compone las hijas + el test de integración sobre los archivos reales (sin sandbox). El agente NO implementa: delega a run_ephemeral_agent.
  • run_ephemeral_agent(task_path) - delega la implementación al modelo pequeño local y la valida contra el gate. El servidor fija modelo y endpoint; el LLM anfitrión solo pasa task_path (no elige el modelo). El operador puede elegir el modelo por entorno (CCDD_EXECUTOR_MODEL, CCDD_EXECUTOR_API) sin tocar la fuente; el LLM no. Default: qwen3-coder:480b-cloud vía Ollama (http://localhost:11434/v1).
  • audit_composition(root?) - composición sin gatear project-wide; separa deuda de FORMA (composición ejercitada por el test del composer) de deuda de COMPORTAMIENTO (mock o test ausente). ok = sin deuda de comportamiento.
  • audit_orphan_targets(root?) - .py de implementación que no son target de ningún contrato (exime tests/__init__/datos puros). Para proyectos 100% CCDD.
  • audit_annotations(root?) - nombres usados en anotaciones sin importar/definir, sobre todos los targets; caza bugs de portabilidad que Python 3.14 (lazy annotations) enmascara en runtime.
  • mutation_audit(task_path) - fuerza del oráculo vía mutation testing determinista; un mutante sobreviviente delata un test débil. Opt-in (corre los tests por mutante).
  • request_human_attestation(code, reason) - permite al agente pedir una excepción firmada cuando no puede reducir la complejidad por reglas de negocio.
  • run_eval_gate(eval_path) - pilar de evals (Tier 1): veredicto determinista sobre el output NO determinista de un agente (dataset congelado + checks). Sin LLM.
  • eval_rubric() - rúbrica firmada del juez Tier 2 (contrato eval-agent).
  • judge_audit(eval_path, provider?) - acuerdo del juez Tier 2 vs golden set humano; si baja del umbral, el juez no es de fiar. Provider stub (determinista) por defecto.

Checklist de cierre (antes de dar una tarea por terminada): corré las cuatro auditorías —audit_composition, audit_orphan_targets, audit_annotations y mutation_audit— hasta ok:true. El gate de función no cubre composición, código huérfano, anotaciones ni la fuerza del oráculo; quedarse con la auditoría que da verde y declarar "todo en verde" es el modo de falla que la checklist existe para cerrar (y que el CI hace no-opcional).

Pilar de evals: gatear output NO determinista (Tier 1 + Tier 2 opt-in)

El gate de complejidad/tests verifica código (funciones con oráculo independiente). Pero un agente de producción (un bot de soporte, un asistente de research) produce texto/JSON no determinista que ese gate no cubre. El pilar de evals lo cierra sin renunciar al determinismo donde se puede, en dos niveles:

  • Tier 1 — checks deterministas, sin LLM (eval_gate.py + eval_checks.py). Sobre cada caso de un dataset CONGELADO y firmado (cases_sha256, igual que tests_sha256): schema del output, contención/ausencia de términos, must_cite + groundedness (toda cita apunta a una fuente existente → anti-alucinación), PII, y evaluación de trayectoria (tools requeridas/prohibidas, max_steps). Veredicto = función del budget: pass_rate ≥ pass_rate_min y violaciones duras ≤ forbidden_violations_max. Mismo input → mismo veredicto. Muchos agentes (extracción, clasificación, routing) se gatean 100% aquí.

  • Tier 2 — juez LLM ACOTADO, opt-in (eval_judge.py + judge_audit.py). Solo para lo genuinamente subjetivo (coherencia, utilidad). El modelo se pinnea (temperature 0) y su veredicto no cuenta hasta pasar judge_audit: el juez se calibra contra un golden set atestado por humano y debe alcanzar agreement ≥ agreement_min. Es a la calidad lo que mutation_audit es al oráculo: no confía en el juez, lo mide. La única relajación de determinismo (el score por-corrida del LLM) queda acotada y auditada; cualquier deriva del modelo pinneado la caza judge_audit en CI.

# Firmar el dataset (OK humano, congela los casos)
python runners/approve_eval_cases.py examples/eval/support-bot-refunds/eval.md

# Veredicto Tier 1 (sin LLM, reproducible)
python runners/eval_gate.py examples/eval/support-bot-refunds/eval.md

# Calibrar el juez Tier 2 contra el golden set (offline con provider stub)
python runners/judge_audit.py examples/eval/support-bot-refunds/eval.md

El eval-contract (front-matter YAML + cuerpo, espeja al task-contract) declara target, agent_entry, dataset, budget, deterministic_checks y el bloque judge (opt-in). Ver examples/eval/support-bot-refunds/ para el ejemplo end-to-end (agente determinista de juguete, dataset firmado, schema y rúbrica).

El loop grande/pequeño (Stateless Feedback y Evolución CEFL)

El loop es stateless (sin estado) pero incorpora mecánicas evolutivas inspiradas en CEFL (Candidate Expansion and Freezing):

  1. Expansión Paralela: El orquestador no pide 1 intento; pide N candidatos paralelos (--candidates 3).
  2. Torneo de Complejidad (Freezing): Cada candidato se aísla y se valida contra los tests y el budget de complejidad. Si varios pasan, el Gate elige automáticamente el que tenga la menor puntuación matemática de complejidad, congelándolo como la respuesta definitiva.
  3. Feedback Masivo (Partial Success): Si todos los candidatos fallan, el sistema no reenvía código roto uno por uno. Agrupa las N soluciones fallidas junto a sus errores (stack traces) en un JSON combinado masivo. Esto permite al modelo en la siguiente iteración aprender cruzando los fracasos de todas sus rutas exploratorias, resolviendo problemas que un solo reintento jamás lograría.
# OFFLINE (sin modelo): stub que entrega 1 impl rota y 1 buena -> intento 1 FAIL, intento 2 PASS
python runners/orchestrator.py examples/sandbox/loop_demo/task.md \
  --provider stub \
  --stub examples/sandbox/loop_demo/_stub_bad.py \
  --stub examples/sandbox/loop_demo/_stub_good.py --max-attempts 3

# CON MODELOS: el pequeño genera 3 vías paralelas; si todas fallan, escala al grande
python runners/orchestrator.py examples/sandbox/loop_demo/task.md \
  --provider openai --model <modelo-chico> --candidates 3 \
  --escalate-provider ollama --escalate-model <modelo-grande> --escalate-attempts 2

Providers: ollama, openai (LM Studio/vLLM, urllib stdlib), anthropic (SDK), y stub (secuencia offline para probar la mecánica sin modelo).

task-contract (formato)

Front-matter YAML (machine-checkable) + cuerpo Markdown (prescriptivo). Ver examples/sandbox/task.md y el rubric de autoría en contracts/task-author-agent/. Regla central: especifica el contrato y los property-tests con oráculo independiente, NO el algoritmo. Los tests se congelan y firman antes de que el implementador toque la tarea.

Campo target_line (opcional, desambiguación). El gate de complejidad resuelve la función objetivo por nombre. Si el target tiene varias funciones/métodos homónimos (p. ej. set en varias clases), declara target_line: N (la línea de la def correcta) para que mida esa y no otra. Sin desambiguador y con >1 def del nombre, el gate devuelve INVALID (ambiguo) en vez de medir la última en silencio (issue #41). Con un solo match el campo es innecesario (comportamiento idéntico).

Campo enforce_deps (opcional, anti-slopsquatting). Si enforce_deps: true, el gate corre la etapa gate-deps: flaggea los imports top-level del target que no estén en deps_allowed (ni en la stdlib) y falla con stage: gate-deps. Opt-in (default off): los contratos que no lo declaran no cambian. Los módulos locales del propio proyecto se eximen automáticamente: el gate pasa como raíces de búsqueda locales el directorio del contrato y el del target, así que un import top-level que resuelva a <dir>/m.py o <dir>/m/__init__.py bajo alguno de esos dirs no se flaggea (no hace falta listarlo en deps_allowed). Los imports de tercero no listados siguen flaggeándose normalmente. Limitación actual: la exención solo mira el top-level de esos dos dirs (no recursiva ni configurable a otros dirs del repo); y sigue siendo solo Python.

Conformidad de firma (etapa gate-signature, default-on). El gate compara la firma IMPLEMENTADA con la signature del contrato (nombre + nombres de parámetros en orden; ignora anotaciones, defaults y retorno) y falla con stage: gate-signature si difieren — caza el drift de firma que rompe a los callers. Es default-on: no requiere campo. Solo Python (compara vía AST).

Gates de antipatrones opt-in (default-off, Python, vía AST). Cada uno corre solo si el contrato declara su campo; sin él, el contrato no cambia. Todos fallan con su propio stage:

CampoEtapaFalla si la función…
pure: truegate-puritytiene operaciones impuras (print/open/eval/exec/__import__/input, global/nonlocal, import interno)
forbid_mutable_defaults: truegate-mutdeftiene params con default mutable ([]/{}/set()/list()/dict())
forbid_bare_except: truegate-bareexcepttiene un except: desnudo (sin tipo)
forbid_assert: truegate-assertusa assert (desaparece con python -O)
forbid_none_eq: truegate-nonecmpcompara con None usando ==/!= en vez de is/is not

Cada uno honra target_line para funciones homónimas y tiene su tool MCP (check_purity, check_mutable_defaults, check_bare_except, check_asserts, check_none_cmp).

Reglas project-wide por glob (rules_gate). Los gates de antipatrón de arriba se disparan por contrato. Para aplicarlos a TODO el repo sin contratos, runners/rules_gate.py lee un rules.yaml declarativo (lista de {check, files}) y corre el check determinista sobre cada función de los archivos que matchean el glob — política de repo, no solo por función gateada. La idea del formato declarativo + glob está tomada de autorules, pero el árbitro es AST determinista, no un LLM (autorules usa un juez LLM; ccdd-gate no). Ver examples/rules.yaml.example y la evaluación completa en docs/evaluations/autorules.md.

python runners/rules_gate.py rules.yaml [root]   # exit 0 ok · 1 violaciones · 2 config inválida

Linters externos deterministas (linter_gate). Hermano de rules_gate pero el veredicto lo emite un linter externo invocado como subproceso con salida machine-readable, no un AST propio: el gate no reimplementa reglas, delega en la herramienta y solo normaliza su salida a findings. Opt-in desde un linters.yaml declarativo (lista de {tool, version, files?, args?, required?}). La salida de un linter depende de su versión, por eso version es pin exacto obligatorio: versión instalada != pin → entorno inválido (exit 2, no es PASS). Tool ausente + required:false → skip anunciado + exit 0 (precedente tree-sitter del repo); required:true → exit 2. Findings → exit 1; limpio → exit 0. HOY solo hay adaptador ruff (el registro queda listo para clippy/eslint/golangci-lint sin implementarlos); ruff NO es dependencia del paquete — es una dep opcional que el operador instala y pinea. Ver examples/linters.yaml.example.

python runners/linter_gate.py linters.yaml [root]   # exit 0 limpio · 1 findings · 2 config/entorno inválido

Dogfooding: el propio repo se gatea con su linters.yaml de raíz (ruff==0.15.20, required: true, excluye fixtures/+examples/+ccdd.py, ignora E731/E402) — paso bloqueante del CI (.github/workflows/test.yml).

Metodología KDD

El repo se desarrolla con KDD (Knowledge-Driven Development): modela el contexto con Open Knowledge Format (OKF) y gobierna el desarrollo con CCDD (task-contracts + gates deterministas). El conocimiento vive en knowledge/ (nodos OKF: OKF-SPEC.md, architecture/, concepts/, contracts/) y las reglas del ciclo de vida en .agents/AGENTS.md + la skill .agents/skills/kdd-okf-ccdd-hybrid/ (puente en AGENTS.md).

Un task-contract híbrido es un nodo OKF (type: 'Task Contract') con frontmatter CCDD (task, intent, target, signature, budget, tests, test_command, deps_allowed, forbids) + 7 secciones canónicas. Ejemplo real y validado: knowledge/contracts/kdd-sample-slugify.md (slugify pura en src/kdd_sample/slugify.py).

Se valida en dos niveles, ambos deterministas (sin LLM):

# Nivel 1 — frontmatter + secciones + examples; y el test_command del contrato en verde.
python scripts/validate_contracts.py knowledge/contracts

# Nivel 2 nativo — el gate CCDD real de este repo (lint del contrato: forma + semántica).
python runners/tc_lint.py knowledge/contracts/kdd-sample-slugify.md   # -> "ok": true

El paso de Nivel 1 corre en CI (.github/workflows/test.yml, bloqueante). Plantilla y spec del formato híbrido: https://github.com/MauricioPerera/KDD.

Campo language (opcional, multi-lenguaje). Por defecto python. Con language: python la firma se valida con el AST nativo (preciso). Para otros lenguajes (typescript, javascript, go, …) tc_lint valida la firma por aridad genérica (cuenta de parámetros top-level y extracción de nombre, respetando ()[]{}<> y comillas) y emite el warning tc-signature-generic para señalar que no hay parser nativo. params_max y el resto de reglas se aplican igual. Sin el campo, el comportamiento es idéntico al actual (Python).

Conformancia multi-lenguaje

fixtures/conformance/ define un oráculo congelado de las 4 métricas (fixtures equivalentes por lenguaje + valores esperados). Todo backend debe reproducirlo: Python es el baseline y los backends tree-sitter (TS/TSX/JS/Rust/Go/Java/C#/PHP/Ruby/Kotlin/C/Swift/C++) pasan la suite con métricas estructurales idénticas (cyclomatic/nesting_depth/parameter_count); solo function_length diverge por formato y se fija por-lenguaje. Un backend nuevo no se acepta hasta pasar tests/test_conformance.py. Ver fixtures/conformance/README.md.

Multi-lenguaje hoy (métricas de complejidad): Python nativo (sin deps) + TS/TSX/JS/Rust/Go/Java/C#/PHP/Ruby/Kotlin/C/Swift/C++ vía tree-sitter (dep opcional: pip install tree_sitter tree_sitter_typescript tree_sitter_rust tree_sitter_go tree_sitter_java tree_sitter_c_sharp tree_sitter_php tree_sitter_ruby tree_sitter_kotlin tree_sitter_c tree_sitter_swift tree_sitter_cpp). El gate, el hook y measure_complexity miden esas extensiones igual que .py cuando las gramáticas están instaladas; si no, esos archivos son no-op anunciado. Los checks de antipatrones (gate-mutdef, gate-assert, gate-signature, gate-deps, etc.) siguen siendo Python-only (AST nativo).

Integración GitHub (opcional, integrations/github/)

Capa adaptadora opcional: el sustrato determinista no depende de GitHub. Sin ella, todo funciona en local. Usa gh CLI; tokens por entorno, nunca en el repo.

  • reporter.py — toma el JSON de task_gate/complexity_gate y genera un comentario Markdown determinista (PASS/FAIL, métricas vs budget, motivo). Idempotente: actualiza un comentario "marcado" en vez de spamear. Offline imprime el Markdown; --post lo publica vía gh.
  • ci_gate.py + .github/workflows/ccdd-gate.yml — GitHub Action: en cada PR descubre los task-contracts afectados (el .md o su target), corre tc_lint + task_gate y bloquea el merge (exit 1) si el veredicto no pasa; publica el resumen como comentario idempotente vía el Reporter. Además corre no-opcionalmente audit_composition y audit_annotations project-wide y mutation_audit sobre los contratos afectados: composición sin gatear, anotaciones sin importar o mutantes sobrevivientes también ponen el check en rojo (lo que el autor tiende a saltar cuando es opt-in, acá no se puede saltar). Sin LLM, sin secretos (usa el GH_TOKEN del runner). Copiable a un repo consumidor: copia .github/workflows/ccdd-gate.yml e integrations/github/ (o vendoriza/instala ccdd-gate) y activa branch protection sobre el check ccdd-gate.
  • scaffold.py — genera el esqueleto de un task-contract desde un issue (--issue owner/repo#N o --from-json offline). Captura la intención (título/cuerpo/labels) con placeholders TODO; el resultado es incompleto a propósito (tc_lint lo marca, no falsamente verde).
  • lifecycle.py — conecta el orquestador con el ciclo de vida del issue: un issue ccdd:ready con gate verde abre un PR enlazado (Closes #N) y pasa a ccdd:in-review; si escala → ccdd:escalated; si ni el grande pasa → ccdd:needs-split. El loop del orquestador no sabe de GitHub (recibe un callback opcional on_result); sin él, corre igual en local. Transiciones de label deterministas y reversibles.
  • decompose.py — materializa task-contracts atómicos como sub-issues de un issue padre (epic/feature), con vínculo bidireccional y marker idempotente (re-ejecutar no duplica). No decide la descomposición (la decide el autor): solo la materializa.
  • link.py — vínculo bidireccional contrato↔issue: status --contract (estado + labels) o status --issue owner/repo#N (contratos que lo referencian), y sync-labels (refleja el estado como labels ccdd:*, idempotente, sin pisar labels ajenas). El campo issue del front-matter es opcional; tc_lint valida su formato (regla tc-issue-ref) sin romper contratos sin él.

Benchmarks

Ver BENCHMARKS.md. En resumen: el gate determinista cuesta 0 tokens y su lógica es sub-milisegundo (python benchmarks/bench_gate.py). La economía grande/pequeño está medida y honestamente etiquetada como ilustrativa — gana por reuso/volumen, no en el one-shot trivial (ahí es más caro).

Honestidades (léelas antes de creerle a nadie)

  • El ahorro de tokens es condicional, no universal. En una tarea trivial de un tiro, este flujo puede salir más caro que solo llamar al modelo grande. Gana por volumen, reuso y dificultad. Medilo vos con runners/measure.py; no te fíes del titular.
  • El gate es tan fuerte como sus property-tests. Tests laxos → el modelo pequeño pasa basura. La auditoría del modelo grande (test_audit.py) y el oráculo independiente son lo que hace que el veredicto signifique algo.
  • task_gate ejecuta los tests. En local con tus modelos es seguro; para correr código ajeno usá un sandbox aislado (contenedor), no el host.
  • Auditar tests requiere un modelo grande. Un modelo de ~12B como auditor tiende a aprobar tests rotos. Implementar lo hace bien; auditar, no.
  • pre_complexity_runner.py no es ninguno de los "dos pilares". Analiza un documento de diseño (sin AST posible), así que su veredicto (summary.critical > 0) depende enteramente de las signals que devuelve el LLM — a diferencia del resto del sistema, no tiene un mecanismo de calibración tipo judge_audit.py que mida si ese veredicto es de fiar. Tratalo como una señal advisory, no como un gate determinista.

Licencia

MIT © 2026 Mauricio Perera. Incluye ccdd.py y ccdd_context.schema.json del proyecto CCDD (mismo autor, MIT), con una única adaptación para uso standalone: ccdd.py resuelve su schema junto a sí mismo (upstream lo busca en el directorio padre). Publicado as-is, sin garantía ni soporte.

↑↓ navigate openesc close