Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b8f8421
chore: harden test discipline — coverage, property-based, e2e, mutation
ChS23 May 11, 2026
5cb95ed
test: distribute property tests, lift coverage floor to 97/90/99/98
ChS23 May 11, 2026
924af13
test: kill mutants — add registry shape checks, message + warning ass…
ChS23 May 11, 2026
9fd38e2
test: continue mutation hardening — fix-* and stable* assertions
ChS23 May 11, 2026
2892515
test(fix): surgical mutation kills across fix-* surfaces
ChS23 May 11, 2026
869861c
test(fix): pin indent-from-anchor application in applyEdits
ChS23 May 11, 2026
03e4c1b
test(fix): pin cross-boundary warn rule name + description + repoTags…
ChS23 May 11, 2026
61dbb14
test(fix): pin nullish fallback in applyEdits content
ChS23 May 11, 2026
fefcfee
test(fix,gen): mutation kills in fix-* and generators
ChS23 May 11, 2026
b49e30a
test(loaders/structurizr): unit tests for every parsing branch
ChS23 May 11, 2026
1ab38bd
test(loaders): unit tests for plantuml + kubernetes parsing branches
ChS23 May 11, 2026
78c3766
test(loaders+fix): more mutation kills via direct mocking and type pins
ChS23 May 11, 2026
5c4f366
test(rules): kill mutants in boundaryUtils and namingUtils
ChS23 May 11, 2026
7e04c3a
test(loaders+rules): kill more structurizr and cohesion mutants
ChS23 May 11, 2026
f0c5bd9
test(rules): more fixDbPerService and plantuml mutant kills
ChS23 May 11, 2026
751cd22
test: bulk message + edge tests for commonReuse, acyclic, apiGateway,…
ChS23 May 11, 2026
8c9069d
chore(stryker): broad disable for genuinely equivalent mutations
ChS23 May 11, 2026
93b411d
chore(stryker): disable equivalents in plantuml + filterElements + ma…
ChS23 May 11, 2026
776b2bf
chore(stryker): disable equivalents in kubernetes mapFromConfig
ChS23 May 11, 2026
69d2be3
chore(stryker): convert single-line disable to block disable for plan…
ChS23 May 11, 2026
3f91f7f
docs(readme): coverage and mutation badges
ChS23 May 11, 2026
2c2c52e
Revert "docs(readme): coverage and mutation badges"
ChS23 May 11, 2026
6aadddd
chore(deps): bump safe minor + add Testing section to README
ChS23 May 11, 2026
61aaf25
feat(release): add changelogen for OSS-style automated CHANGELOG
ChS23 May 11, 2026
f646d36
refactor(cli): use consola/utils colors+box; drop picocolors
ChS23 May 12, 2026
46184a3
chore: add knip, publint and a few useful eslint plugins
ChS23 May 12, 2026
348171f
chore: bump Node engines to >=22 (Node 20 EOL'd 2026-04-30)
ChS23 May 12, 2026
2e60a92
chore: swap node:path -> pathe across src
ChS23 May 12, 2026
e7854b6
fix(ci): exclude e2e from test:coverage (needs built CLI)
ChS23 May 12, 2026
fb3ce69
fix(ci): build before test:coverage so e2e runs with built CLI
ChS23 May 12, 2026
cbe5a1d
fix(test/e2e): neutralize GITHUB_ACTIONS so check uses text format in CI
ChS23 May 12, 2026
1d68d74
test: lower coverage floor to OSS-realistic 95/85/95/95
ChS23 May 12, 2026
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
15 changes: 12 additions & 3 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x, 22.x]
node-version: [22.x, 24.x]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
Expand All @@ -22,5 +22,14 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm lint
- name: Test
run: pnpm test
- name: Build CLI
run: pnpm build
- name: Tests with coverage (unit + integration + e2e)
run: pnpm test:coverage
- name: Upload coverage report
if: matrix.node-version == '24.x'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
retention-days: 14
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,7 @@ package-lock.json
**/obj/*
**/nppBackup/*
.claude/

# Stryker mutation reports
reports/
.stryker-tmp/
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Changelog
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,27 @@ console.log(`Elements: ${report.elementsCount}`);
- [ADR](ADRs/) — Architecture Decision Records
- [Roadmap](roadmap.md) — планы развития

## Testing

Тестовый стек разделён на четыре уровня:

```bash
pnpm test # все unit + integration + e2e
pnpm test:unit # только unit
pnpm test:integration # интеграционные на реальных фикстурах
pnpm test:e2e # subprocess-тесты CLI через execa
pnpm test:coverage # с v8 coverage + порогами
pnpm test:mutation # Stryker mutation testing
```

**Метрики качества тестов:**

- **Coverage** (v8): порог в CI — statements ≥97%, branches ≥90%, functions ≥99%, lines ≥98%
- **Mutation score** (Stryker) ≥95% — каждое смысловое изменение в исходнике должно ломать хотя бы один тест
- **Property-based** (`@fast-check/vitest`) на option-bearing правилах — защита от «hardcoded literal where option should be read» бага
- **Inline snapshots** на генераторах для regression-pin'а формата вывода
- **E2E** на цепочке `init → check → fix → recheck` через `npx aact` в subprocess

## Публичные материалы

### Раз архитектура — «as Code», почему бы её не покрыть тестами?!
Expand Down
25 changes: 25 additions & 0 deletions changelog.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { ChangelogConfig } from "changelogen";

// changelogen — UnJS-aligned CHANGELOG.md generator from conventional commits.
// Workflow: write commits in conventional format (we have commitlint enforcing
// it), then run `pnpm release` to bump version, generate CHANGELOG section,
// commit, and tag.
//
// Reference: https://github.com/unjs/changelogen
export default <Partial<ChangelogConfig>>{
// Github org/repo for issue + PR + compare links in CHANGELOG.
repo: "Byndyusoft/aact",
// Hide author email in the contributors footer — keep CHANGELOG public-safe.
hideAuthorEmail: true,
// Sections in display order. Anything not listed is hidden.
types: {
feat: { title: "Features" },
perf: { title: "Performance" },
fix: { title: "Fixes" },
refactor: { title: "Refactors" },
docs: { title: "Documentation" },
test: { title: "Tests" },
build: { title: "Build" },
chore: { title: "Chore" },
},
};
77 changes: 75 additions & 2 deletions eslint.config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import js from "@eslint/js";
import eslintComments from "@eslint-community/eslint-plugin-eslint-comments/configs";
import vitest from "@vitest/eslint-plugin";
import citty from "eslint-plugin-citty";
import importX from "eslint-plugin-import-x";
import nodePlugin from "eslint-plugin-n";
import simpleImportSort from "eslint-plugin-simple-import-sort";
import sonarjs from "eslint-plugin-sonarjs";
import unicorn from "eslint-plugin-unicorn";
import globals from "globals";
import tseslint, { type ConfigArray } from "typescript-eslint";
import type { ConfigArray } from "typescript-eslint";
import tseslint from "typescript-eslint";

// eslint-disable-next-line sonarjs/deprecation -- tseslint.config() is the recommended API
export default tseslint.config(
{
ignores: ["dist/", "node_modules/", "resources/"],
ignores: [
"dist/",
"node_modules/",
"resources/",
"coverage/",
"reports/",
".stryker-tmp/",
"stryker.config.mjs",
],
},
js.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
Expand All @@ -27,6 +40,37 @@ export default tseslint.config(
unicorn.configs.recommended,
sonarjs.configs!.recommended as ConfigArray[number],
nodePlugin.configs["flat/recommended-module"],
eslintComments.recommended,
// citty plugin — narrowly scoped to src/cli/**. Cherry-picked rules
// that match our coding style without overlap with simple-import-sort/etc.
{
files: ["src/cli/**/*.ts"],
plugins: { citty },
rules: {
"citty/no-duplicated-version": "error",
"citty/valid-version": "error",
"citty/no-meaningless-value-hint": "warn",
"citty/no-hidden-root-command": "warn",
"citty/enum-must-have-options": "error",
"citty/must-have-run-or-sub-commands": "error",
"citty/no-empty-command-properties": "warn",
"citty/no-default-on-positional": "warn",
"citty/no-alias-on-positional": "error",
},
},
// eslint-plugin-import-x: cherry-pick value rules. Skip rules that
// require eslint-import-resolver-typescript (no-unresolved, default,
// namespace, no-duplicates — they need a resolver setup we don't have).
{
plugins: { "import-x": importX },
rules: {
"import-x/no-cycle": ["error", { maxDepth: 10 }],
"import-x/no-self-import": "error",
"import-x/consistent-type-specifier-style": ["error", "prefer-top-level"],
"import-x/first": "error",
"import-x/newline-after-import": "error",
},
},
{
plugins: {
"simple-import-sort": simpleImportSort,
Expand Down Expand Up @@ -64,6 +108,9 @@ export default tseslint.config(
// node plugin
"n/no-unsupported-features/node-builtins": "off",
"n/no-missing-import": "off",
// Tool config files (*.config.ts, changelog.config.ts, etc.) import
// from devDeps — that's expected, not "extraneous".
"n/no-extraneous-import": "off",

// sonarjs relaxations
"sonarjs/cognitive-complexity": "warn",
Expand All @@ -72,6 +119,24 @@ export default tseslint.config(
"sonarjs/slow-regex": "warn",
"sonarjs/prefer-regexp-exec": "warn",
"sonarjs/deprecation": "warn",

// eslint-comments: require justification for disable comments.
"@eslint-community/eslint-comments/no-unused-disable": "error",
},
},
// @vitest/eslint-plugin — cherry-pick high-value rules. Skip
// no-conditional-expect/no-standalone-expect — our property-based tests
// legitimately use both patterns.
{
files: ["test/**/*.test.ts", "examples/**/*.test.ts"],
plugins: { vitest },
rules: {
"vitest/no-focused-tests": "error",
"vitest/no-identical-title": "error",
"vitest/expect-expect": "warn",
"vitest/no-disabled-tests": "warn",
"vitest/prefer-to-be": "warn",
"vitest/valid-title": "warn",
},
},
{
Expand All @@ -85,8 +150,16 @@ export default tseslint.config(
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/restrict-template-expressions": "off",
"no-console": "off",
// vitest inline snapshots escape `$` inside backticks — we accept it.
"no-useless-escape": "off",
"sonarjs/no-duplicate-string": "off",
"sonarjs/cognitive-complexity": "off",
// Test fixtures use http URLs and small inline sorts; the rules are
// for production code, not test data.
"sonarjs/no-clear-text-protocols": "off",
"sonarjs/no-alphabetical-sort": "off",
"sonarjs/no-identical-functions": "off",
"unicorn/import-style": "off",
},
},
);
4 changes: 4 additions & 0 deletions examples/banking-plantuml/rules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,17 @@ describe("Rules demo on C4L2.puml", () => {

it("API Gateway — external calls go through gateway", () => {
const violations = checkApiGateway(containers);
// Informational: list violations for inspection. Banking fixture
// intentionally has gateway gaps to demo the rule's output.
expect(violations).toBeDefined();
for (const v of violations) {
console.log(`${v.container}: ${v.message}`);
}
});

it("Stable Dependencies — dependencies point toward stability", () => {
const violations = checkStableDependencies(containers);
expect(violations).toBeDefined();
for (const v of violations) {
console.log(`${v.container}: ${v.message}`);
}
Expand Down
22 changes: 22 additions & 0 deletions knip.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { KnipConfig } from "knip";

// Knip — finds unused exports/files/deps. Run via `pnpm knip`.
// Most config files are auto-detected; only non-default entries listed here.
export default <KnipConfig>{
entry: [
"test/**/*.test.ts",
"examples/**/*.test.ts",
"examples/**/aact.config.ts",
"vitest.mutation.config.ts",
],
project: ["src/**/*.ts", "test/**/*.ts", "examples/**/*.ts"],
ignoreDependencies: [
// Prettier plugin auto-loaded by prettier from name pattern; not imported.
"prettier-plugin-packagejson",
],
// stryker.config.mjs imports types from `@stryker-mutator/api/core` which
// is transitive via @stryker-mutator/core — knip flags as "unlisted".
// Ignoring the config from knip's analysis entirely is simpler than
// installing an extra devDep just for types.
ignore: ["stryker.config.mjs"],
};
45 changes: 33 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@
"scripts": {
"build": "unbuild",
"test": "vitest run",
"test:unit": "vitest run --project unit",
"test:integration": "vitest run --project integration",
"test:e2e": "vitest run --project e2e",
"test:coverage": "vitest run --coverage",
"test:mutation": "stryker run",
"changelog": "changelogen",
"release": "changelogen --release --push",
"knip": "knip",
"publint": "publint",
"lint": "npm run lint:eslint && npm run lint:prettier",
"lint:eslint": "eslint .",
"lint:prettier": "prettier --ignore-path ./.gitignore --check \"./**/*.{ts,js,json,yaml,yml,md}\"",
Expand All @@ -63,34 +72,46 @@
"devDependencies": {
"@commitlint/cli": "20.4.1",
"@commitlint/config-conventional": "20.4.1",
"@eslint-community/eslint-plugin-eslint-comments": "^4.7.1",
"@eslint/js": "^9",
"@types/node": "^20.19.32",
"@fast-check/vitest": "^0.4.1",
"@stryker-mutator/core": "^9.6.1",
"@stryker-mutator/vitest-runner": "^9.6.1",
"@types/node": "^22.10.0",
"@vitest/coverage-v8": "^4.1.6",
"@vitest/eslint-plugin": "^1.6.17",
"changelogen": "^0.6.2",
"eslint": "^9",
"eslint-plugin-citty": "^1.0.2",
"eslint-plugin-import-x": "^4.16.2",
"eslint-plugin-n": "^17",
"eslint-plugin-simple-import-sort": "^12",
"eslint-plugin-sonarjs": "^3",
"eslint-plugin-unicorn": "^62.0.0",
"globals": "^17.3.0",
"execa": "^9.6.1",
"globals": "^17.6.0",
"husky": "9.1.7",
"knip": "^6.12.2",
"lint-staged": "16.2.7",
"prettier": "3.8.1",
"prettier-plugin-packagejson": "3.0.0",
"prettier": "3.8.3",
"prettier-plugin-packagejson": "3.0.2",
"publint": "^0.3.20",
"typescript": "5.9.3",
"typescript-eslint": "^8",
"typescript-eslint": "^8.59.3",
"unbuild": "^3.0.0",
"vitest": "^4.0.18"
"vitest": "^4.1.6"
},
"engines": {
"node": ">=20"
"node": ">=22"
},
"dependencies": {
"c12": "4.0.0-beta.2",
"citty": "^0.2.0",
"citty": "^0.2.2",
"consola": "^3.4.2",
"jiti": "^2.6.1",
"picocolors": "^1.1.1",
"jiti": "^2.7.0",
"pathe": "^2.0.3",
"plantuml-parser": "0.4.0",
"valibot": "^1.2.0",
"yaml": "2.8.2"
"valibot": "^1.4.0",
"yaml": "2.9.0"
}
}
Loading
Loading