@nitra/cursor 1.13.15 → 1.13.26

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.
Files changed (77) hide show
  1. package/.claude-template/hooks/normalize-decisions.sh +1 -1
  2. package/CHANGELOG.md +85 -0
  3. package/package.json +1 -1
  4. package/rules/abie/abie.mdc +1 -4
  5. package/rules/abie/policy/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +21 -40
  6. package/rules/abie/policy/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml +9 -0
  7. package/rules/adr/adr.mdc +2 -2
  8. package/rules/docker/docker.mdc +2 -50
  9. package/rules/docker/policy/lint_docker_yml/lint_docker_yml.rego +33 -47
  10. package/rules/docker/policy/lint_docker_yml/template/lint-docker.yml.snippet.yml +41 -0
  11. package/rules/docker/policy/package_json/package_json.rego +11 -28
  12. package/rules/docker/policy/package_json/template/package.json.snippet.json +1 -0
  13. package/rules/image-avif/policy/package_json/package_json.rego +21 -35
  14. package/rules/image-avif/policy/package_json/template/package.json.deny.json +5 -0
  15. package/rules/image-compress/image-compress.mdc +2 -8
  16. package/rules/image-compress/policy/package_json/package_json.rego +24 -65
  17. package/rules/image-compress/policy/package_json/template/package.json.contains.json +5 -0
  18. package/rules/image-compress/policy/package_json/template/package.json.deny.json +8 -0
  19. package/rules/js-bun-db/policy/package_json/package_json.rego +8 -21
  20. package/rules/js-bun-db/policy/package_json/template/package.json.deny.json +7 -0
  21. package/rules/js-bun-redis/policy/package_json/package_json.rego +8 -30
  22. package/rules/js-bun-redis/policy/package_json/template/package.json.deny.json +12 -0
  23. package/rules/js-lint/policy/jscpd/jscpd.rego +29 -23
  24. package/rules/js-lint/policy/jscpd/template/.jscpd.json.snippet.json +6 -0
  25. package/rules/js-lint/policy/lint_js_yml/lint_js_yml.rego +39 -47
  26. package/rules/js-lint/policy/lint_js_yml/template/lint-js.yml.snippet.yml +44 -0
  27. package/rules/js-lint/policy/package_json/package_json.rego +22 -42
  28. package/rules/js-lint/policy/package_json/template/package.json.snippet.json +6 -0
  29. package/rules/js-lint/policy/vscode_extensions/template/extensions.json.snippet.json +7 -0
  30. package/rules/js-lint/policy/vscode_extensions/vscode_extensions.rego +4 -17
  31. package/rules/js-run/policy/configmap/configmap.rego +11 -35
  32. package/rules/js-run/policy/configmap/template/configmap.yaml.contains.yml +4 -0
  33. package/rules/js-run/policy/jsconfig/jsconfig.rego +39 -46
  34. package/rules/js-run/policy/jsconfig/template/jsconfig.json.snippet.json +10 -0
  35. package/rules/js-run/policy/package_json/package_json.rego +10 -21
  36. package/rules/js-run/policy/package_json/template/package.json.deny.json +10 -0
  37. package/rules/php/php.mdc +2 -56
  38. package/rules/php/policy/lint_php_yml/lint_php_yml.rego +15 -13
  39. package/rules/php/policy/lint_php_yml/template/lint-php.yml.snippet.yml +47 -0
  40. package/rules/php/policy/package_json/package_json.rego +9 -12
  41. package/rules/php/policy/package_json/template/package.json.contains.json +5 -0
  42. package/rules/security/fix/trufflehog/check.mjs +45 -0
  43. package/rules/security/fix/trufflehog/template/.trufflehog-exclude.snippet.txt +6 -0
  44. package/rules/security/policy/lint_security_yml/lint_security_yml.rego +33 -0
  45. package/rules/security/policy/lint_security_yml/target.json +8 -0
  46. package/rules/security/policy/lint_security_yml/template/lint-security.yml.snippet.yml +31 -0
  47. package/rules/security/policy/package_json/template/package.json.deny.json +2 -2
  48. package/rules/security/policy/package_json/template/package.json.snippet.json +1 -1
  49. package/rules/security/security.mdc +17 -38
  50. package/rules/style-lint/policy/lint_style_yml/lint_style_yml.rego +14 -16
  51. package/rules/style-lint/policy/lint_style_yml/template/lint-style.yml.snippet.yml +39 -0
  52. package/rules/style-lint/policy/package_json/package_json.rego +22 -30
  53. package/rules/style-lint/policy/package_json/template/package.json.contains.json +5 -0
  54. package/rules/style-lint/policy/package_json/template/package.json.snippet.json +5 -0
  55. package/rules/style-lint/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
  56. package/rules/style-lint/policy/vscode_extensions/vscode_extensions.rego +5 -15
  57. package/rules/style-lint/policy/vscode_settings/template/settings.json.snippet.json +5 -0
  58. package/rules/style-lint/policy/vscode_settings/vscode_settings.rego +7 -16
  59. package/rules/text/policy/cspell/cspell.rego +39 -59
  60. package/rules/text/policy/cspell/template/.cspell.json.contains.json +3 -0
  61. package/rules/text/policy/cspell/template/.cspell.json.deny.json +5 -0
  62. package/rules/text/policy/cspell/template/.cspell.json.snippet.json +12 -0
  63. package/rules/text/policy/markdownlint/markdownlint.rego +37 -50
  64. package/rules/text/policy/markdownlint/template/.markdownlint-cli2.jsonc.snippet.jsonc +11 -0
  65. package/rules/text/policy/oxfmtrc/oxfmtrc.rego +23 -58
  66. package/rules/text/policy/oxfmtrc/template/.oxfmtrc.json.snippet.json +12 -0
  67. package/rules/text/policy/package_json/package_json.rego +14 -52
  68. package/rules/text/policy/package_json/template/package.json.deny.json +15 -0
  69. package/rules/text/policy/vscode_extensions/template/extensions.json.snippet.json +7 -0
  70. package/rules/text/policy/vscode_extensions/vscode_extensions.rego +4 -28
  71. package/rules/text/policy/vscode_settings/template/settings.json.snippet.json +9 -0
  72. package/rules/text/policy/vscode_settings/vscode_settings.rego +20 -42
  73. package/skills/adr-normalize/SKILL.md +2 -2
  74. package/rules/security/fix/gitleaks/check.mjs +0 -25
  75. package/rules/security/fix/gitleaks/template/.gitleaks.toml.snippet.toml +0 -12
  76. package/rules/security/policy/gitleaks/gitleaks.rego +0 -17
  77. package/rules/security/policy/gitleaks/target.json +0 -8
@@ -1,58 +1,37 @@
1
1
  ---
2
- description: Локальний та CI-секюріті-лінт через gitleaks — скрипт `lint-security`, `.gitleaks.toml`, інтеграція в агрегований `lint`
3
- globs: "**/.gitleaks.toml,**/package.json,**/.github/workflows/**/*.yml"
2
+ description: Локальний та CI-секюріті-лінт через TruffleHog — скрипт `lint-security`, `.trufflehog-exclude`, інтеграція в агрегований `lint`
3
+ globs: "**/.trufflehog-exclude,**/package.json,**/.github/workflows/**/*.yml"
4
4
  alwaysApply: false
5
- version: '1.1'
5
+ version: '2.0'
6
6
  ---
7
7
 
8
- [gitleaks](https://github.com/gitleaks/gitleaks) — глобальний CLI (як `shellcheck`, `conftest`); **не** додавай до `dependencies`/`devDependencies`.
8
+ [TruffleHog](https://github.com/trufflesecurity/trufflehog) — глобальний CLI (як `shellcheck`, `conftest`); **не** додавай до `dependencies`/`devDependencies`.
9
9
 
10
10
  ## Канон `package.json#scripts`
11
11
 
12
12
  - `lint-security` скрипт: [package.json.snippet.json](./policy/package_json/template/package.json.snippet.json)
13
13
  - `lint` агрегатор повинен містити: [package.json.contains.json](./policy/package_json/template/package.json.contains.json)
14
- - Заборонено `gitleaks` у `dependencies`/`devDependencies`: [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
14
+ - Заборонено `trufflehog` у `dependencies`/`devDependencies`: [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
15
15
 
16
16
  **Зауваження:**
17
17
 
18
- - `gitleaks detect` — сканує робоче дерево (uncommitted + tracked); швидше і безпечніше для частого `bun run lint`, ніж `gitleaks git`.
19
- - `--no-banner` — прибирає ASCII-арт (CI-friendly).
18
+ - `trufflehog filesystem .` — сканує робоче дерево як директорію (включно з untracked/gitignored файлами); підкоманда `git file://.` лишається на CI для аудиту історії.
19
+ - `--no-update` — вимикає self-update check (CI-friendly).
20
+ - `--exclude-paths .trufflehog-exclude` — файл з regex-patterns, які треба пропускати (аналог `[allowlist].paths` із gitleaks).
21
+ - `--results=verified,unknown` — показує лише верифіковані секрети + ті, що TruffleHog не зміг перевірити (`unverified` дублікат відсіюється).
22
+ - `--fail` — exit-code `183` за наявності знахідок (потрібно, щоб `bun run lint` падав).
20
23
  - Позиція в `lint`: за конвенцією після інших `lint-*` і перед `oxfmt`.
21
24
 
22
- ## `.gitleaks.toml` (рекомендована основа)
25
+ ## `.trufflehog-exclude` (рекомендована основа)
23
26
 
24
- Канон (допускає розширення в `[allowlist].paths`): [.gitleaks.toml.snippet.toml](./fix/gitleaks/template/.gitleaks.toml.snippet.toml)
27
+ Канон (допускає розширення): [.trufflehog-exclude.snippet.txt](./fix/trufflehog/template/.trufflehog-exclude.snippet.txt)
25
28
 
26
- **Важливо:** `useDefault = true` НЕ перетирай дефолтний `rules`-масив; реальні винятки лише через `[allowlist]`.
29
+ **Важливо:** один regex-pattern на рядок, без TOML-обгортки; коментарі починаються з `#`.
27
30
 
28
- ## GitHub Actions (опційно)
31
+ ## CI: `.github/workflows/lint-security.yml`
29
32
 
30
- Окремий workflow не обовʼязковий — `lint-security` уже виконується через агрегований `lint`. Для fast-feedback окремо:
33
+ Workflow обовʼязковий — забезпечує незалежний скан секретів на push/PR (агрегований `lint` локально + окремий fail-fast job на CI).
31
34
 
32
- ```yaml title=".github/workflows/lint-security.yml"
33
- name: Lint Security
35
+ - Канон: [lint-security.yml.snippet.yml](./policy/lint_security_yml/template/lint-security.yml.snippet.yml)
34
36
 
35
- on:
36
- push:
37
- branches: [dev, main]
38
- pull_request:
39
- branches: [dev, main]
40
-
41
- concurrency:
42
- group: ${{ github.ref }}-${{ github.workflow }}
43
- cancel-in-progress: true
44
-
45
- jobs:
46
- security:
47
- runs-on: ubuntu-latest
48
- permissions:
49
- contents: read
50
- steps:
51
- - uses: actions/checkout@v6
52
- with:
53
- persist-credentials: false
54
- fetch-depth: 0
55
- - uses: gitleaks/gitleaks-action@v2
56
- ```
57
-
58
- Для повного скану git-історії потрібен `fetch-depth: 0`.
37
+ Перевіряється policy `security.lint_security_yml`: серед `uses:` має бути крок з `trufflesecurity/trufflehog@main`. Універсальні workflow-перевірки (checkout, permissions, persist-credentials) — у `ga.workflow_common`. Для повного скану історії потрібен `fetch-depth: 0`.
@@ -1,21 +1,19 @@
1
- # Порт перевірки `lint-style.yml` з `npm/scripts/check-style-lint.mjs` (style-lint.mdc).
1
+ # Перевірка `lint-style.yml` (style-lint.mdc).
2
2
  #
3
- # Запуск (локально):
4
- # conftest test .github/workflows/lint-style.yml -p npm/policy/style_lint \
5
- # --namespace style_lint.lint_style_yml
6
- #
7
- # Перевіряє: хоча б один крок `run` містить `npx stylelint` (саме через npx, не
8
- # `bun run lint-style`). Універсальні workflow-перевірки (concurrency, заборонені
9
- # setup-bun/cache/install) — у `ga.workflow_common`.
10
- #
11
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
12
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
13
- # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/lint-style.yml.snippet.yml.
5
+ # Маркер `run:` (npx stylelint substring) збирається з template's stylelint-job steps.
6
+ # Універсальні workflow-перевірки — у `ga.workflow_common`.
14
7
  package style_lint.lint_style_yml
15
8
 
16
9
  import rego.v1
17
10
 
18
- # Усі тексти `run:` зі steps усіх jobs, склеєні в один blob — для substring-перевірки.
11
+ expected_run_blob := concat("\n", [r |
12
+ some step in data.template.snippet.jobs.stylelint.steps
13
+ r := object.get(step, "run", "")
14
+ r != ""
15
+ ])
16
+
19
17
  all_run_text := concat("\n", [run_text |
20
18
  some job in object.get(input, "jobs", {})
21
19
  some step in object.get(job, "steps", [])
@@ -23,11 +21,11 @@ all_run_text := concat("\n", [run_text |
23
21
  ])
24
22
 
25
23
  deny contains msg if {
26
- not contains(all_run_text, "npx stylelint")
27
- msg := "lint-style.yml: жоден крок run не містить `npx stylelint` (style-lint.mdc)"
24
+ expected_run_blob != ""
25
+ not contains(all_run_text, expected_run_blob)
26
+ msg := sprintf("lint-style.yml: жоден крок run не містить %q (style-lint.mdc)", [expected_run_blob])
28
27
  }
29
28
 
30
- # Текст `run:` як один рядок: підтримує string і array форми (YAML).
31
29
  step_run_to_text(step) := step.run if is_string(step.run)
32
30
 
33
31
  else := concat("\n", [s | some s in step.run]) if is_array(step.run)
@@ -0,0 +1,39 @@
1
+ name: StyleLint
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - dev
7
+ - main
8
+ paths:
9
+ - '**/*.css'
10
+ - '**/*.scss'
11
+ - '**/*.vue'
12
+
13
+ pull_request:
14
+ branches:
15
+ - dev
16
+ - main
17
+ paths:
18
+ - '**/*.css'
19
+ - '**/*.scss'
20
+ - '**/*.vue'
21
+
22
+ concurrency:
23
+ group: ${{ github.ref }}-${{ github.workflow }}
24
+ cancel-in-progress: true
25
+
26
+ jobs:
27
+ stylelint:
28
+ runs-on: ubuntu-latest
29
+ permissions:
30
+ contents: read
31
+ steps:
32
+ - uses: actions/checkout@v6
33
+ with:
34
+ persist-credentials: false
35
+
36
+ - uses: ./.github/actions/setup-bun-deps
37
+
38
+ - name: StyleLint
39
+ run: npx stylelint '**/*.{css,scss,vue}' --fix
@@ -1,49 +1,41 @@
1
- # Порт перевірок `package.json` з `npm/scripts/check-style-lint.mjs` (style-lint.mdc).
1
+ # Порт перевірок `package.json` (style-lint.mdc).
2
2
  #
3
- # Запуск (локально):
4
- # conftest test package.json -p npm/policy/style_lint --namespace style_lint.package_json
3
+ # Канон надходить через --data: { "template": { "contains": ..., "snippet": ... } }
4
+ # Структура --data сформована з template/package.json.{contains,snippet}.json.
5
+ # FS-альтернативи (`.stylelintrc.*` файли) + `.stylelintignore` — у JS.
5
6
  #
6
- # Перевіряє: наявність скрипта `lint-style` з `npx stylelint`, `@nitra/stylelint-config`
7
- # у `devDependencies`, поле `stylelint.extends == "@nitra/stylelint-config"`. FS-частина
8
- # (зовнішні `.stylelintrc.*` як альтернатива полю; `.stylelintignore`) лишається у JS.
9
- #
10
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
11
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
12
- # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
7
+ # Логіка, що ЛИШАЄТЬСЯ у rego (inverse не виноситься у template):
8
+ # - `@nitra/stylelint-config` має бути у devDependencies (presence-check).
13
9
  package style_lint.package_json
14
10
 
15
11
  import rego.v1
16
12
 
17
- # ── deny: lint-style скрипт ───────────────────────────────────────────────
13
+ # ── deny: substring requirements у scripts (contains) ────────────────────
18
14
 
19
15
  deny contains msg if {
20
- not object.get(object.get(input, "scripts", {}), "lint-style", false)
21
- msg := "package.json не містить скрипт \"lint-style\" (style-lint.mdc)"
16
+ some script_name, needles in data.template.contains.scripts
17
+ actual := object.get(object.get(input, "scripts", {}), script_name, "")
18
+ some needle in needles
19
+ not contains(actual, needle)
20
+ msg := sprintf("package.json: scripts.%s має містити %q (style-lint.mdc)", [script_name, needle])
22
21
  }
23
22
 
23
+ # ── deny: 2-level snippet walker (для stylelint.extends, якщо поле є) ────
24
+
24
25
  deny contains msg if {
25
- lint_style := object.get(object.get(input, "scripts", {}), "lint-style", "")
26
- lint_style != ""
27
- not contains(lint_style, "npx stylelint")
28
- msg := sprintf("lint-style має викликати stylelint через npx (зараз: %q) (style-lint.mdc)", [lint_style])
26
+ some section, expected_inner in data.template.snippet
27
+ cfg := object.get(input, section, null)
28
+ is_object(cfg)
29
+ some leaf_key, expected_value in expected_inner
30
+ actual := object.get(cfg, leaf_key, null)
31
+ actual != expected_value
32
+ msg := sprintf("package.json: %s.%s має бути %q (style-lint.mdc)", [section, leaf_key, expected_value])
29
33
  }
30
34
 
31
- # ── deny: @nitra/stylelint-config у devDependencies ───────────────────────
35
+ # ── deny: @nitra/stylelint-config у devDependencies (inverse) ────────────
32
36
 
33
37
  deny contains msg if {
34
38
  dev := object.get(input, "devDependencies", {})
35
39
  not "@nitra/stylelint-config" in object.keys(dev)
36
40
  msg := "@nitra/stylelint-config відсутній — bun add -d @nitra/stylelint-config (style-lint.mdc)"
37
41
  }
38
-
39
- # ── deny: поле stylelint.extends = "@nitra/stylelint-config" ──────────────
40
- #
41
- # JS-перевірка дозволяє альтернативу: окремий файл `.stylelintrc.*`. Цю частину
42
- # перевіряємо в JS (FS-вибірка); тут — лише структурна валідація поля, якщо воно є.
43
-
44
- deny contains msg if {
45
- cfg := object.get(input, "stylelint", null)
46
- is_object(cfg)
47
- object.get(cfg, "extends", null) != "@nitra/stylelint-config"
48
- msg := "package.json: stylelint.extends має бути \"@nitra/stylelint-config\" (style-lint.mdc)"
49
- }
@@ -0,0 +1,5 @@
1
+ {
2
+ "scripts": {
3
+ "lint-style": ["npx stylelint"]
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "stylelint": {
3
+ "extends": "@nitra/stylelint-config"
4
+ }
5
+ }
@@ -0,0 +1 @@
1
+ { "recommendations": ["stylelint.vscode-stylelint"] }
@@ -1,23 +1,13 @@
1
1
  # Перевірка `.vscode/extensions.json` для style-lint (style-lint.mdc).
2
2
  #
3
- # Запуск (локально):
4
- # conftest test .vscode/extensions.json -p npm/policy/style_lint/vscode_extensions \
5
- # --namespace style_lint.vscode_extensions
6
- #
7
- # Canonical (style-lint.mdc):
8
- # { "recommendations": ["stylelint.vscode-stylelint"] }
9
- #
10
- # Канон задає мінімум — `recommendations` має МІСТИТИ `stylelint.vscode-stylelint`;
11
- # додаткові записи (від інших правил — markdownlint, oxc тощо) дозволені.
12
- #
13
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
14
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`.
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/extensions.json.snippet.json.
15
5
  package style_lint.vscode_extensions
16
6
 
17
7
  import rego.v1
18
8
 
19
9
  deny contains msg if {
20
- recs := object.get(input, "recommendations", [])
21
- not "stylelint.vscode-stylelint" in {r | some r in recs}
22
- msg := ".vscode/extensions.json: recommendations має містити \"stylelint.vscode-stylelint\" (style-lint.mdc)"
10
+ some rec in data.template.snippet.recommendations
11
+ not rec in {r | some r in object.get(input, "recommendations", [])}
12
+ msg := sprintf(".vscode/extensions.json: recommendations має містити %q (style-lint.mdc)", [rec])
23
13
  }
@@ -0,0 +1,5 @@
1
+ {
2
+ "css.validate": false,
3
+ "less.validate": false,
4
+ "scss.validate": false
5
+ }
@@ -1,24 +1,15 @@
1
1
  # Перевірка `.vscode/settings.json` для style-lint (style-lint.mdc).
2
2
  #
3
- # Запуск (локально):
4
- # conftest test .vscode/settings.json -p npm/policy/style_lint/vscode_settings \
5
- # --namespace style_lint.vscode_settings
6
- #
7
- # Canonical (style-lint.mdc): вимкнути вбудовану валідацію CSS/SCSS/Less, щоб
8
- # stylelint був єдиним джерелом діагностики.
9
- # { "css.validate": false, "less.validate": false, "scss.validate": false }
10
- #
11
- # `editor.codeActionsOnSave` у каноні є, але це smell-test — навмисно не deny,
12
- # щоб не падати на пакетах, які мають свій codeActionsOnSave-конфіг.
13
- #
14
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
15
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`.
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/settings.json.snippet.json.
5
+ # Top-level літеральні keys — leaf-by-leaf walker.
16
6
  package style_lint.vscode_settings
17
7
 
18
8
  import rego.v1
19
9
 
20
10
  deny contains msg if {
21
- some key in {"css.validate", "less.validate", "scss.validate"}
22
- object.get(input, key, null) != false
23
- msg := sprintf(".vscode/settings.json: \"%s\" має бути false (style-lint.mdc)", [key])
11
+ some key, expected_value in data.template.snippet
12
+ actual := object.get(input, key, null)
13
+ actual != expected_value
14
+ msg := sprintf(".vscode/settings.json: \"%s\" має бути %v (style-lint.mdc)", [key, expected_value])
24
15
  }
@@ -1,91 +1,71 @@
1
- # Порт перевірок `.cspell.json` з `npm/scripts/check-text.mjs` (text.mdc).
1
+ # Перевірка `.cspell.json` (text.mdc).
2
2
  #
3
- # Запуск (локально):
4
- # conftest test .cspell.json -p npm/policy/text --namespace text.cspell
3
+ # Канон надходить через --data: { "template": { "snippet": ..., "contains": ..., "deny": ... } }
4
+ # Структура --data сформована з template/.cspell.json.{snippet,contains,deny}.json.
5
5
  #
6
- # Перевіряє: `version: "0.2"`, наявність `language`, імпорт `@nitra/cspell-dict`,
7
- # відсутність прямих імпортів `@cspell/dict-*`, обовʼязкові glob-и в `ignorePaths`
8
- # (text.mdc).
9
- #
10
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
11
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
12
- # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
6
+ # Логіка, що ЛИШАЄТЬСЯ у rego (inverse — не виноситься у template):
7
+ # - `language` має бути присутнє (presence-only).
13
8
  package text.cspell
14
9
 
15
10
  import rego.v1
16
11
 
17
- # ── Очікувані значення ─────────────────────────────────────────────────────
12
+ # ── deny: top-level snippet leafs (version etc) ──────────────────────────
18
13
 
19
- # Канонічні `ignorePaths` з text.mdc — кожен має бути присутнім.
20
- required_ignore_paths := {
21
- "**/node_modules/**",
22
- "**/vscode-extension/**",
23
- "**/.git/**",
24
- ".vscode",
25
- "report",
26
- "*.svg",
27
- "**/k8s/**/*.yaml",
14
+ deny contains msg if {
15
+ some key, expected_value in data.template.snippet
16
+ not is_array(expected_value)
17
+ not is_object(expected_value)
18
+ actual := object.get(input, key, null)
19
+ actual != expected_value
20
+ msg := sprintf(".cspell.json: %s має бути %v (text.mdc)", [key, expected_value])
28
21
  }
29
22
 
30
- nitra_cspell_dict_marker := "@nitra/cspell-dict"
31
-
32
- legacy_dict_marker := "@cspell/dict-"
33
-
34
- # Шаблон повідомлення про заборонений імпорт `@cspell/dict-*` — через `concat`
35
- # для regal style/line-length.
36
- legacy_dict_import_template := concat(" ", [
37
- ".cspell.json не має імпортувати @cspell/dict-* —",
38
- "використовуй лише @nitra/cspell-dict (знайдено: %s) (text.mdc)",
39
- ])
40
-
41
- # ── deny: version / language ──────────────────────────────────────────────
23
+ # ── deny: ignorePaths subset-of ──────────────────────────────────────────
42
24
 
43
25
  deny contains msg if {
44
- object.get(input, "version", null) != "0.2"
45
- msg := ".cspell.json: version має бути \"0.2\" (text.mdc)"
26
+ some field, expected_values in data.template.snippet
27
+ is_array(expected_values)
28
+ is_array(object.get(input, field, null))
29
+ actual_set := {v | some v in input[field]}
30
+ some required in expected_values
31
+ not required in actual_set
32
+ msg := sprintf(".cspell.json %s: додай %q (text.mdc)", [field, required])
46
33
  }
47
34
 
35
+ # ── deny: language presence (inverse, in rego) ───────────────────────────
36
+
48
37
  deny contains msg if {
49
38
  not object.get(input, "language", false)
50
39
  msg := ".cspell.json: відсутнє поле language (text.mdc)"
51
40
  }
52
41
 
53
- # ── deny: imports ─────────────────────────────────────────────────────────
42
+ # ── deny: import substrings required (contains) ─────────────────────────
54
43
 
55
44
  deny contains msg if {
56
- imports := object.get(input, "import", [])
45
+ some field, needles in data.template.contains
46
+ imports := object.get(input, field, [])
57
47
  is_array(imports)
58
- not has_nitra_dict_import(imports)
59
- msg := ".cspell.json не імпортує @nitra/cspell-dict/cspell-ext.json (text.mdc)"
48
+ some needle in needles
49
+ not has_substring_in_array(imports, needle)
50
+ msg := sprintf(".cspell.json: %s має містити %q (text.mdc)", [field, needle])
60
51
  }
61
52
 
53
+ # ── deny: import substrings forbidden ────────────────────────────────────
54
+
62
55
  deny contains msg if {
56
+ some forbidden, reason in data.template.deny["import-substrings"]
63
57
  imports := object.get(input, "import", [])
64
58
  is_array(imports)
65
59
  some imp in imports
66
60
  is_string(imp)
67
- contains(imp, legacy_dict_marker)
68
- msg := sprintf(legacy_dict_import_template, [imp])
61
+ contains(imp, forbidden)
62
+ msg := sprintf(".cspell.json import містить заборонений %q — %s", [imp, reason])
69
63
  }
70
64
 
71
- # ── deny: ignorePaths ─────────────────────────────────────────────────────
65
+ # ── helpers ──────────────────────────────────────────────────────────────
72
66
 
73
- deny contains msg if {
74
- not is_array(object.get(input, "ignorePaths", null))
75
- msg := ".cspell.json: додай масив ignorePaths з канонічними glob-ами (text.mdc)"
76
- }
77
-
78
- deny contains msg if {
79
- is_array(input.ignorePaths)
80
- some path in required_ignore_paths
81
- not path in {p | some p in input.ignorePaths}
82
- msg := sprintf(".cspell.json ignorePaths: додай %q (text.mdc)", [path])
83
- }
84
-
85
- # ── helpers ────────────────────────────────────────────────────────────────
86
-
87
- has_nitra_dict_import(imports) if {
88
- some imp in imports
89
- is_string(imp)
90
- contains(imp, nitra_cspell_dict_marker)
67
+ has_substring_in_array(arr, needle) if {
68
+ some item in arr
69
+ is_string(item)
70
+ contains(item, needle)
91
71
  }
@@ -0,0 +1,3 @@
1
+ {
2
+ "import": ["@nitra/cspell-dict"]
3
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "import-substrings": {
3
+ "@cspell/dict-": "використовуй лише @nitra/cspell-dict (text.mdc)"
4
+ }
5
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": "0.2",
3
+ "ignorePaths": [
4
+ "**/node_modules/**",
5
+ "**/vscode-extension/**",
6
+ "**/.git/**",
7
+ ".vscode",
8
+ "report",
9
+ "*.svg",
10
+ "**/k8s/**/*.yaml"
11
+ ]
12
+ }
@@ -1,70 +1,57 @@
1
- # Порт перевірки `.markdownlint-cli2.jsonc` з `npm/scripts/check-text.mjs` (text.mdc).
1
+ # Перевірка `.markdownlint-cli2.jsonc` (text.mdc).
2
2
  #
3
- # Запуск (локально):
4
- # conftest test .markdownlint-cli2.jsonc -p npm/policy/text \
5
- # --namespace text.markdownlint --parser json
6
- #
7
- # Конфтест парсить `.jsonc` як JSON лише якщо файл — валідний JSON (без коментарів).
8
- # У випадку справжнього JSONC з `//` коментарями цей крок мовчки ігноруватиметься
9
- # (conftest skip). FS-перевірка (наявність файлу) живе у JS.
10
- #
11
- # Перевіряє канонічний baseline з text.mdc (мінімум — додаткові ключі дозволені):
12
- # { "gitignore": true,
13
- # "config": { "default": true, "MD013": false, "MD024": {"siblings_only": true},
14
- # "MD029": false, "MD040": false, "MD041": false } }
15
- # MD041 off навмисно — `.mdc` з frontmatter (див. text.mdc).
16
- #
17
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
18
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
19
- # (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/.markdownlint-cli2.jsonc.snippet.jsonc.
5
+ # Walker: top-level leaf, потім вкладені обʼєкти (config.<rule>); також рекурсивний
6
+ # leaf-check для MD024.siblings_only.
20
7
  package text.markdownlint
21
8
 
22
9
  import rego.v1
23
10
 
24
- # ── Шаблони повідомлень ────────────────────────────────────────────────────
25
-
26
- config_rule_template := concat(" ", [
27
- ".markdownlint-cli2.jsonc: config.%s має бути %v",
28
- "(зараз: %v) (text.mdc)",
29
- ])
30
-
31
- # ── deny: gitignore ───────────────────────────────────────────────────────
11
+ # ── deny: top-level leafs ───────────────────────────────────────────────
32
12
 
33
13
  deny contains msg if {
34
- object.get(input, "gitignore", null) != true
35
- msg := ".markdownlint-cli2.jsonc: додай на верхньому рівні \"gitignore\": true (text.mdc)"
14
+ some key, expected_value in data.template.snippet
15
+ not is_object(expected_value)
16
+ actual := object.get(input, key, null)
17
+ actual != expected_value
18
+ msg := sprintf(".markdownlint-cli2.jsonc: %s має бути %v (text.mdc)", [key, expected_value])
36
19
  }
37
20
 
38
- # ── deny: config.default ──────────────────────────────────────────────────
21
+ # ── deny: 2-level leafs (config.<rule> = scalar) ────────────────────────
39
22
 
40
23
  deny contains msg if {
41
- config := object.get(input, "config", {})
42
- object.get(config, "default", null) != true
43
- msg := sprintf(config_rule_template, ["default", true, object.get(config, "default", null)])
24
+ some section, expected_inner in data.template.snippet
25
+ is_object(expected_inner)
26
+ inner := object.get(input, section, {})
27
+ is_object(inner)
28
+ some leaf_key, expected_value in expected_inner
29
+ not is_object(expected_value)
30
+ actual := object.get(inner, leaf_key, null)
31
+ actual != expected_value
32
+ msg := sprintf(".markdownlint-cli2.jsonc: %s.%s має бути %v (text.mdc)", [section, leaf_key, expected_value])
44
33
  }
45
34
 
46
- # ── deny: MD013 / MD029 / MD040 / MD041 повинні бути `false` ──────────────
35
+ # ── deny: 3-level leafs (config.MD024.siblings_only) ────────────────────
47
36
 
48
37
  deny contains msg if {
49
- config := object.get(input, "config", {})
50
- some rule in {"MD013", "MD029", "MD040", "MD041"}
51
- object.get(config, rule, null) != false
52
- msg := sprintf(config_rule_template, [rule, false, object.get(config, rule, null)])
38
+ some section, expected_inner in data.template.snippet
39
+ is_object(expected_inner)
40
+ some inner_key, expected_subinner in expected_inner
41
+ is_object(expected_subinner)
42
+ subinner := object.get(object.get(input, section, {}), inner_key, {})
43
+ is_object(subinner)
44
+ some leaf, expected in expected_subinner
45
+ actual := object.get(subinner, leaf, null)
46
+ actual != expected
47
+ msg := sprintf(".markdownlint-cli2.jsonc: %s.%s.%s має бути %v (text.mdc)", [section, inner_key, leaf, expected])
53
48
  }
54
49
 
55
- # ── deny: MD024.siblings_only == true ─────────────────────────────────────
56
-
57
- deny contains msg if {
58
- config := object.get(input, "config", {})
59
- md024 := object.get(config, "MD024", null)
60
- not is_object(md024)
61
- msg := sprintf(config_rule_template, ["MD024", "{\"siblings_only\": true}", md024])
62
- }
50
+ # ── deny: vкладеного обʼєкта взагалі немає ──────────────────────────────
63
51
 
64
52
  deny contains msg if {
65
- config := object.get(input, "config", {})
66
- md024 := object.get(config, "MD024", {})
67
- is_object(md024)
68
- object.get(md024, "siblings_only", null) != true
69
- msg := sprintf(config_rule_template, ["MD024.siblings_only", true, object.get(md024, "siblings_only", null)])
53
+ some section, expected_inner in data.template.snippet
54
+ is_object(expected_inner)
55
+ not is_object(object.get(input, section, null))
56
+ msg := sprintf(".markdownlint-cli2.jsonc: відсутній обʼєкт %s (text.mdc)", [section])
70
57
  }
@@ -0,0 +1,11 @@
1
+ {
2
+ "gitignore": true,
3
+ "config": {
4
+ "default": true,
5
+ "MD013": false,
6
+ "MD024": { "siblings_only": true },
7
+ "MD029": false,
8
+ "MD040": false,
9
+ "MD041": false
10
+ }
11
+ }