Skip to content

feat: aact v2 — CLI, auto-fix, Structurizr, новые правила#17

Open
ChS23 wants to merge 62 commits intoByndyusoft:mainfrom
ChS23:v2
Open

feat: aact v2 — CLI, auto-fix, Structurizr, новые правила#17
ChS23 wants to merge 62 commits intoByndyusoft:mainfrom
ChS23:v2

Conversation

@ChS23
Copy link

@ChS23 ChS23 commented Mar 20, 2026

aact v2

CLI и конфигурация, авто-фикс нарушений, поддержка Structurizr DSL, новые правила валидации.

CLI

npx aact check            # проверка правил
npx aact check --fix      # авто-исправление
npx aact check --dry-run  # предпросмотр фиксов
npx aact analyze          # coupling/cohesion метрики
npx aact generate         # PlantUML / Kubernetes
npx aact init             # aact.config.ts

Вывод группирует нарушения по правилам, форматы: text, json, github (CI аннотации).

Правила

Правило Auto-fix
acl — интеграции только через ACL да
acyclic — без циклов в зависимостях
apiGateway — внешние вызовы через gateway
crud — БД только через repo/relay да
dbPerService — одна БД = один владелец да
cohesion — связность > связанность
stableDependencies — зависимости к стабильным
commonReuse — используешь часть публичного API контекста, используй всё

Auto-fix

Boundary-aware: cross-boundary редирект через публичный API контекста, same-boundary через repo. Автоматически определяет naming convention (snake_case / camelCase / kebab-case) и генерирует имена в ней. Правки пишутся в PlantUML или Structurizr DSL с сохранением отступов.

Источники

  • PlantUML C4 (.puml)
  • Structurizr workspace.json (DSL-идентификаторы через structurizr.dsl.identifier)
  • Kubernetes deploy configs

Конфиг

import { defineConfig } from "aact";

export default defineConfig({
  source: {
    type: "structurizr",
    path: "./workspace.json",
    writePath: "./workspace.dsl",
  },
  rules: {
    acl: true,
    crud: true,
    dbPerService: true,
    cohesion: true,
    commonReuse: true,
  },
});

ADR

Примеры

  • examples/banking-plantuml/ — PlantUML, правила, K8s генерация
  • examples/microservices-structurizr/ — Structurizr, полный цикл
  • examples/violations-demo/ — демо нарушений

267 тестов, 0 lint errors, 0 tsc errors.

ChS23 and others added 30 commits February 8, 2026 00:54
- Migrate from yarn to pnpm
- Upgrade TypeScript to 5.9, ESLint to 9 (flat config), Prettier to 3
- Replace Jest with Vitest 4, add unbuild
- Upgrade Husky to 9, commitlint to 20, lint-staged to 16
- Add Structurizr workspace support
- Fix all ESLint warnings (cognitive complexity, type safety, slow regex)
- Update GitHub Actions to latest versions
- Rename PumlFile → ArchitectureModel
- Export Relation from entities/index.ts
- Rewrite analyzer.ts with generic entities, remove plantuml-parser coupling
- analyzer now accepts ArchitectureModel instead of filename
- Separate analysis results (BoundaryAnalysis, AnalysisReport) from input data
- Switch structurizr test to workspace.json fixture
Move directories to target structure (entities→model, plantuml→loaders/plantuml,
structurizr→loaders/structurizr, deployConfigs→loaders/kubernetes). Extract
architecture validation logic into reusable rule functions (checkAcl, checkCrud,
checkAcyclic, checkCohesion) in src/rules/. Add unit tests on in-memory fixtures
for all rules and integration tests for loaders. Remove dead code (groupElements.ts).
checkApiGateway validates that ACL containers reach external systems
through an API Gateway (matching technology pattern).
checkStableDependencies enforces the Stable Dependencies Principle:
a component should only depend on more stable components (lower
instability index).
Move example tests into examples/plantuml/ subdirectory and add a
rules.test.ts demonstrating ACL, acyclic, API Gateway, and stable
dependencies checks against C4L2.puml.
Replace dead workshop branch links with actual test file paths for
ACL, CRUD, API Gateway, Database per service, Stable Dependencies,
and Acyclic rules.
- Fix import sorting, utf8 encoding identifiers, nested template literals
- Replace any with unknown in RuleDefinition via type-safe defineRule helper
- Restore exhaustive switch check in loadModel
- Extract computeCoupling to reduce cognitive complexity
- Add eslint-disable for intentional complexity (plantuml generator, check command)
- Add eslint-disable for http:// in test fixtures (internal service URLs)
- Add .markdownlintignore for legacy documentation files
- Extend test ESLint overrides to examples directory
- Remove hardcoded resources/architecture/ from loaders, accept real paths
- Add generatePlantumlFromModel for symmetric model-based PlantUML generation
- Narrow public API: drop internal re-exports from barrel files
- Sync package.json version to 2.0.0
- Set failOnWarn: true in build config
- Add error path tests for config validation and loaders
- Extract K8s loader exclude list into configurable option
Rename examples/plantuml → examples/banking-plantuml and add
examples/microservices-structurizr with Structurizr pipeline demo.
- README: add CLI quick start, library usage, examples and docs sections
- patterns.md: add example links, replace TBD with dashes, add Cohesion
- roadmap.md: reflect v2 progress (CLI, Structurizr, rules, generators)
- ADRs: update test links to actual paths in test/rules/ and examples/
- build.config: prepend shebang to CLI entry for npx support
- package.json: add keywords, fix bin path, bump version
- README: fix video link spacing
GITHUB_ACTIONS env causes detectFormat to return "github" instead
of "text", which skips consola.success calls the test asserts on.
- Add structurizrDslSyntax implementing SourceSyntax for DSL write-back
- Fix loader: detect external systems via tags when location !== "External"
- Fix loader: fall back rel.description as technology when no spaces
- Add source.writePath config field for DSL path separate from JSON path
- check --fix: route structurizr fixes to writePath, skip re-check for DSL
- check --fix: warn when structurizr source has no writePath configured
Three bounded contexts (Orders, Inventory, Fulfillment) with external
systems, ACL containers, and repo-tagged DB accessors — covers all rules.
fixAcl:
- redesign algorithm: one Rel(svc, acl), N replacements Rel(svc, ext) -> Rel(acl, ext)
- warn and skip when acl container already exists

