@vodmal/vdx-cli 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VoDmAl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # vdx-cli — Native predicate evaluator (v0.1)
2
+
3
+ Минимальный аудит-движок для рубрики `vdx-rubric-vodmal`. Шаг C плана
4
+ реализации (см. `../docs/decisions.md` D10 — нативный оценщик, не OPA).
5
+
6
+ ## Установка
7
+
8
+ ```bash
9
+ cd "$(realpath .)"
10
+ npm install
11
+ ```
12
+
13
+ ## Запуск
14
+
15
+ ```bash
16
+ npx tsx src/index.ts audit /path/to/project
17
+ # или
18
+ npm run audit -- /path/to/project --json
19
+ ```
20
+
21
+ По умолчанию рубрика читается из
22
+ `/Users/vdm/AI Projects/vdx-rubric-vodmal/vdx-rubric.yaml`. Override:
23
+ `--rubric /alt/path.yaml`.
24
+
25
+ ## Smoke-тест
26
+
27
+ ```bash
28
+ npm run smoke
29
+ ```
30
+
31
+ Прогоняет аудит на трёх калибровочных референсах
32
+ (telegram / t23b / bookmap). Ожидаемо все capped at L2 на оси `ci`.
33
+
34
+ ## Структура
35
+
36
+ - `src/rubric.ts` — типы + YAML-загрузчик
37
+ - `src/manifest.ts` — парсинг `[vdx]` блока в `mise.toml` + `.vdx-overrides.yml`
38
+ - `src/facts.ts` — fact source loaders (tasks, packages, configs)
39
+ - `src/predicates.ts` — registry предикатов (v0.1: 11 функций)
40
+ - `src/evaluator.ts` — рекурсивный evaluator + сахарная нотация
41
+ - `src/scoring.ts` — delta-style для уровней, flags для гибкой оси
42
+ - `src/audit.ts` — оркестратор: применяет overrides + считает уровень (D7)
43
+ - `src/report.ts` — markdown / JSON формат отчёта
44
+ - `src/index.ts` — CLI entry
45
+
46
+ ## Что НЕ реализовано в v0.1
47
+
48
+ - `config_value`, `command_succeeds` предикаты — заглушки с warning.
49
+ - Загрузка baseline из git-репо по `baseline:` ссылке — пока только local file.
50
+ - Watermark drift (фаза 2 из drift-algorithm.md).
51
+ - MCP-server обёртка (D6 — следующий шаг).
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const path = require('node:path');
4
+ const { register } = require('tsx/esm/api');
5
+ register();
6
+ import(path.join(__dirname, '..', 'src', 'mcp-server.ts')).catch((err) => {
7
+ console.error(err);
8
+ process.exit(1);
9
+ });
package/bin/vdx.cjs ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const path = require('node:path');
4
+ const { register } = require('tsx/esm/api');
5
+ register();
6
+ import(path.join(__dirname, '..', 'src', 'index.ts')).catch((err) => {
7
+ console.error(err);
8
+ process.exit(1);
9
+ });
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@vodmal/vdx-cli",
3
+ "version": "0.2.0",
4
+ "description": "vdx — unified lifecycle interface (up/down/build/test/check/fix) + versioned maturity audit with drift detection. Pairs with the vdx Claude Code plugin.",
5
+ "keywords": [
6
+ "vdx",
7
+ "lifecycle",
8
+ "audit",
9
+ "rubric",
10
+ "mise",
11
+ "claude-code",
12
+ "mcp",
13
+ "drift"
14
+ ],
15
+ "homepage": "https://github.com/VoDmAl/vdx#readme",
16
+ "bugs": "https://github.com/VoDmAl/vdx/issues",
17
+ "license": "MIT",
18
+ "author": "VoDmAl",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/VoDmAl/vdx.git",
22
+ "directory": "cli"
23
+ },
24
+ "type": "module",
25
+ "bin": {
26
+ "vdx": "./bin/vdx.cjs",
27
+ "vdx-mcp": "./bin/vdx-mcp.cjs"
28
+ },
29
+ "files": [
30
+ "src",
31
+ "rubric",
32
+ "bin",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "scripts": {
37
+ "vdx": "tsx src/index.ts",
38
+ "vdx-mcp": "tsx src/mcp-server.ts",
39
+ "audit": "tsx src/index.ts audit",
40
+ "smoke": "bash smoke.sh",
41
+ "typecheck": "tsc --noEmit",
42
+ "test": "tsc --noEmit"
43
+ },
44
+ "dependencies": {
45
+ "@modelcontextprotocol/sdk": "^1.29.0",
46
+ "js-yaml": "^4.1.0",
47
+ "smol-toml": "^1.3.0",
48
+ "tsx": "^4.19.0",
49
+ "zod": "^3.23.0"
50
+ },
51
+ "devDependencies": {
52
+ "@types/js-yaml": "^4.0.9",
53
+ "@types/node": "^22.0.0",
54
+ "typescript": "^5.6.0"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "engines": {
60
+ "node": ">=20.11"
61
+ }
62
+ }
@@ -0,0 +1,536 @@
1
+ # vdx-rubric.yaml — Personal maturity rubric (vodmal)
2
+ #
3
+ # Canonical published rubric. Format specification:
4
+ # https://github.com/vodmal/vdx/blob/main/docs/specs/rubric-format.md
5
+ #
6
+ # Семантика уровней — delta-style: levels.LN.requires описывает ТОЛЬКО
7
+ # дельту относительно L(N-1). Уровень оси = max L, для которого истинны
8
+ # requires(L1), ..., requires(L) подряд (без разрывов).
9
+
10
+ schema_version: "0.2"
11
+
12
+ metadata:
13
+ name: vdx-rubric-vodmal
14
+ version: "0.2.2"
15
+ description: "Personal maturity rubric for multi-stack projects (vodmal)"
16
+ owner: vodmal
17
+ homepage: "https://github.com/vodmal/vdx-rubric"
18
+
19
+ scoring:
20
+ formula: weighted_two_class
21
+ supporting_threshold: 0.8
22
+
23
+ levels:
24
+ L0: { name: chaos, description: "Не поднимается, знание в голове" }
25
+ L1: { name: reproducible, description: "Поднимается одной задокументированной командой" }
26
+ L2: { name: testable, description: "Тесты есть; единый словарь up/test/build" }
27
+ L3: { name: gated, description: "Статанализ + стиль + CI-гейты на PR (реальные!)" }
28
+ L4: { name: exemplar, description: "Полная глубина: хуки, mock-инфра, observability, версионированная общая инфра" }
29
+
30
+ axes:
31
+
32
+ # =========================================================================
33
+ # CRITICAL
34
+ # =========================================================================
35
+
36
+ - id: lifecycle-interface
37
+ class: critical
38
+ description: "Стандартный словарь глаголов up/down/build/test/check/fix"
39
+ storage: level
40
+ default_target: L4
41
+ fact_sources: [mise.toml, composer.json, package.json, Makefile]
42
+ levels:
43
+ L1:
44
+ requires:
45
+ any_of:
46
+ - has_file: docker-compose.yml
47
+ - has_file: docker-compose.yaml
48
+ - has_file: Makefile
49
+ - has_task_matching: ".+"
50
+ L2:
51
+ requires:
52
+ at_least_n_of:
53
+ n: 3
54
+ predicates:
55
+ - has_task: up
56
+ - has_task: down
57
+ - has_task: build
58
+ - has_task: test
59
+ - has_task: check
60
+ - has_task: fix
61
+ L3:
62
+ requires:
63
+ all_of:
64
+ - has_task: up
65
+ - has_task: down
66
+ - has_task: build
67
+ - has_task: test
68
+ - has_task: check
69
+ - has_task: fix
70
+ L4:
71
+ requires:
72
+ any_of:
73
+ - has_task_matching: "check:before:.*"
74
+ - has_task_matching: "build:.*"
75
+ - has_task_matching: "test:.*"
76
+
77
+ - id: tests
78
+ class: critical
79
+ description: "Наличие, разделение по типам, coverage, threshold"
80
+ applies_to: [php, node, go, python]
81
+ storage: level
82
+ default_target: L4
83
+ fact_sources:
84
+ - phpunit.xml.dist
85
+ - phpunit.xml
86
+ - jest.config.js
87
+ - vitest.config.ts
88
+ - package.json
89
+ - composer.json
90
+ - tests/
91
+ - playwright.config.ts
92
+ levels:
93
+ L1:
94
+ requires:
95
+ any_of:
96
+ - has_file: tests
97
+ - has_file: __tests__
98
+ - package_present: { name: phpunit/phpunit, ecosystem: composer }
99
+ - package_present: { name: jest, ecosystem: npm }
100
+ - package_present: { name: vitest, ecosystem: npm }
101
+ L2:
102
+ requires:
103
+ any_of:
104
+ - has_file: tests/Unit
105
+ - has_file: tests/unit
106
+ - file_contains: { path: phpunit.xml.dist, pattern: "<testsuite" }
107
+ - has_task: test:unit
108
+ L3:
109
+ requires:
110
+ any_of:
111
+ - file_contains: { path: phpunit.xml.dist, pattern: "<coverage>" }
112
+ - has_task: coverage
113
+ - file_contains: { path: package.json, pattern: "\"coverage\":" }
114
+ L4:
115
+ requires:
116
+ any_of:
117
+ - file_contains: { path: phpunit.xml.dist, pattern: "coverageMinimum|<report.+threshold" }
118
+ - has_task: test:mutation
119
+ - has_task: test:e2e
120
+ - has_file: playwright.config.ts
121
+
122
+ - id: static-analysis
123
+ class: critical
124
+ description: "Эффективная строгость (не nominal level) — D2 уточнение"
125
+ applies_to: [php, node, go, python]
126
+ storage: flags
127
+ default_target: L4
128
+ stack_implementations:
129
+ php:
130
+ flags:
131
+ - id: phpstan_present
132
+ weight: 1.0
133
+ predicate:
134
+ any_of:
135
+ - package_present: { name: phpstan/phpstan, ecosystem: composer }
136
+ - has_file: phpstan.neon
137
+ - has_file: phpstan.neon.dist
138
+ - has_file: phpstan.dist.neon
139
+ - id: phpstan_bleeding_edge
140
+ weight: 0.5
141
+ predicate: { file_contains: { path: phpstan.neon, pattern: "bleedingEdge.neon" } }
142
+ - id: phpstan_strict_rules
143
+ weight: 1.0
144
+ predicate: { package_present: { name: phpstan/phpstan-strict-rules, ecosystem: composer } }
145
+ - id: phpstan_symplify_rules
146
+ weight: 0.5
147
+ predicate: { package_present: { name: symplify/phpstan-rules, ecosystem: composer } }
148
+ - id: psalm_present
149
+ weight: 1.0
150
+ predicate: { package_present: { name: vimeo/psalm, ecosystem: composer } }
151
+ - id: type_coverage_active
152
+ weight: 0.5
153
+ predicate: { package_present: { name: tomasvotruba/type-coverage, ecosystem: composer } }
154
+ level_thresholds: { L4: 0.85, L3: 0.65, L2: 0.35, L1: 0.10 }
155
+ node:
156
+ flags:
157
+ - id: tsc_strict
158
+ weight: 1.0
159
+ predicate: { tsc_flag: { name: strict, equals: true } }
160
+ - id: no_unchecked_indexed_access
161
+ weight: 1.0
162
+ predicate: { tsc_flag: { name: noUncheckedIndexedAccess, equals: true } }
163
+ - id: exact_optional_property_types
164
+ weight: 0.5
165
+ predicate: { tsc_flag: { name: exactOptionalPropertyTypes, equals: true } }
166
+ - id: no_implicit_override
167
+ weight: 0.3
168
+ predicate: { tsc_flag: { name: noImplicitOverride, equals: true } }
169
+ - id: eslint_flat
170
+ weight: 1.0
171
+ predicate:
172
+ any_of:
173
+ - has_file: eslint.config.js
174
+ - has_file: eslint.config.mjs
175
+ - has_file: eslint.config.ts
176
+ - id: eslint_type_checked
177
+ weight: 1.0
178
+ predicate:
179
+ file_contains:
180
+ paths: [eslint.config.js, eslint.config.mjs, eslint.config.ts]
181
+ pattern: "recommended-type-checked"
182
+ level_thresholds: { L4: 0.85, L3: 0.65, L2: 0.35, L1: 0.10 }
183
+
184
+ - id: ci
185
+ class: critical
186
+ description: "Реальное гейтование PR (no `|| true`!)"
187
+ storage: level
188
+ default_target: L4
189
+ fact_sources:
190
+ - .github/workflows/
191
+ - .gitlab-ci.yml
192
+ - .circleci/config.yml
193
+ levels:
194
+ L1:
195
+ requires:
196
+ any_of:
197
+ - has_file: .github/workflows
198
+ - has_file: .gitlab-ci.yml
199
+ - has_file: .circleci/config.yml
200
+ L2:
201
+ requires:
202
+ any_of:
203
+ - file_contains:
204
+ paths: [.github/workflows/*.yml, .gitlab-ci.yml]
205
+ pattern: "(phpstan|phpunit|composer test|npm test|jest|pytest)"
206
+ L3:
207
+ requires:
208
+ all_of:
209
+ - any_of:
210
+ - gh_workflow_blocks_pr: {}
211
+ - file_contains: { path: .gitlab-ci.yml, pattern: "rules:.+merge_request" }
212
+ - not:
213
+ file_contains:
214
+ paths: [.github/workflows/*.yml, .gitlab-ci.yml]
215
+ pattern: "\\|\\| true"
216
+ L4:
217
+ requires:
218
+ any_of:
219
+ - file_contains: { path: .github/workflows/*.yml, pattern: "matrix:" }
220
+ - file_contains: { path: .github/workflows/*.yml, pattern: "sentry-release|action-release" }
221
+ - gh_workflow_blocks_pr: { check_name: "deploy" }
222
+
223
+ # =========================================================================
224
+ # SUPPORTING
225
+ # =========================================================================
226
+
227
+ - id: reproducibility
228
+ class: supporting
229
+ description: "Onboarding одной командой + pinned tool versions"
230
+ storage: level
231
+ default_target: L4
232
+ fact_sources: [docker-compose.yml, mise.toml, .tool-versions, .nvmrc, composer.json, package.json, README.md, flake.nix]
233
+ levels:
234
+ L1:
235
+ requires:
236
+ any_of:
237
+ - has_file: docker-compose.yml
238
+ - has_file: Dockerfile
239
+ - has_file: Makefile
240
+ L2:
241
+ requires:
242
+ any_of:
243
+ - config_value: { path: package.json, jsonpath: "engines.node", op: present }
244
+ - config_value: { path: composer.json, jsonpath: "require.php", op: present }
245
+ L3:
246
+ requires:
247
+ any_of:
248
+ - has_file: mise.toml
249
+ - has_file: .tool-versions
250
+ - has_file: .nvmrc
251
+ - has_file: flake.nix
252
+ - has_file: devbox.json
253
+ L4:
254
+ requires:
255
+ file_contains:
256
+ path: README.md
257
+ pattern: "(one-command|one command|vdx up|mise run up|\\bdocker compose up\\b)"
258
+
259
+ - id: code-style
260
+ class: supporting
261
+ applies_to: [php, node, go, python]
262
+ storage: level
263
+ default_target: L4
264
+ fact_sources: [ecs.php, .php-cs-fixer.php, phpcs.xml.dist, .prettierrc, biome.json, eslint.config.js]
265
+ levels:
266
+ L1:
267
+ requires:
268
+ has_file: .editorconfig
269
+ L2:
270
+ requires:
271
+ any_of:
272
+ - has_file: ecs.php
273
+ - has_file: .php-cs-fixer.php
274
+ - has_file: phpcs.xml.dist
275
+ - has_file: .prettierrc
276
+ - has_file: biome.json
277
+ - has_file: eslint.config.js
278
+ L3:
279
+ requires:
280
+ any_of:
281
+ - has_task: check:code:style
282
+ - has_task: lint
283
+ - has_task: check
284
+ - has_task: fix
285
+ L4:
286
+ requires:
287
+ any_of:
288
+ - all_of:
289
+ - has_file: ecs.php
290
+ - has_file: phpcs.xml.dist
291
+ - all_of:
292
+ - any_of: [{ has_file: .prettierrc }, { has_file: biome.json }]
293
+ - has_file: eslint.config.js
294
+
295
+ - id: dependency-hygiene
296
+ class: supporting
297
+ applies_to: [php, node, go, python]
298
+ storage: level
299
+ default_target: L4
300
+ fact_sources:
301
+ - composer.json
302
+ - composer.lock
303
+ - package.json
304
+ - package-lock.json
305
+ - pnpm-lock.yaml
306
+ - .github/dependabot.yml
307
+ - renovate.json
308
+ levels:
309
+ L1:
310
+ requires:
311
+ any_of:
312
+ - has_file: composer.lock
313
+ - has_file: package-lock.json
314
+ - has_file: pnpm-lock.yaml
315
+ - has_file: yarn.lock
316
+ - has_file: go.sum
317
+ L2:
318
+ requires:
319
+ any_of:
320
+ - package_present: { name: roave/security-advisories, ecosystem: composer }
321
+ - has_task: check:code:security
322
+ - file_contains: { path: .github/workflows/*.yml, pattern: "(composer audit|npm audit|pnpm audit)" }
323
+ L3:
324
+ requires:
325
+ any_of:
326
+ - has_file: renovate.json
327
+ - has_file: .github/dependabot.yml
328
+ - has_file: .renovaterc
329
+ - has_file: .renovaterc.json
330
+ L4:
331
+ requires:
332
+ any_of:
333
+ - package_present: { name: maglnet/composer-require-checker, ecosystem: composer }
334
+ - package_present: { name: deptrac/deptrac, ecosystem: composer }
335
+ - package_present: { name: knip, ecosystem: npm }
336
+ - package_present: { name: depcheck, ecosystem: npm }
337
+
338
+ - id: secrets-config
339
+ class: supporting
340
+ storage: level
341
+ default_target: L4
342
+ fact_sources: [.env.example, lintnet.jsonnet, .sops.yaml, .vault.yml]
343
+ levels:
344
+ L1:
345
+ requires:
346
+ any_of:
347
+ - has_file: .env
348
+ - has_file: .env.dev
349
+ L2:
350
+ requires:
351
+ has_file: .env.example
352
+ L3:
353
+ requires:
354
+ any_of:
355
+ - package_present: { name: dotenv-linter, ecosystem: any }
356
+ - has_file: lintnet.jsonnet
357
+ - has_task: check:config:env
358
+ L4:
359
+ requires:
360
+ any_of:
361
+ - has_file: .sops.yaml
362
+ - has_file: .vault.yml
363
+ - file_contains: { path: .gitlab-ci.yml, pattern: "(vault|sops)" }
364
+
365
+ - id: git-hygiene
366
+ class: supporting
367
+ storage: level
368
+ default_target: L4
369
+ fact_sources: [.husky/, .lefthook.yml, lefthook.yml, composer.json, package.json]
370
+ levels:
371
+ L1:
372
+ requires:
373
+ has_file: .gitignore
374
+ L2:
375
+ requires:
376
+ any_of:
377
+ - has_file: .husky
378
+ - has_file: .lefthook.yml
379
+ - has_file: lefthook.yml
380
+ - package_present: { name: brainmaestro/composer-git-hooks, ecosystem: composer }
381
+ - package_present: { name: husky, ecosystem: npm }
382
+ L3:
383
+ requires:
384
+ any_of:
385
+ - file_contains: { path: composer.json, pattern: "\"pre-push\"" }
386
+ - file_contains: { paths: [.lefthook.yml, lefthook.yml], pattern: "pre-push:" }
387
+ - has_file: .husky/pre-push
388
+ L4:
389
+ requires:
390
+ any_of:
391
+ - file_contains: { path: composer.json, pattern: "\"(post-commit|commit-msg|post-merge)\"" }
392
+ - file_contains: { paths: [.lefthook.yml, lefthook.yml], pattern: "(post-commit|commit-msg|post-merge):" }
393
+
394
+ - id: observability
395
+ class: supporting
396
+ storage: level
397
+ default_target: L4
398
+ fact_sources: [composer.json, package.json, .github/workflows/]
399
+ levels:
400
+ L1:
401
+ requires: { fn: always_true } # есть кодовая база — хотя бы print/console.log
402
+ L2:
403
+ requires:
404
+ any_of:
405
+ - package_present: { name: monolog/monolog, ecosystem: composer }
406
+ - package_present: { name: winston, ecosystem: npm }
407
+ - package_present: { name: pino, ecosystem: npm }
408
+ L3:
409
+ requires:
410
+ any_of:
411
+ - package_present: { name: sentry/sentry, ecosystem: composer }
412
+ - package_present: { name: sentry/sentry-symfony, ecosystem: composer }
413
+ - package_present: { name: '@sentry/node', ecosystem: npm }
414
+ - package_present: { name: '@sentry/browser', ecosystem: npm }
415
+ L4:
416
+ requires:
417
+ any_of:
418
+ - file_contains: { path: .github/workflows/*.yml, pattern: "(sentry.release|sentry-release|action-release)" }
419
+ - package_present: { name: cronitor/cronitor-php, ecosystem: composer }
420
+
421
+ - id: docs
422
+ class: supporting
423
+ storage: level
424
+ default_target: L4
425
+ fact_sources: [README.md, docs/, CHANGELOG.md, PROJECT_CHANGELOG.md, CLAUDE.md, AGENTS.md, docs/adr/]
426
+ levels:
427
+ L1:
428
+ requires:
429
+ has_file: README.md
430
+ L2:
431
+ requires:
432
+ has_file: docs
433
+ L3:
434
+ requires:
435
+ all_of:
436
+ - any_of:
437
+ - has_file: CLAUDE.md
438
+ - has_file: AGENTS.md
439
+ - any_of:
440
+ - has_file: CHANGELOG.md
441
+ - has_file: PROJECT_CHANGELOG.md
442
+ L4:
443
+ requires:
444
+ any_of:
445
+ - has_file: docs/adr
446
+ - has_file: docs/architecture.md
447
+ - has_file: docs/onboarding.md
448
+
449
+ - id: mock-infra
450
+ class: supporting
451
+ applies_to: [php, node, go, python]
452
+ storage: level
453
+ default_target: L4
454
+ fact_sources: [composer.json, package.json, docker-compose.yml, mock-server/]
455
+ levels:
456
+ L1:
457
+ requires: { fn: always_true }
458
+ L2:
459
+ requires:
460
+ any_of:
461
+ - package_present: { name: msw, ecosystem: npm }
462
+ - package_present: { name: nock, ecosystem: npm }
463
+ - package_present: { name: mcustiel/phiremock-client, ecosystem: composer }
464
+ - has_file: tests/Fixtures
465
+ L3:
466
+ requires:
467
+ any_of:
468
+ - all_of:
469
+ - has_file: docker-compose.yml
470
+ - file_contains: { path: docker-compose.yml, pattern: "mock[-_]?[a-zA-Z0-9_-]*(server|api|service)" }
471
+ - has_file: mock-server
472
+ - package_present: { name: mcustiel/phiremock-server, ecosystem: composer }
473
+ L4:
474
+ requires:
475
+ any_of:
476
+ - file_contains: { path: .gitlab-ci.yml, pattern: "(validate.*production-config|prod.*mock)" }
477
+ - file_contains: { path: .github/workflows/*.yml, pattern: "(validate-no-mock-in-prod|prod-mock-check)" }
478
+
479
+ - id: shared-infra
480
+ class: supporting
481
+ description: "Качество подхода + автопровижининг (D8)"
482
+ storage: level
483
+ default_target: L4
484
+ fact_sources: [docker-compose.yml, misc/traefik-global/, scripts/, mise.toml]
485
+ levels:
486
+ L1:
487
+ requires:
488
+ file_contains: { path: docker-compose.yml, pattern: "ports:" }
489
+ L2:
490
+ requires:
491
+ any_of:
492
+ - has_file: misc/traefik-global
493
+ - file_contains: { path: docker-compose.yml, pattern: "traefik" }
494
+ - file_contains: { path: docker-compose.yml, pattern: "networks:.+external" }
495
+ L3:
496
+ requires:
497
+ any_of:
498
+ - file_contains: { path: scripts/docker-up.sh, pattern: "(traefik|nginx-proxy)" }
499
+ - config_value: { path: mise.toml, jsonpath: "vdx.shared_infra.precheck", equals: true }
500
+ L4:
501
+ requires:
502
+ any_of:
503
+ - config_value:
504
+ path: mise.toml
505
+ jsonpath: "vdx.shared_infra.provider"
506
+ op: matches
507
+ pattern: "github.com/.+@v\\d+"
508
+ - file_contains:
509
+ paths: [scripts/*.sh]
510
+ pattern: "git clone.+traefik|mise install.+traefik"
511
+
512
+ - id: shared-infra-drift
513
+ class: supporting
514
+ description: "Подось shared-infra. Применимо только если shared-infra ≤ L3 (есть локальная копия)"
515
+ storage: level
516
+ default_target: L3
517
+ fact_sources: [misc/traefik-global/README.md, misc/traefik-global/start.sh]
518
+ levels:
519
+ L1:
520
+ requires:
521
+ has_file: misc/traefik-global
522
+ L2:
523
+ requires:
524
+ file_contains:
525
+ paths: [misc/traefik-global/README.md, misc/traefik-global/start.sh]
526
+ pattern: "Based on:"
527
+ L3:
528
+ requires:
529
+ file_contains:
530
+ paths: [misc/traefik-global/README.md, misc/traefik-global/start.sh]
531
+ pattern: "Based on:.+@[a-f0-9]{7,40}" # commit hash pointer
532
+ L4:
533
+ requires:
534
+ file_contains:
535
+ paths: [misc/traefik-global/README.md, misc/traefik-global/start.sh]
536
+ pattern: "@v\\d+\\.\\d+\\.\\d+" # semver pin