Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions .github/workflows/sdk-cli-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,28 @@ on:
# scanner filtered to `src/sdk/cli/**` cannot see a change to `src/packages/**`
# or `src/apps/**`, which is most of the code in this repository.
#
# The `push` trigger below keeps its filter on purpose: pushes are not gated by
# The `push` trigger below keeps a filter on purpose: pushes are not gated by
# required checks, so no deadlock is possible there and the CI minutes are worth
# saving.
# saving on docs-only changes. The filter now spans every tree CodeQL scans
# (GT-713) — a push that changes no code still skips the run.
pull_request:
branches: [main, develop]
push:
branches: [main, develop]
paths:
- 'src/sdk/cli/**'
# GT-713: the `CodeQL SAST` job below is the ONLY producer of the CodeQL
# analysis for `refs/heads/main` (category `/language:javascript-typescript`,
# the one the Security tab's alerts are keyed to). A pull-request run is
# diff-informed and does not update the branch's alerts, and the default
# "Code Quality" setup is a different suite. With the filter limited to the
# CLI tree, promotion 19d736da (2026-09-19, changes under src/packages and
# src/apps only) landed on main with no analysis at all: the tab kept
# reporting alerts on code that no longer existed until a manual
# `gh workflow run sdk-cli-ci.yml --ref main`. Every path CodeQL scans
# therefore has to be a path that triggers this workflow on push.
- 'src/packages/**'
- 'src/apps/**'
- '.harness/**'
# package.json / package-lock.json: this pipeline installs with `npm ci`,
# so a lock desync breaks every job here -- and until now neither file
Expand Down
22 changes: 21 additions & 1 deletion README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Y no es solo un linter: **las reglas van atadas a la fase SDLC del producto.** E

Es para equipos que quieren sus decisiones de arquitectura aplicadas en CI y no revisadas a mano, para plataformas que bloquean artefactos no conformes antes de producción, y para agentes de IA que necesitan validar su propia salida contra las mismas reglas.