fixDbPerService:
- prefer repo/relay-tagged container as DB owner over first-in-list
- add ownerTags option (default ["repo", "relay"])
- warn when relation not found in flatMap

applyEdits:
- warn when pattern not found in source
- warn when pattern matches multiple lines

tests:
- fix cohesion test: parent boundary containers must be empty (leaf-only)
- update fixAcl and fixDbPerService tests to reflect new behaviour
ChS23 and others added 30 commits March 11, 2026 15:59
- remaining violations count after fix
- violations with no auto-fix available (acyclic)
- structurizr --fix without writePath → warn + throw
- structurizr --fix with writePath → writes DSL, warns to regenerate
- analyze: unknown format falls back to text output
- analyze: coupling relations are logged per boundary
- generate: kubernetes writes no files when model has no deployable containers
- init: throws when file write fails
rules:
- acyclic: self-cycle A→A, empty container list
- acl: empty list, violation message lists all external deps
- dbPerService: custom dbType option
- stableDependencies: custom externalType option

loaders:
- structurizr: empty workspace, async relation tag, external by tags
- plantuml: no throw on relation to unknown container
- analyzer: pre-build nameSet/parentMap/childNames per boundary,
  extract classifyRelation and isSyncApiCall helpers,
  use Set for db name lookups in analyzeDatabases
- cohesion: pre-build boundary name Set once per call
- fix: use splice for line insertion, single findIndex scan
- check: extract formatResults, handleFixMode, writeFixes, suggestFixes;
  run() is now a flat coordinator, eslint-disable removed
- generate: load model once in run(), pass to runPlantuml/runKubernetes
…re dbPerService fix

- Use DSL identifier (structurizr.dsl.identifier) as container name in Structurizr loader
- Redesign check output: ruff-style grouping, aligned columns, picocolors, summary line
- Rewrite violation messages across all rules to be human-readable
- fixDbPerService: cross-boundary violators now redirect to public API, not internal repo
- Add violations-demo example with intentional ACL and dbPerService violations
applyEdits now detects leading whitespace from the matched line and
applies it to inserted/replaced content, so generated blocks align
with surrounding DSL instead of starting at column 0.
- fixCrud redirects non-repo DB accessors through existing repo or creates
  a new one (deriving name from DB, e.g. orders_db → orders_repo)
- fixCrud removes non-database dependencies from repo-tagged containers
- Add repoSuffix option to CrudOptions for configurable naming
- Fix plantumlSyntax.containerPattern to match any container type
  (Container, ContainerDb, etc.) using (name, instead of Container(name,
…tils

- buildContainerBoundaryMap and findPublicApiCandidate moved to boundaryUtils.ts
- resolveRedirectTarget added: same-boundary → repo, cross-boundary → public API
- fixDbPerService and fixCrud both use shared utils
- fixCrud Type 1 is now boundary-aware: cross-boundary with no existing repo
  warns and skips instead of creating a repo in the wrong context
- check.ts: add null coalescing for edit.content, return exitWithViolations() for TS narrowing
- check.test.ts: replace readonly relations mutation with Object.assign
- generate.test.ts: cast mockMkdir to void-returning type to allow mockResolvedValue() without args
- loadConfig.test.ts: add as unknown as for overlapping type cast
Extract detectNamingConvention + joinName into namingUtils.ts.
fixCrud and fixAcl now derive repo/acl names using the model's
actual convention (snake_case, camelCase, kebab-case) instead of
hardcoded suffixes. Remove repoSuffix and aclSuffix options.
- fix.ts: use RegExp.exec() instead of String.match()
- fixCrud.ts: replace slow-regex patterns with endsWith/slice stripDbWord helper
  covering _db, -db, db, _database, -database, database suffixes
- add tests for database suffix stripping and repo label derivation
- boundaryUtils.test.ts: direct tests for buildContainerBoundaryMap,
  findPublicApiCandidate (in-degree ranking) and resolveRedirectTarget
- fixCrud: cross-boundary redirect to public API, warn+skip scenarios
- fixDbPerService: cross-boundary redirect, no public API, mixed boundaries
- fixAcl: camelCase (myServiceAcl) and kebab-case (my-service-acl) naming
- loadConfig: verify removed aclSuffix field is rejected by schema
Checks that if a consumer boundary uses at least one public service
of a provider boundary, it uses all of them. Public = has incoming
relations from outside the boundary.
Three bounded contexts with intentional CRP violation:
inventory uses orders_api but not orders_events.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant