@imolko/ultra-agent 0.1.0-beta
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.
- package/README.md +93 -0
- package/bin/cli.js +2 -0
- package/dist-node/bin.d.ts +2 -0
- package/dist-node/bin.d.ts.map +1 -0
- package/dist-node/bin.js +5 -0
- package/dist-node/bin.js.map +1 -0
- package/dist-node/cli.d.ts +41 -0
- package/dist-node/cli.d.ts.map +1 -0
- package/dist-node/cli.js +170 -0
- package/dist-node/cli.js.map +1 -0
- package/dist-node/index.d.ts +14 -0
- package/dist-node/index.d.ts.map +1 -0
- package/dist-node/index.js +40 -0
- package/dist-node/index.js.map +1 -0
- package/dist-node/init-lint.d.ts +43 -0
- package/dist-node/init-lint.d.ts.map +1 -0
- package/dist-node/init-lint.js +84 -0
- package/dist-node/init-lint.js.map +1 -0
- package/dist-node/manifest.d.ts +33 -0
- package/dist-node/manifest.d.ts.map +1 -0
- package/dist-node/manifest.js +59 -0
- package/dist-node/manifest.js.map +1 -0
- package/dist-node/materializer.d.ts +52 -0
- package/dist-node/materializer.d.ts.map +1 -0
- package/dist-node/materializer.js +194 -0
- package/dist-node/materializer.js.map +1 -0
- package/dist-node/package-root.d.ts +5 -0
- package/dist-node/package-root.d.ts.map +1 -0
- package/dist-node/package-root.js +17 -0
- package/dist-node/package-root.js.map +1 -0
- package/dist-node/payload.d.ts +22 -0
- package/dist-node/payload.d.ts.map +1 -0
- package/dist-node/payload.js +49 -0
- package/dist-node/payload.js.map +1 -0
- package/dist-node/pointer-hub.d.ts +22 -0
- package/dist-node/pointer-hub.d.ts.map +1 -0
- package/dist-node/pointer-hub.js +50 -0
- package/dist-node/pointer-hub.js.map +1 -0
- package/dist-node/publication.cli.d.ts +9 -0
- package/dist-node/publication.cli.d.ts.map +1 -0
- package/dist-node/publication.cli.js +17 -0
- package/dist-node/publication.cli.js.map +1 -0
- package/dist-node/publication.d.ts +23 -0
- package/dist-node/publication.d.ts.map +1 -0
- package/dist-node/publication.js +47 -0
- package/dist-node/publication.js.map +1 -0
- package/dist-node/reconcile.cli.d.ts +11 -0
- package/dist-node/reconcile.cli.d.ts.map +1 -0
- package/dist-node/reconcile.cli.js +39 -0
- package/dist-node/reconcile.cli.js.map +1 -0
- package/dist-node/reconcile.d.ts +30 -0
- package/dist-node/reconcile.d.ts.map +1 -0
- package/dist-node/reconcile.js +51 -0
- package/dist-node/reconcile.js.map +1 -0
- package/dist-node/ship/prompts/fix.md +22 -0
- package/dist-node/ship/prompts/implement.md +34 -0
- package/dist-node/ship/prompts/review.md +28 -0
- package/dist-node/ship/ship.agent.d.ts +22 -0
- package/dist-node/ship/ship.agent.d.ts.map +1 -0
- package/dist-node/ship/ship.agent.js +142 -0
- package/dist-node/ship/ship.agent.js.map +1 -0
- package/dist-node/ship/ship.args.d.ts +15 -0
- package/dist-node/ship/ship.args.d.ts.map +1 -0
- package/dist-node/ship/ship.args.js +93 -0
- package/dist-node/ship/ship.args.js.map +1 -0
- package/dist-node/ship/ship.context.d.ts +12 -0
- package/dist-node/ship/ship.context.d.ts.map +1 -0
- package/dist-node/ship/ship.context.js +3 -0
- package/dist-node/ship/ship.context.js.map +1 -0
- package/dist-node/ship/ship.d.ts +4 -0
- package/dist-node/ship/ship.d.ts.map +1 -0
- package/dist-node/ship/ship.js +221 -0
- package/dist-node/ship/ship.js.map +1 -0
- package/dist-node/ship/ship.mr.d.ts +9 -0
- package/dist-node/ship/ship.mr.d.ts.map +1 -0
- package/dist-node/ship/ship.mr.js +171 -0
- package/dist-node/ship/ship.mr.js.map +1 -0
- package/dist-node/ship/ship.output.d.ts +9 -0
- package/dist-node/ship/ship.output.d.ts.map +1 -0
- package/dist-node/ship/ship.output.js +21 -0
- package/dist-node/ship/ship.output.js.map +1 -0
- package/dist-node/ship/ship.tracker.d.ts +37 -0
- package/dist-node/ship/ship.tracker.d.ts.map +1 -0
- package/dist-node/ship/ship.tracker.js +220 -0
- package/dist-node/ship/ship.tracker.js.map +1 -0
- package/dist-node/version.cli.d.ts +9 -0
- package/dist-node/version.cli.d.ts.map +1 -0
- package/dist-node/version.cli.js +30 -0
- package/dist-node/version.cli.js.map +1 -0
- package/dist-node/version.d.ts +24 -0
- package/dist-node/version.d.ts.map +1 -0
- package/dist-node/version.js +76 -0
- package/dist-node/version.js.map +1 -0
- package/docs/adr/ADR-0000-proceso-y-formato-de-adrs.md +115 -0
- package/docs/adr/foundational/A-001-clean-architecture-4-capas.md +88 -0
- package/docs/adr/foundational/A-002-cqrs-semantico.md +58 -0
- package/docs/adr/foundational/A-003-tdd.md +97 -0
- package/docs/adr/foundational/A-005-either-return-custom-methods.md +80 -0
- package/docs/adr/foundational/A-006-feature-completion-criteria.md +47 -0
- package/docs/adr/foundational/A-007-data-contract-fail-fast.md +109 -0
- package/docs/adr/foundational/A-008-bounded-context-decomposition.md +130 -0
- package/docs/adr/foundational/A-009-use-case-taxonomy.md +99 -0
- package/docs/adr/foundational/A-010-propiedad-emision-eventos-dominio.md +95 -0
- package/docs/adr/foundational/A-011-characterization-first-refactoring.md +90 -0
- package/docs/adr/foundational/A-012-helm-mecanismo-primario-despliegue.md +67 -0
- package/docs/adr/foundational/A-013-contrato-settings-unica-fuente-configuracion.md +74 -0
- package/docs/adr/foundational/A-014-generacion-artefactos-despliegue-desde-contrato-settings.md +75 -0
- package/docs/adr/foundational/A-015-cloudevents-estandar-eventos.md +85 -0
- package/docs/adr/foundational/A-016-cicd-gitlab-pipeline-raiz-pipelines-modulares.md +92 -0
- package/docs/adr/foundational/A-017-taxonomia-repositorios-librerias-sistemas.md +93 -0
- package/docs/adr/foundational/A-018-practicas-build-imagenes-docker.md +85 -0
- package/docs/adr/foundational/A-019-convenciones-autoria-helm-charts.md +97 -0
- package/docs/adr/foundational/A-020-entorno-desarrollo-estandarizado-devcontainer.md +58 -0
- package/docs/adr/foundational/A-022-formato-reporte-tests-ctrf.md +55 -0
- package/docs/adr/foundational/A-023-composicion-bloques-prompt-agentes.md +65 -0
- package/docs/adr/foundational/A-024-features-slices-verticales-portables.md +41 -0
- package/docs/adr/foundational/A-025-anatomia-mecanica-feature-puertos-adaptadores-drivers.md +104 -0
- package/docs/adr/foundational/A-026-infraestructura-compartida.md +44 -0
- package/docs/adr/foundational/A-027-patron-wiring-entry-point.md +67 -0
- package/docs/adr/foundational/A-029-verbos-intencion-metodos-lado-negocio.md +54 -0
- package/docs/adr/foundational/A-030-global-development-tooling.md +52 -0
- package/docs/adr/foundational/A-031-ubiquitous-language.md +64 -0
- package/docs/adr/foundational/A-032-domain-artifact-documentation-format.md +128 -0
- package/docs/adr/foundational/A-033-estandar-mensajes-commit.md +127 -0
- package/docs/adr/foundational/A-034-canonical-skill-set.md +64 -0
- package/docs/adr/foundational/A-035-captura-ideas-reanclaje-candies.md +51 -0
- package/docs/adr/foundational/A-036-adopcion-wayfinder-ortogonalidad-labels.md +37 -0
- package/docs/adr/foundational/A-037-formalizacion-taxonomia-labels.md +98 -0
- package/docs/adr/foundational/A-038-issues-referencian-skills-nombre.md +54 -0
- package/docs/adr/foundational/A-039-pipeline-ship-issues-hijos-spec.md +64 -0
- package/docs/adr/foundational/A-040-punteros-configuracion-repo-skills.md +44 -0
- package/docs/adr/foundational/A-041-estrategia-de-ramas.md +45 -0
- package/docs/adr/foundational/A-042-formato-documentos-issue-tracker.md +78 -0
- package/docs/adr/foundational/A-043-vocabulario-etiquetas-adrs.md +42 -0
- package/docs/adr/foundational/A-044-composicion-funcional-procesos-operaciones.md +44 -0
- package/docs/adr/foundational/A-045-guardrails-aplicables-no-directrices.md +48 -0
- package/docs/adr/foundational/A-046-metricas-de-forma-y-legibilidad-del-codigo.md +50 -0
- package/docs/adr/foundational/A-047-dependencias-explicitas-sin-globales-ambientales.md +40 -0
- package/docs/adr/foundational/A-048-cobertura-minima-codigo.md +39 -0
- package/docs/adr/foundational/A-049-materializacion-bucle-guardrails-extension-nativa-pi.md +49 -0
- package/docs/adr/foundational/A-050-el-caso-de-uso-pide-al-dominio-no-lee-su-representacion.md +50 -0
- package/docs/adr/foundational/A-051-orden-imports-por-distancia.md +47 -0
- package/docs/adr/foundational/A-052-distribucion-metodologia-paquete-versionado.md +42 -0
- package/docs/adr/foundational/A-053-errores-compartidos-entre-features-dos-hogares-por-capa.md +43 -0
- package/docs/adr/foundational/A-054-contrato-costura-driver-port-raw-data-canal-error.md +65 -0
- package/docs/adr/foundational/index.md +98 -0
- package/docs/adr/index.md +19 -0
- package/docs/adr/python/P-001-dependencias-por-feature-fastapi-depends.md +63 -0
- package/docs/adr/python/P-002-inyeccion-token-por-request-sin-persistirlo.md +35 -0
- package/docs/adr/python/P-003-sqlalchemy-async-connection-pooling.md +59 -0
- package/docs/adr/python/P-004-serializer-json-custom-modelos-pydantic.md +47 -0
- package/docs/adr/python/P-005-patrones-pitfalls-nicegui.md +148 -0
- package/docs/adr/python/P-006-convenciones-pytest.md +96 -0
- package/docs/adr/python/P-007-ci-python-alineacion-poetry-version-unica.md +55 -0
- package/docs/adr/python/P-008-empaquetado-infraestructura-compartida-por-tecnologia.md +38 -0
- package/docs/adr/python/index.md +14 -0
- package/docs/adr/typescript/T-001-ddd-artifact-validation-zod-primitives.md +136 -0
- package/docs/adr/typescript/T-002-test-documentation-jsdoc.md +133 -0
- package/docs/adr/typescript/T-003-readme-formatting-domain-artifacts.md +51 -0
- package/docs/adr/typescript/T-005-input-optionality-use-case-not-entity.md +130 -0
- package/docs/adr/typescript/T-006-small-enums-inlined-zod-regex.md +77 -0
- package/docs/adr/typescript/T-007-controller-input-zod-schema.md +79 -0
- package/docs/adr/typescript/T-008-expressive-use-case-pipe.md +207 -0
- package/docs/adr/typescript/T-009-mdx-brace-escaping.md +80 -0
- package/docs/adr/typescript/T-010-stack-tooling-typescript-dominio.md +53 -0
- package/docs/adr/typescript/T-011-ultra-reporter-herramienta-canonica-documentacion.md +52 -0
- package/docs/adr/typescript/T-012-npm-workspaces-estructura-monorepo-clean-architecture.md +54 -0
- package/docs/adr/typescript/T-013-definicion-eventos-versionados-ultra-ddd.md +45 -0
- package/docs/adr/typescript/T-014-publicacion-distribucion-librerias-npm-semver.md +53 -0
- package/docs/adr/typescript/T-015-method-placement-definition.md +35 -0
- package/docs/adr/typescript/T-016-contrato-settings-zod.md +34 -0
- package/docs/adr/typescript/T-017-characterization-tests-golden-files.md +39 -0
- package/docs/adr/typescript/T-018-procedencia-campos-frontmatter.md +55 -0
- package/docs/adr/typescript/T-019-ultra-generator-herramienta-canonica-generacion.md +35 -0
- package/docs/adr/typescript/T-020-ultra-settings-herramienta-canonica-exportacion-despliegue.md +36 -0
- package/docs/adr/typescript/T-021-pipe-flujo-datos-operadores-semantica-contexto-inmutable.md +105 -0
- package/docs/adr/typescript/T-022-construction-is-execution-single-boundary.md +112 -0
- package/docs/adr/typescript/T-023-registry-cli-declara-dependencias-reales.md +84 -0
- package/docs/adr/typescript/T-024-paquete-ultra-agent-manifiesto-pi-peers-cli.md +39 -0
- package/docs/adr/typescript/T-025-publicacion-npm-trusted-publishing-procedencia.md +59 -0
- package/docs/adr/typescript/index.md +30 -0
- package/eslint/index.mjs +721 -0
- package/package.json +94 -0
- package/pi/extensions/handoff/README.md +92 -0
- package/pi/extensions/handoff/handoff-prompt.txt +9 -0
- package/pi/extensions/handoff/index.ts +296 -0
- package/pi/extensions/handoff/package.json +12 -0
- package/pi/extensions/qa-guardrail/README.md +108 -0
- package/pi/extensions/qa-guardrail/adapters/cli.ts +100 -0
- package/pi/extensions/qa-guardrail/adapters/eslint.ts +90 -0
- package/pi/extensions/qa-guardrail/adapters/registry.ts +45 -0
- package/pi/extensions/qa-guardrail/baseline.ts +74 -0
- package/pi/extensions/qa-guardrail/cerco.ts +134 -0
- package/pi/extensions/qa-guardrail/check.ts +185 -0
- package/pi/extensions/qa-guardrail/config.ts +189 -0
- package/pi/extensions/qa-guardrail/delta.ts +52 -0
- package/pi/extensions/qa-guardrail/feedback.ts +26 -0
- package/pi/extensions/qa-guardrail/index.ts +311 -0
- package/pi/extensions/qa-guardrail/nstop.ts +82 -0
- package/pi/extensions/qa-guardrail/package.json +12 -0
- package/pi/extensions/qa-guardrail/selection.ts +98 -0
- package/pi/extensions/qa-guardrail/types.ts +64 -0
- package/scripts/copy-assets.sh +10 -0
- package/scripts/reconcile-tags.sh +94 -0
- package/scripts/release-beta.sh +64 -0
- package/scripts/release-build.sh +53 -0
- package/scripts/release.sh +51 -0
- package/scripts/verify-adr-consistency.py +217 -0
- package/skills/code-review/SKILL.md +66 -0
- package/skills/codebase-design/SKILL.md +46 -0
- package/skills/configure-repo/SKILL.md +33 -0
- package/skills/diagnose-bug/SKILL.md +63 -0
- package/skills/domain-modeling/SKILL.md +56 -0
- package/skills/grill-me/SKILL.md +34 -0
- package/skills/i-have-an-idea/SKILL.md +166 -0
- package/skills/implement/SKILL.md +22 -0
- package/skills/research/SKILL.md +17 -0
- package/skills/ship/SKILL.md +26 -0
- package/skills/spike/SKILL.md +27 -0
- package/skills/tdd/SKILL.md +68 -0
- package/skills/tdd/references/deep-modules.md +53 -0
- package/skills/tdd/references/interface-design.md +74 -0
- package/skills/tdd/references/mocking.md +80 -0
- package/skills/tdd/references/refactoring.md +49 -0
- package/skills/tdd/references/tests.md +184 -0
- package/skills/to-specs/SKILL.md +141 -0
- package/skills/to-tasks/SKILL.md +143 -0
- package/skills/triage/SKILL.md +73 -0
- package/skills/ultra-generator/SKILL.md +263 -0
- package/skills/ultra-generator/references/aggregate-root.md +149 -0
- package/skills/ultra-generator/references/domain-event.md +174 -0
- package/skills/ultra-generator/references/entity.md +133 -0
- package/skills/ultra-generator/references/service.md +92 -0
- package/skills/ultra-generator/references/value-object.md +135 -0
- package/skills/wayfinder/SKILL.md +201 -0
- package/skills/workflow/SKILL.md +31 -0
- package/skills/writing-for-agents/SKILL-MECHANICS.md +32 -0
- package/skills/writing-for-agents/SKILL.md +86 -0
- package/skills/writing-for-agents/references/adrs.md +19 -0
package/eslint/index.mjs
ADDED
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preset flat-config del tier A — reglas ejecutables de forma, legibilidad y
|
|
3
|
+
* arquitectura.
|
|
4
|
+
*
|
|
5
|
+
* Materializa, como configuración de ESLint, las decisiones canónicas del
|
|
6
|
+
* registro:
|
|
7
|
+
* - A-001/A-024/A-025/A-026/A-027: dirección de dependencias entre las capas
|
|
8
|
+
* (`domain`, `feature`, `shared-infra`, `representation`, `app`), frameworks
|
|
9
|
+
* de infraestructura vetados en las capas internas, y tipos sin especificar
|
|
10
|
+
* en los ports.
|
|
11
|
+
* - A-046: métricas de forma y legibilidad (parámetros, líneas, complejidad,
|
|
12
|
+
* anidamiento, sentencias, números mágicos, tipos por archivo, identificadores
|
|
13
|
+
* e igualdad estricta).
|
|
14
|
+
* - A-047: sin globales ambientales (`console`, `process.env`).
|
|
15
|
+
* - A-050: el caso de uso habla en términos del dominio; no lee la
|
|
16
|
+
* representación serializada (`getPrimitive`/`getPayload`/`getSnapshot`) ni
|
|
17
|
+
* decide comparando `status`/`state`/`type`.
|
|
18
|
+
* - A-051: orden de imports por distancia, con el prefijo del paquete interno
|
|
19
|
+
* como grupo `internal`.
|
|
20
|
+
* - A-053: el catálogo compartido de errores (`feature-error`) es un hogar
|
|
21
|
+
* hermano de las features, importable por ellas sin violar boundaries.
|
|
22
|
+
* - A-054: la costura del driver port es raw data; ningún objeto con
|
|
23
|
+
* comportamiento ni hecho de dominio sin confirmar cruza el puerto.
|
|
24
|
+
*
|
|
25
|
+
* El preset es un módulo componible: el consumidor lo importa, lo invoca con su
|
|
26
|
+
* prefijo interno y compone sus reglas de proyecto encima. No escribe archivos
|
|
27
|
+
* ni lintea por sí mismo — el único ejecutor sigue siendo `qa-guardrail`
|
|
28
|
+
* (A-045/A-049).
|
|
29
|
+
*
|
|
30
|
+
* Uso:
|
|
31
|
+
* ```js
|
|
32
|
+
* // eslint.config.mjs del consumidor
|
|
33
|
+
* import ultra from "@imolko/ultra-agent/eslint";
|
|
34
|
+
*
|
|
35
|
+
* export default [
|
|
36
|
+
* ...ultra({ internal: "@acme/*" }),
|
|
37
|
+
* // reglas de proyecto del consumidor
|
|
38
|
+
* ];
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import tsParser from "@typescript-eslint/parser";
|
|
43
|
+
import tsPlugin from "@typescript-eslint/eslint-plugin";
|
|
44
|
+
import importX from "eslint-plugin-import-x";
|
|
45
|
+
import boundaries from "eslint-plugin-boundaries";
|
|
46
|
+
|
|
47
|
+
/** Archivos que el preset cubre por defecto. */
|
|
48
|
+
export const PRESET_FILES = ["**/*.{ts,tsx,js,mjs,cjs}"];
|
|
49
|
+
|
|
50
|
+
/** Carpetas y archivos que el preset ignora por defecto. */
|
|
51
|
+
export const PRESET_IGNORES = [
|
|
52
|
+
"**/node_modules/**",
|
|
53
|
+
"**/dist/**",
|
|
54
|
+
"**/build/**",
|
|
55
|
+
"**/coverage/**",
|
|
56
|
+
"temp-*/**",
|
|
57
|
+
".ultra-reporter/**",
|
|
58
|
+
".nx/**",
|
|
59
|
+
".pi/**",
|
|
60
|
+
"scenarios/**",
|
|
61
|
+
"docs/**",
|
|
62
|
+
"eslint.config.mjs",
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Capas canónicas de A-001 y el glob que las identifica por defecto.
|
|
67
|
+
*
|
|
68
|
+
* `domain`, `shared-infra`, `representations` y `apps` son un elemento por glob;
|
|
69
|
+
* `feature` captura cada slice (`features/<caso-de-uso>`) como un elemento
|
|
70
|
+
* distinto para que la matriz pueda vetar el acoplamiento entre features (A-024).
|
|
71
|
+
* El consumidor puede sobreescribir cualquier glob vía `layers`.
|
|
72
|
+
*/
|
|
73
|
+
export const DEFAULT_LAYERS = Object.freeze({
|
|
74
|
+
domain: "domain/**",
|
|
75
|
+
feature: "features/*",
|
|
76
|
+
"shared-infra": "shared-infra/*",
|
|
77
|
+
representation: "representations/*",
|
|
78
|
+
app: "apps/*",
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/** Nombres de capa, en el orden de A-001. */
|
|
82
|
+
export const LAYER_TYPES = Object.freeze(Object.keys(DEFAULT_LAYERS));
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Tipo de elemento del catálogo compartido de errores de infraestructura
|
|
86
|
+
* (A-053): un módulo hermano de los slices de feature, no una feature. Las
|
|
87
|
+
* features lo importan sin violar «una feature nunca importa otra feature».
|
|
88
|
+
*/
|
|
89
|
+
export const FEATURE_ERROR_TYPE = "feature-error";
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Glob por defecto del catálogo compartido de errores (A-053). Se deriva del
|
|
93
|
+
* glob de `feature` cuando este termina en `/*`; el consumidor puede
|
|
94
|
+
* sobreescribirlo explícitamente vía `layers[FEATURE_ERROR_TYPE]`.
|
|
95
|
+
*/
|
|
96
|
+
export const DEFAULT_FEATURE_ERROR_GLOB = "features/errors/**";
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Frameworks de infraestructura vetados en las capas internas: el dominio no los
|
|
100
|
+
* importa (A-001) y una feature es tech-free (A-025).
|
|
101
|
+
*/
|
|
102
|
+
export const INFRA_FRAMEWORKS = Object.freeze([
|
|
103
|
+
// Patrones con `/**` para vetar también los imports profundos (p. ej.
|
|
104
|
+
// `@nestjs/common/decorators`): minimatch no cruza `/` con un solo `*`.
|
|
105
|
+
"@nestjs/**",
|
|
106
|
+
"mongoose",
|
|
107
|
+
"pg",
|
|
108
|
+
"postgres",
|
|
109
|
+
"typeorm",
|
|
110
|
+
"sequelize",
|
|
111
|
+
"knex",
|
|
112
|
+
"prisma",
|
|
113
|
+
"@prisma/**",
|
|
114
|
+
"graphql",
|
|
115
|
+
"@apollo/**",
|
|
116
|
+
"apollo-server*",
|
|
117
|
+
"apollo-server**",
|
|
118
|
+
"express",
|
|
119
|
+
"fastify",
|
|
120
|
+
"rxjs",
|
|
121
|
+
"ioredis",
|
|
122
|
+
"redis",
|
|
123
|
+
"mongodb",
|
|
124
|
+
"kafkajs",
|
|
125
|
+
"amqplib",
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
const DOMAIN_FRAMEWORK_MESSAGE =
|
|
129
|
+
"El dominio no importa frameworks de infraestructura (A-001): solo validación y result-type.";
|
|
130
|
+
const FEATURE_FRAMEWORK_MESSAGE =
|
|
131
|
+
"La feature es tech-free (A-025): el driver tecnológico vive en shared-infra/ y se inyecta por el driver port.";
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Driver ports: la costura raw (A-025/A-054). Además de los tipos sin
|
|
135
|
+
* especificar, se aplican los selectores de costura raw.
|
|
136
|
+
*/
|
|
137
|
+
export const DRIVER_PORT_FILES = Object.freeze([
|
|
138
|
+
"**/driver.port.ts",
|
|
139
|
+
"**/*.driver.port.ts",
|
|
140
|
+
"**/service.port.ts",
|
|
141
|
+
"**/*.service.port.ts",
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Puertos de dominio (`use-case/*.port.ts`). A-025 exige que un puerto de
|
|
146
|
+
* dominio hable en conceptos de dominio y que nunca use `any`/`unknown`; el
|
|
147
|
+
* bloque no aplica los selectores raw (el dominio sí usa tipos con
|
|
148
|
+
* comportamiento). Los globs se derivan del glob de `feature` para respetar el
|
|
149
|
+
* override del consumidor.
|
|
150
|
+
*
|
|
151
|
+
* @param {string} featureGlob
|
|
152
|
+
* @returns {string[]}
|
|
153
|
+
*/
|
|
154
|
+
export function domainPortFileGlobs(featureGlob) {
|
|
155
|
+
return [`${featureGlob}/use-case/**/*.port.ts`];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Selector de globales ambientales (A-047). */
|
|
159
|
+
const AMBIENT_SYNTAX = {
|
|
160
|
+
selector: "MemberExpression[object.name='process'][property.name='env']",
|
|
161
|
+
message:
|
|
162
|
+
"Acceso directo a process.env prohibido (A-047). Inyecta la configuración por el contrato de settings.",
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/** Tipos sin especificar vetados en los ports (A-025). */
|
|
166
|
+
const UNTYPED_PORT_SYNTAX = [
|
|
167
|
+
{
|
|
168
|
+
selector: "TSAnyKeyword",
|
|
169
|
+
message: "Los ports no usan `any` (A-025): declara el raw data de la costura.",
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
selector: "TSUnknownKeyword",
|
|
173
|
+
message: "Los ports no usan `unknown` (A-025): declara el raw data de la costura.",
|
|
174
|
+
},
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Patrones por defecto de las formas raw que sí cruzan la costura del driver
|
|
179
|
+
* port (A-054): mementos/snapshots, la envolvente CloudEvent, DTOs y demás data
|
|
180
|
+
* plana; el canal de error (`Execution`/`Validation`, cualquier `*Error`), la
|
|
181
|
+
* promesa y las utilidades genéricas del tier.
|
|
182
|
+
*
|
|
183
|
+
* Son fragmentos de expresión regular (sin anclas; el patrón final las añade).
|
|
184
|
+
* El consumidor puede extenderlos vía `rawSeamTypes` para sus propias formas
|
|
185
|
+
* raw (A-052: el preset sirve a cualquier consumidor, no a la nomenclatura de
|
|
186
|
+
* un proyecto concreto).
|
|
187
|
+
*/
|
|
188
|
+
const DEFAULT_RAW_SEAM_TYPES = Object.freeze([
|
|
189
|
+
".*(?:Snapshot|Memento|Payload|DTO|Dto|Data|Event|CloudEvent|Props|Record|Row|Model|Schema|Filters|Filter|Query|Result)",
|
|
190
|
+
".*Error",
|
|
191
|
+
"Error",
|
|
192
|
+
"Promise",
|
|
193
|
+
"Execution",
|
|
194
|
+
"Validation",
|
|
195
|
+
"TaskExecution",
|
|
196
|
+
"TaskValidation",
|
|
197
|
+
"Result",
|
|
198
|
+
"Array",
|
|
199
|
+
"ReadonlyArray",
|
|
200
|
+
"Readonly",
|
|
201
|
+
"Record",
|
|
202
|
+
"Partial",
|
|
203
|
+
"Pick",
|
|
204
|
+
"Omit",
|
|
205
|
+
"Map",
|
|
206
|
+
"Set",
|
|
207
|
+
"Date",
|
|
208
|
+
"Buffer",
|
|
209
|
+
"Uint8Array",
|
|
210
|
+
"URL",
|
|
211
|
+
"RegExp",
|
|
212
|
+
]);
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Compone el patrón de los tipos que **sí** cruzan la costura raw (A-054) a
|
|
216
|
+
* partir de los defaults y de los patrones extra del consumidor.
|
|
217
|
+
*
|
|
218
|
+
* @param {string[]} extra Fragmentos de regex del consumidor.
|
|
219
|
+
* @returns {string}
|
|
220
|
+
*/
|
|
221
|
+
function rawSeamTypePattern(extra) {
|
|
222
|
+
return `^(?:${[...DEFAULT_RAW_SEAM_TYPES, ...extra].join("|")})$`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Normaliza y valida los patrones raw extra del consumidor.
|
|
227
|
+
*
|
|
228
|
+
* @param {unknown} rawSeamTypes
|
|
229
|
+
* @returns {string[]}
|
|
230
|
+
*/
|
|
231
|
+
function requireRawSeamTypes(rawSeamTypes) {
|
|
232
|
+
if (rawSeamTypes === undefined) {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
if (
|
|
236
|
+
!Array.isArray(rawSeamTypes) ||
|
|
237
|
+
rawSeamTypes.some((pattern) => typeof pattern !== "string" || pattern.trim() === "")
|
|
238
|
+
) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
"El preset @imolko/ultra-agent/eslint espera `rawSeamTypes` como array de patrones de tipo no vacíos.",
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return rawSeamTypes.map((pattern) => pattern.trim());
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Selectores de la costura raw del driver port (A-054): ningún objeto con
|
|
248
|
+
* comportamiento (agregado, entidad o value object) ni hecho de dominio sin
|
|
249
|
+
* confirmar cruza el puerto. El adaptador produce la forma raw.
|
|
250
|
+
*
|
|
251
|
+
* El primer selector es un heurístico de nomenclatura: marca toda referencia de
|
|
252
|
+
* tipo que no encaja en las formas raw conocidas (o en las que declare el
|
|
253
|
+
* consumidor vía `rawSeamTypes`). No distingue con certeza un objeto con
|
|
254
|
+
* comportamiento de un DTO plano de nombre no listado; A-054 mantiene esa
|
|
255
|
+
* detección como deuda de guardrail y la revisión sigue siendo la autoridad.
|
|
256
|
+
*
|
|
257
|
+
* @param {string[]} extra Fragmentos de regex extra del consumidor.
|
|
258
|
+
* @returns {Array<{ selector: string, message: string }>}
|
|
259
|
+
*/
|
|
260
|
+
function seamSyntax(extra) {
|
|
261
|
+
const raw = rawSeamTypePattern(extra).replaceAll("/", "\\/");
|
|
262
|
+
return [
|
|
263
|
+
{
|
|
264
|
+
selector: "TSTypeReference[typeName.name!=/" + raw + "/]",
|
|
265
|
+
message:
|
|
266
|
+
"La costura del driver port es raw data (A-054): ningún agregado, entidad ni value object del dominio cruza el puerto. Usa el memento/snapshot o un DTO plano, o declara tu forma raw en `rawSeamTypes`.",
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
selector: "Identifier[name='UncommittedEvent']",
|
|
270
|
+
message:
|
|
271
|
+
"La costura del driver port recibe la envolvente raw (A-054): el hecho de dominio sin confirmar no cruza el puerto; el adaptador construye la ocurrencia.",
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
selector: "Identifier[name='eventClass']",
|
|
275
|
+
message:
|
|
276
|
+
"El driver port no conoce la referencia de clase del hecho (A-054): el adaptador produce la envolvente raw; el puerto no arrastra `eventClass`.",
|
|
277
|
+
},
|
|
278
|
+
];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Selectores del caso de uso que no lee la representación (A-050): prohíbe
|
|
283
|
+
* invocar los getters de la representación serializada y decidir comparando un
|
|
284
|
+
* campo de estado (`status`/`state`/`type`) en lugar de un predicado del dominio.
|
|
285
|
+
*/
|
|
286
|
+
const USE_CASE_SYNTAX = [
|
|
287
|
+
{
|
|
288
|
+
selector: "CallExpression[callee.property.name=/^(getPrimitive|getPayload|getSnapshot)$/]",
|
|
289
|
+
message:
|
|
290
|
+
"El caso de uso no lee la representación serializada del dominio (A-050): pide el hecho al dominio (predicados, consultas, fábricas); la traducción a la wire la hace la frontera.",
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
selector: "BinaryExpression[operator=/^[!=]=?=?$/][left.property.name=/^(status|state|type)$/]",
|
|
294
|
+
message:
|
|
295
|
+
"El caso de uso no decide comparando un campo de estado de la representación (A-050): usa un predicado del dominio (p. ej. claim.isOpen()).",
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
selector: "BinaryExpression[operator=/^[!=]=?=?$/][right.property.name=/^(status|state|type)$/]",
|
|
299
|
+
message:
|
|
300
|
+
"El caso de uso no decide comparando un campo de estado de la representación (A-050): usa un predicado del dominio (p. ej. claim.isOpen()).",
|
|
301
|
+
},
|
|
302
|
+
];
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Reglas de forma y legibilidad (A-046) y dependencias explícitas (A-047).
|
|
306
|
+
* Son agnósticas al prefijo de paquete interno y a las capas, por eso viven
|
|
307
|
+
* fuera de la fábrica.
|
|
308
|
+
*/
|
|
309
|
+
export const STYLE_RULES = {
|
|
310
|
+
// A-046 — métricas de forma.
|
|
311
|
+
"max-params": ["error", 2],
|
|
312
|
+
"max-lines-per-function": ["error", { max: 50, skipBlankLines: true }],
|
|
313
|
+
"max-lines": ["error", { max: 250, skipBlankLines: true }],
|
|
314
|
+
"no-magic-numbers": [
|
|
315
|
+
"error",
|
|
316
|
+
{
|
|
317
|
+
detectObjects: false,
|
|
318
|
+
enforceConst: true,
|
|
319
|
+
ignore: [0, 1, -1, 2],
|
|
320
|
+
ignoreArrayIndexes: true,
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
complexity: ["error", 10],
|
|
324
|
+
"max-depth": ["error", 4],
|
|
325
|
+
"max-statements": ["error", 20],
|
|
326
|
+
"max-classes-per-file": ["error", 1],
|
|
327
|
+
"ultra/one-type-per-file": "error",
|
|
328
|
+
"id-length": ["error", { min: 2 }],
|
|
329
|
+
eqeqeq: ["error", "always"],
|
|
330
|
+
|
|
331
|
+
// A-047 — sin globales ambientales.
|
|
332
|
+
"no-console": "error",
|
|
333
|
+
"no-restricted-syntax": ["error", AMBIENT_SYNTAX],
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
/** Tipos que definen un tipo con nombre a nivel de archivo. */
|
|
337
|
+
const TYPE_DECLARATION_TYPES = new Set([
|
|
338
|
+
"ClassDeclaration",
|
|
339
|
+
"TSInterfaceDeclaration",
|
|
340
|
+
"TSTypeAliasDeclaration",
|
|
341
|
+
"TSEnumDeclaration",
|
|
342
|
+
]);
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Reconoce una declaración de tipo, desenvolviendo el `export` que la envuelve.
|
|
346
|
+
*
|
|
347
|
+
* @param {{ type?: string, declaration?: unknown } | undefined} node
|
|
348
|
+
* @returns {boolean}
|
|
349
|
+
*/
|
|
350
|
+
function isTypeDeclaration(node) {
|
|
351
|
+
if (!node || typeof node.type !== "string") {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
if (TYPE_DECLARATION_TYPES.has(node.type)) {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
|
|
358
|
+
return isTypeDeclaration(node.declaration);
|
|
359
|
+
}
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Regla local `ultra/one-type-per-file` (A-046 §8). `max-classes-per-file` solo
|
|
365
|
+
* cuenta clases; esta regla extiende la métrica a interfaces, alias de tipo y
|
|
366
|
+
* enums para que «un tipo principal por archivo» sea verificable de verdad.
|
|
367
|
+
*/
|
|
368
|
+
const ONE_TYPE_PER_FILE_RULE = {
|
|
369
|
+
meta: {
|
|
370
|
+
type: "problem",
|
|
371
|
+
docs: { description: "Un tipo principal por archivo (A-046 §8)." },
|
|
372
|
+
schema: [],
|
|
373
|
+
messages: {
|
|
374
|
+
tooMany:
|
|
375
|
+
"Un tipo principal por archivo (A-046 §8): mueve el tipo adicional a su propio archivo.",
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
create(context) {
|
|
379
|
+
return {
|
|
380
|
+
Program(node) {
|
|
381
|
+
const declarations = node.body.filter(isTypeDeclaration);
|
|
382
|
+
for (const extra of declarations.slice(1)) {
|
|
383
|
+
context.report({ node: extra, messageId: "tooMany" });
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Plugin local del preset: aloja las reglas que materializan una métrica de
|
|
392
|
+
* A-046 que el core de ESLint no cubre, sin introducir un segundo ejecutor.
|
|
393
|
+
*/
|
|
394
|
+
const ULTRA_PLUGIN = Object.freeze({
|
|
395
|
+
rules: Object.freeze({ "one-type-per-file": ONE_TYPE_PER_FILE_RULE }),
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Matriz de dependencias entre capas (A-001/A-024/A-025/A-026/A-027). El default
|
|
400
|
+
* es vetar toda dependencia local no declarada; las capas interiores no conocen
|
|
401
|
+
* a las exteriores:
|
|
402
|
+
* - app → domain, feature, shared-infra, representation
|
|
403
|
+
* - feature → domain, feature-error (el catálogo compartido de A-053)
|
|
404
|
+
* - feature-error → domain, feature-error (su propio catálogo)
|
|
405
|
+
* - shared-infra → domain, feature (los driver ports)
|
|
406
|
+
* - representation → domain
|
|
407
|
+
* - domain → domain
|
|
408
|
+
*/
|
|
409
|
+
const LAYER_DEPENDENCY_POLICIES = [
|
|
410
|
+
{
|
|
411
|
+
from: { element: { type: "app" } },
|
|
412
|
+
allow: {
|
|
413
|
+
to: {
|
|
414
|
+
element: {
|
|
415
|
+
types: { anyOf: ["domain", "feature", "shared-infra", "representation"] },
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
from: { element: { type: "feature" } },
|
|
422
|
+
allow: {
|
|
423
|
+
to: { element: { types: { anyOf: ["domain", FEATURE_ERROR_TYPE] } } },
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
from: { element: { type: FEATURE_ERROR_TYPE } },
|
|
428
|
+
allow: {
|
|
429
|
+
to: { element: { types: { anyOf: ["domain", FEATURE_ERROR_TYPE] } } },
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
from: { element: { type: "shared-infra" } },
|
|
434
|
+
allow: { to: { element: { types: { anyOf: ["domain", "feature"] } } } },
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
from: { element: { type: "representation" } },
|
|
438
|
+
allow: { to: { element: { type: "domain" } } },
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
from: { element: { type: "domain" } },
|
|
442
|
+
allow: { to: { element: { type: "domain" } } },
|
|
443
|
+
},
|
|
444
|
+
];
|
|
445
|
+
|
|
446
|
+
const LAYER_DEPENDENCIES_OPTIONS = {
|
|
447
|
+
default: "disallow",
|
|
448
|
+
policies: LAYER_DEPENDENCY_POLICIES,
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Construye las reglas de orden de imports por distancia (A-051): builtins,
|
|
453
|
+
* externos, internos, relativos de carpeta lejana a cercana. Dentro de cada
|
|
454
|
+
* grupo, orden alfabético. Los imports de efecto lateral quedan fuera del
|
|
455
|
+
* ordenador (la regla no los mueve).
|
|
456
|
+
*
|
|
457
|
+
* @param {string} internal Prefijo del paquete interno (p. ej. `@acme/*`).
|
|
458
|
+
* @returns {Record<string, unknown>} Reglas de `import-x`.
|
|
459
|
+
*/
|
|
460
|
+
export function importRules(internal) {
|
|
461
|
+
return {
|
|
462
|
+
"import-x/order": [
|
|
463
|
+
"error",
|
|
464
|
+
{
|
|
465
|
+
groups: [
|
|
466
|
+
"builtin",
|
|
467
|
+
"external",
|
|
468
|
+
"internal",
|
|
469
|
+
"parent",
|
|
470
|
+
"sibling",
|
|
471
|
+
"index",
|
|
472
|
+
],
|
|
473
|
+
pathGroups: [{ pattern: internal, group: "internal", position: "before" }],
|
|
474
|
+
// Sin excluir `external`: un paquete con el prefijo interno se
|
|
475
|
+
// re-clasifica como `internal` y se ordena después de los externos.
|
|
476
|
+
pathGroupsExcludedImportTypes: ["builtin"],
|
|
477
|
+
alphabetize: { order: "asc", caseInsensitive: true },
|
|
478
|
+
// A-051: los imports se leen en bloques de distancia; el salto de línea
|
|
479
|
+
// separa cada capa y la funde visualmente si se omite.
|
|
480
|
+
"newlines-between": "always",
|
|
481
|
+
},
|
|
482
|
+
],
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Valida y normaliza el prefijo del paquete interno.
|
|
488
|
+
*
|
|
489
|
+
* @param {unknown} internal
|
|
490
|
+
* @returns {string}
|
|
491
|
+
*/
|
|
492
|
+
function requireInternal(internal) {
|
|
493
|
+
if (typeof internal !== "string" || internal.trim() === "") {
|
|
494
|
+
throw new Error(
|
|
495
|
+
"El preset @imolko/ultra-agent/eslint requiere `internal` (el prefijo del paquete interno, p. ej. \"@acme/*\").",
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
return internal.trim();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Deriva el glob del catálogo compartido de errores (A-053) del glob de las
|
|
503
|
+
* features: `features/*` describe slices hermanas de `features/errors/`. Si el
|
|
504
|
+
* glob no termina en `/*` no se puede derivar y se usa el default.
|
|
505
|
+
*
|
|
506
|
+
* @param {string} featureGlob
|
|
507
|
+
* @returns {string | undefined}
|
|
508
|
+
*/
|
|
509
|
+
function deriveFeatureErrorGlob(featureGlob) {
|
|
510
|
+
if (typeof featureGlob === "string" && featureGlob.endsWith("/*")) {
|
|
511
|
+
return `${featureGlob.slice(0, -2)}/errors/**`;
|
|
512
|
+
}
|
|
513
|
+
return undefined;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Resuelve las capas partiendo de los defaults de A-001 y aplicando los overrides
|
|
518
|
+
* del consumidor. Valida que la capa exista y que el glob sea una cadena no vacía.
|
|
519
|
+
* El catálogo de errores (A-053) se deriva del glob de `feature` salvo que se
|
|
520
|
+
* sobreescriba explícitamente con la clave `feature-error`.
|
|
521
|
+
*
|
|
522
|
+
* @param {unknown} overrides Globs de capa opcionales.
|
|
523
|
+
* @returns {Record<string, string>} Globs de capa completos.
|
|
524
|
+
*/
|
|
525
|
+
function resolveLayers(overrides) {
|
|
526
|
+
const layers = { ...DEFAULT_LAYERS };
|
|
527
|
+
if (overrides !== undefined) {
|
|
528
|
+
if (typeof overrides !== "object" || overrides === null || Array.isArray(overrides)) {
|
|
529
|
+
throw new Error(
|
|
530
|
+
"El preset @imolko/ultra-agent/eslint espera `layers` como objeto de globs por capa.",
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
for (const [layer, glob] of Object.entries(overrides)) {
|
|
534
|
+
if (layer !== FEATURE_ERROR_TYPE && !Object.hasOwn(DEFAULT_LAYERS, layer)) {
|
|
535
|
+
throw new Error(
|
|
536
|
+
`Capa desconocida en \`layers\`: "${layer}". Capas válidas: ${[
|
|
537
|
+
...LAYER_TYPES,
|
|
538
|
+
FEATURE_ERROR_TYPE,
|
|
539
|
+
].join(", ")}.`,
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
if (typeof glob !== "string" || glob.trim() === "") {
|
|
543
|
+
throw new Error(`El glob de la capa "${layer}" debe ser una cadena no vacía.`);
|
|
544
|
+
}
|
|
545
|
+
layers[layer] = glob.trim();
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (layers[FEATURE_ERROR_TYPE] === undefined) {
|
|
549
|
+
layers[FEATURE_ERROR_TYPE] =
|
|
550
|
+
deriveFeatureErrorGlob(layers.feature) ?? DEFAULT_FEATURE_ERROR_GLOB;
|
|
551
|
+
}
|
|
552
|
+
return layers;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Traduce un glob de capa (patrón de elemento de `eslint-plugin-boundaries`) al
|
|
557
|
+
* conjunto de globs de archivo que ESLint acepta en `files`. Un glob que termina
|
|
558
|
+
* en `/*` describe la carpeta del elemento (p. ej. `features/*`), así que hay que
|
|
559
|
+
* alcanzar sus descendientes; un glob `**` ya los cubre.
|
|
560
|
+
*
|
|
561
|
+
* @param {string} glob
|
|
562
|
+
* @returns {string[]}
|
|
563
|
+
*/
|
|
564
|
+
function layerFileGlobs(glob) {
|
|
565
|
+
return glob.endsWith("/*") ? [glob, `${glob}/**`] : [glob];
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Globs de archivo del caso de uso dentro de cada slice de feature (A-025). Se
|
|
570
|
+
* derivan del glob de `feature` para que respeten el override del consumidor.
|
|
571
|
+
*
|
|
572
|
+
* @param {string} featureGlob
|
|
573
|
+
* @returns {string[]}
|
|
574
|
+
*/
|
|
575
|
+
function useCaseFileGlobs(featureGlob) {
|
|
576
|
+
return [`${featureGlob}/use-case/**`];
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Describe las capas para `eslint-plugin-boundaries`.
|
|
581
|
+
*
|
|
582
|
+
* @param {Record<string, string>} layers
|
|
583
|
+
* @returns {Array<{ type: string, pattern: string, capture: string[] }>}
|
|
584
|
+
*/
|
|
585
|
+
function elementDescriptors(layers) {
|
|
586
|
+
// El catálogo de errores (A-053) va antes que `feature`: `features/errors/**`
|
|
587
|
+
// también encajaría en `features/*`, y el primer descriptor que encaja gana.
|
|
588
|
+
return [FEATURE_ERROR_TYPE, ...LAYER_TYPES].map((type) => ({
|
|
589
|
+
type,
|
|
590
|
+
pattern: layers[type],
|
|
591
|
+
capture: ["elementName"],
|
|
592
|
+
}));
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Crea el array flat-config del tier A.
|
|
597
|
+
*
|
|
598
|
+
* @param {{
|
|
599
|
+
* internal: string,
|
|
600
|
+
* layers?: Record<string, string>,
|
|
601
|
+
* rawSeamTypes?: string[],
|
|
602
|
+
* }} options Opciones del preset. `internal` es obligatorio: el prefijo del
|
|
603
|
+
* paquete interno del consumidor. `layers` sobreescribe los globs de capa
|
|
604
|
+
* (defaults de A-001) y admite la clave `feature-error` para el catálogo de
|
|
605
|
+
* errores de A-053. `rawSeamTypes` extiende, con fragmentos de expresión
|
|
606
|
+
* regular, las formas raw que cruzan el driver port (A-054) cuando el
|
|
607
|
+
* consumidor nombra sus DTOs al margen de los sufijos por defecto.
|
|
608
|
+
* @returns {import("eslint").Linter.Config[]}
|
|
609
|
+
*/
|
|
610
|
+
export function createPreset(options) {
|
|
611
|
+
const internal = requireInternal(options?.internal);
|
|
612
|
+
const layers = resolveLayers(options?.layers);
|
|
613
|
+
const rawSeamTypes = requireRawSeamTypes(options?.rawSeamTypes);
|
|
614
|
+
|
|
615
|
+
return [
|
|
616
|
+
{ ignores: PRESET_IGNORES },
|
|
617
|
+
{
|
|
618
|
+
files: PRESET_FILES,
|
|
619
|
+
languageOptions: {
|
|
620
|
+
parser: tsParser,
|
|
621
|
+
parserOptions: {
|
|
622
|
+
ecmaVersion: "latest",
|
|
623
|
+
sourceType: "module",
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
plugins: {
|
|
627
|
+
// Se registra aunque no habilita reglas propias: resuelve las
|
|
628
|
+
// directivas de supresión de `@typescript-eslint/*` que ya tenga el
|
|
629
|
+
// consumidor y sirve de superficie para que componga sus reglas de
|
|
630
|
+
// proyecto encima del preset (A-052: el preset es componible).
|
|
631
|
+
"@typescript-eslint": tsPlugin,
|
|
632
|
+
"import-x": importX,
|
|
633
|
+
boundaries,
|
|
634
|
+
ultra: ULTRA_PLUGIN,
|
|
635
|
+
},
|
|
636
|
+
linterOptions: {
|
|
637
|
+
reportUnusedDisableDirectives: "off",
|
|
638
|
+
},
|
|
639
|
+
settings: {
|
|
640
|
+
"import-x/resolver": { typescript: { alwaysTryTypes: true } },
|
|
641
|
+
"import-x/extensions": [".ts", ".tsx", ".js", ".mjs", ".cjs"],
|
|
642
|
+
// `eslint-plugin-boundaries` resuelve con el resolver de `import`.
|
|
643
|
+
"import/resolver": { typescript: { alwaysTryTypes: true } },
|
|
644
|
+
"boundaries/elements": elementDescriptors(layers),
|
|
645
|
+
},
|
|
646
|
+
rules: {
|
|
647
|
+
...STYLE_RULES,
|
|
648
|
+
...importRules(internal),
|
|
649
|
+
"boundaries/dependencies": ["error", LAYER_DEPENDENCIES_OPTIONS],
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
files: layerFileGlobs(layers.domain),
|
|
654
|
+
rules: {
|
|
655
|
+
"no-restricted-imports": [
|
|
656
|
+
"error",
|
|
657
|
+
{ patterns: [{ group: [...INFRA_FRAMEWORKS], message: DOMAIN_FRAMEWORK_MESSAGE }] },
|
|
658
|
+
],
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
files: layerFileGlobs(layers.feature),
|
|
663
|
+
rules: {
|
|
664
|
+
"no-restricted-imports": [
|
|
665
|
+
"error",
|
|
666
|
+
{ patterns: [{ group: [...INFRA_FRAMEWORKS], message: FEATURE_FRAMEWORK_MESSAGE }] },
|
|
667
|
+
],
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
// A-050 — el caso de uso no lee la representación serializada. Va antes del
|
|
671
|
+
// bloque de ports para que un `*.service.port.ts` conserve las reglas de
|
|
672
|
+
// A-025 (el último bloque que encaja gana). Los tests y el formateo de
|
|
673
|
+
// errores quedan fuera: consumen la representación legítimamente.
|
|
674
|
+
{
|
|
675
|
+
files: useCaseFileGlobs(layers.feature),
|
|
676
|
+
ignores: [
|
|
677
|
+
"**/*.spec.ts",
|
|
678
|
+
"**/*.test.ts",
|
|
679
|
+
"**/*.integration.test.ts",
|
|
680
|
+
"**/test-setup.ts",
|
|
681
|
+
"**/test-helpers.ts",
|
|
682
|
+
"**/test-doubles/**",
|
|
683
|
+
"**/errors/**",
|
|
684
|
+
],
|
|
685
|
+
rules: {
|
|
686
|
+
"no-restricted-syntax": ["error", AMBIENT_SYNTAX, ...USE_CASE_SYNTAX],
|
|
687
|
+
},
|
|
688
|
+
},
|
|
689
|
+
// A-025 — un puerto de dominio también habla en conceptos de dominio y
|
|
690
|
+
// nunca usa `any`/`unknown`. Va después del bloque de A-050 para añadir la
|
|
691
|
+
// métrica a los `*.port.ts` sin perder los selectores de A-050 que ya
|
|
692
|
+
// recibían, y antes del bloque de driver ports para que un
|
|
693
|
+
// `*.service.port.ts` herede además los selectores de costura.
|
|
694
|
+
{
|
|
695
|
+
files: domainPortFileGlobs(layers.feature),
|
|
696
|
+
rules: {
|
|
697
|
+
"no-restricted-syntax": [
|
|
698
|
+
"error",
|
|
699
|
+
AMBIENT_SYNTAX,
|
|
700
|
+
...USE_CASE_SYNTAX,
|
|
701
|
+
...UNTYPED_PORT_SYNTAX,
|
|
702
|
+
],
|
|
703
|
+
},
|
|
704
|
+
},
|
|
705
|
+
// A-054 — la costura raw del driver port (va al final: es el bloque que gana
|
|
706
|
+
// en los archivos que solapan con los anteriores).
|
|
707
|
+
{
|
|
708
|
+
files: [...DRIVER_PORT_FILES],
|
|
709
|
+
rules: {
|
|
710
|
+
"no-restricted-syntax": [
|
|
711
|
+
"error",
|
|
712
|
+
AMBIENT_SYNTAX,
|
|
713
|
+
...UNTYPED_PORT_SYNTAX,
|
|
714
|
+
...seamSyntax(rawSeamTypes),
|
|
715
|
+
],
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
];
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
export default createPreset;
|