[Pruébalo](#pruébalo-en-dos-minutos) · [Cuatro términos](#cuatro-términos-que-necesitas) · [En CI](#en-ci) · [Qué hay dentro](#qué-hay-dentro) · [Qué no es](#qué-no-es) · [Documentación](#documentación) · [Atlas interactivo](https://beyondnetcode.github.io/evolith_arch32/)
[Pruébalo](#pruébalo-en-dos-minutos) · [Cuatro términos](#cuatro-términos-que-necesitas) · [En CI](#en-ci) · [Qué hay dentro](#qué-hay-dentro) · [Cómo se compara](#cómo-se-compara) · [Qué no es](#qué-no-es) · [Documentación](#documentación) · [Atlas interactivo](https://beyondnetcode.github.io/evolith_arch32/)

---

Expand Down Expand Up @@ -95,6 +95,26 @@ Cuántas reglas, packs y ADRs carga tu instalación lo imprime `evolith rulesets

---

## Cómo se compara

Las herramientas a las que uno acude primero comprueban cosas distintas, y las diferencias están en las filas, no en los adjetivos. Verificado contra la documentación de cada herramienta el 2026-09-19; las correcciones son bienvenidas como PR.

| | ArchUnit | dependency-cruiser | Conftest | Evolith |
|---|---|---|---|---|
| **Qué lee** | Bytecode de la JVM: clases, paquetes, capas | El grafo de imports de módulos JS/TS | Ficheros de configuración estructurados (YAML, JSON, HCL, Dockerfile…) | El repositorio alrededor del código: layout, workflows, manifiestos, ADRs — **no** el AST |
| **Lenguaje de reglas** | DSL fluido en Java, ejecutado como tests unitarios | Configuración JSON/JS (`forbidden` / `allowed`) | Rego | Rego, compilado a Wasm, en packs JSON |
| **Dónde viven las reglas** | En el código, por repositorio | En el repositorio (`.dependency-cruiser.js`) | Un directorio de políticas; compartible con `conftest pull` (git, OCI) | Una biblioteca fuera de los repositorios, adoptada por repositorio con `--select` |
| **Una regla que no se evaluó** | Falla la regla cuyo `should` recibió un conjunto vacío (`failOnEmptyShould`, activo por defecto) | `severity: ignore` la omite en silencio; no existe otro resultado | Sin resultado: un `deny` indefinido es un aprobado | `skipped` es un veredicto de primera clase, contado junto a `passed` y `failed`; un `skipped` **bloqueante** hace fallar la ejecución |
| **Código de salida** | Un test unitario que falla | El número de violaciones `error` | `1` si falla (`0`/`1`/`2` con `--fail-on-warn`) | `0` pasa · `1` falló la herramienta · `2` el gate bloqueó · `3` invocación inválida |
| **Superficies** | Tests Java | CLI (+ grafos de dependencias) | CLI | CLI · GitHub Action · servidor MCP · API REST |
| **Reglas derivadas de ADRs** | — | — | — | Sí: `evolith adr create`, y muchos packs se derivan de decisiones |
| **Alcance de lenguaje** | JVM | JavaScript / TypeScript | Cualquier fichero estructurado | Cualquier repositorio para reglas estructurales, de CI/CD y de ADR; Node/TypeScript para las de dependencias y linters |
| **Licencia** | Apache-2.0 | MIT | Apache-2.0 | MIT |

Son complementos, no sustitutos: ArchUnit y dependency-cruiser ven *dentro* del código, Conftest ve un fichero cada vez, Evolith ve el repositorio como unidad gobernada y cuenta lo que no pudo decidir. Ejecutar Evolith junto a una de ellas es la configuración prevista.

---

## Qué no es

- **No sustituye a ArchUnit, Conftest ni dependency-cruiser; los complementa.** Las reglas viven fuera del código, como datos que gobiernan muchos repositorios y que un agente puede leer. Evolith *es* OPA por debajo y añade la biblioteca de reglas, la derivación de ADR a regla y la contabilidad de cobertura.
Expand Down
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ And it is not only a linter: **the rules are tied to the product's SDLC phase.**

It is for engineering teams that want their architecture decisions enforced in CI rather than reviewed by hand, for platform teams blocking non-conformant artifacts before production, and for AI agents that need to validate their own output against the same rules.

[Try it](#try-it-in-two-minutes) · [Four terms](#four-terms-you-need) · [In CI](#in-ci) · [What is inside](#what-is-inside) · [What it is not](#what-it-is-not) · [Documentation](#documentation) · [Interactive atlas](https://beyondnetcode.github.io/evolith_arch32/)
[Try it](#try-it-in-two-minutes) · [Four terms](#four-terms-you-need) · [In CI](#in-ci) · [What is inside](#what-is-inside) · [How it compares](#how-it-compares) · [What it is not](#what-it-is-not) · [Documentation](#documentation) · [Interactive atlas](https://beyondnetcode.github.io/evolith_arch32/)

---

Expand Down Expand Up @@ -95,6 +95,26 @@ How many rules, packs and ADRs your installation loads is printed by `evolith ru

---

## How it compares

The tools people reach for first check different things, and the differences are in the rows, not in the adjectives. Verified against each tool's own documentation on 2026-09-19; corrections welcome as a PR.

| | ArchUnit | dependency-cruiser | Conftest | Evolith |
|---|---|---|---|---|
| **What it reads** | JVM bytecode: classes, packages, layers | The JS/TS module import graph | Structured config files (YAML, JSON, HCL, Dockerfile…) | The repository around the code: layout, workflows, manifests, ADRs — **not** the AST |
| **Rule language** | Java fluent DSL, run as unit tests | JSON/JS config (`forbidden` / `allowed`) | Rego | Rego, compiled to Wasm, in JSON packs |
| **Where rules live** | In the code base, per repository | In the repository (`.dependency-cruiser.js`) | A policy directory; shareable with `conftest pull` (git, OCI) | A library outside the repositories, adopted per repository with `--select` |
| **A rule that did not evaluate** | Fails a rule whose `should` got an empty set (`failOnEmptyShould`, on by default) | `severity: ignore` skips it silently; no other outcome exists | No outcome: an undefined `deny` is a pass | `skipped` is a first-class verdict, counted next to `passed` and `failed`; a **blocking** `skipped` fails the run |
| **Exit code** | A failing unit test | The number of `error` violations | `1` on failure (`0`/`1`/`2` with `--fail-on-warn`) | `0` pass · `1` the tool failed · `2` the gate blocked · `3` invalid invocation |
| **Surfaces** | Java tests | CLI (+ dependency graphs) | CLI | CLI · GitHub Action · MCP server · REST API |
| **Rules derived from ADRs** | — | — | — | Yes: `evolith adr create`, and many packs are derived from decisions |
| **Language scope** | JVM | JavaScript / TypeScript | Any structured file | Any repository for structural, CI/CD and ADR rules; Node/TypeScript for the dependency and linter rules |
| **License** | Apache-2.0 | MIT | Apache-2.0 | MIT |

They are complements, not substitutes: ArchUnit and dependency-cruiser see *inside* the code, Conftest sees one file at a time, Evolith sees the repository as a governed unit and counts what it could not decide. Running Evolith next to one of them is the intended setup.

---

## What it is not

- **Not a replacement for ArchUnit, Conftest or dependency-cruiser; it complements them.** Rules live outside the codebase, as data that governs many repositories and that an agent can read. Evolith *is* OPA underneath, and adds the rule library, the ADR-to-rule derivation and the coverage accounting.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10671,6 +10671,51 @@
"node .harness/scripts/ci/09-reconcile-maturity.mjs --check # still green on the same tree; the warning band is empty today",
"node .harness/scripts/ci/40-validate-path-literals.mjs # the workflow's run: body resolves"
]
},
{
"id": "GT-712",
"closedAt": "2026-09-19",
"closureCommit": "cb2c8a1e",
"dependencyDisposition": "none",
"evidence": [
"src/packages/core-domain/src/domain/interfaces.ts",
"src/packages/infra-providers/src/architecture/nx-workspace.strategy.ts",
"src/packages/mcp-server/src/tools/scaffold.tool.ts",
"src/packages/mcp-server/src/tools/scaffold.tool.spec.ts",
"src/apps/core-api/src/application/services/workspace-reference-resolver.service.ts",
"src/apps/core-api/src/presentation/controllers/evaluation.controller.ts",
"src/apps/core-api/src/presentation/controllers/evaluation.controller.spec.ts",
"src/apps/core-api/src/presentation/controllers/architecture.controller.ts",
"src/packages/core-domain/src/application/use-cases/initialize-project.use-case.ts",
"src/packages/core-domain/src/application/services/project-scaffolder.service.ts",
"src/packages/mcp-server/src/tools/config.tools.ts",
"src/packages/mcp-server/src/tools/config.tools.spec.ts"
],
"validationCommands": [
"THE COUNT IS CODEQL'S, NOT OURS: the /language:javascript-typescript analysis of main at c5547114 (sdk-cli-ci run 35445364556, dispatched by hand -- see GT-713) reports 37 results and 0 open alerts; the Scorecard run dispatched the same afternoon (35445777316) closed the 7 Token-Permissions alerts; Dependabot and secret-scanning were already at 0. The 58 dismissals each carry a dismissed_comment naming why (false positive / used in tests / won't fix).",
"THREE COMMITS, ONE CLOSURE: cb2c8a1e (#725) fixed the 37; 8920b140 (#737) rewrote three guards into the shapes CodeQL models (path.resolve + one startsWith; includes('..') + isAbsolute on the same variable; a literal === '__proto__' at the write) after the main run kept 10 open on containment it did not credit; b200c5cb (#748) replaced the .NET scaffolder's second read of input.name with path.basename(projectDir). Promoted in #726, #739, #752.",
"MEASURED, NOT ASSUMED: the former /\\/+$/ on 200 000 slashes took 15 326 ms in node before the change; the char-based trim is O(n). parseRepoUrl was checked against https, .git, git@…: and ssh:// forms (accepted) and evil.example/github.com/…, github.com.evil (rejected).",
"npx jest --config src/packages/mcp-server/jest.config.js --runInBand src/packages/mcp-server/src/tools/scaffold.tool.spec.ts src/packages/mcp-server/src/tools/config.tools.spec.ts # 12 tests: every execFile call is npx/npm with the exact argv; 'api; rm -rf /' and '../../escape' refused before any nx g; __proto__/constructor/prototype refused with the file untouched",
"npx jest --config src/apps/core-api/jest.config.js --runInBand src/apps/core-api/src/presentation/controllers/evaluation.controller.spec.ts src/apps/core-api/src/presentation/controllers/architecture.controller.spec.ts # /etc, ../outside, /workspaces/../etc, /workspacesX/sat, /somewhere/else are 400 before the use case; the manifest's paths are pinned; no resolver fails closed",
"npx jest --config src/packages/core-domain/jest.config.js --runInBand src/packages/core-domain/src/application/use-cases/initialize-project.use-case.spec.ts # ../escape, a/b, .., .hidden, -flag, NUL and '' refused as project names with nothing written; Billing.Api -> Billing.Api.csproj",
"npx tsc -b tsconfig.json # clean on the 11 projects",
"node .harness/scripts/ci/40-validate-path-literals.mjs # the seven workflows still resolve every path they name"
]
},
{
"id": "GT-713",
"closedAt": "2026-09-19",
"closureCommit": "72aceb70",
"dependencyDisposition": "none",
"evidence": [
".github/workflows/sdk-cli-ci.yml"
],
"validationCommands": [
"THE GAP WAS OBSERVED TWICE THE SAME AFTERNOON: promotions 19d736da (12:25) and c5547114 (13:15) reached main with no push run of sdk-cli-ci (their changes were under src/packages and src/apps only), no /language:javascript-typescript analysis for refs/heads/main was recorded for either SHA, and the tab kept reporting alerts on code that was gone; the pull_request run on the same head (PR #740) uploaded to refs/pull/740/merge with results=0 because PR analyses are diff-informed. Both times `gh workflow run sdk-cli-ci.yml --ref main` produced the analysis (runs 35443436054 and 35445364556) and the alerts closed.",
"WHAT IS NOT CLAIMED: that the widened filter fired on a real promotion. The first push to main after this lands that changes src/packages/** or src/apps/** is what confirms it -- a push-event run of 'Evolith SDK CLI - CI Pipeline' on that SHA and a refs/heads/main CodeQL analysis with that commit_sha, without a dispatch. The promotion carrying this change touches the workflow file itself, which was already in the filter, so it proves nothing about the new paths.",
"node -e \"const y=require('yaml');const d=y.parse(require('fs').readFileSync('.github/workflows/sdk-cli-ci.yml','utf8'));const p=d.on.push.paths;if(!p.includes('src/packages/**')||!p.includes('src/apps/**')||d.on.pull_request.paths)process.exit(1);console.log(p.join(' '))\" # push filter spans every tree CodeQL scans; the PR trigger stays unfiltered",
"node .harness/scripts/ci/40-validate-path-literals.mjs # the workflow's paths resolve"
]
}
]
}
Loading
Loading