@nitra/cursor 3.22.0 → 3.23.1
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/.pi-template/extensions/n-cursor-adr/docs/index.md +181 -0
- package/AGENTS.template.md +4 -0
- package/CHANGELOG.md +37 -3
- package/bin/docs/n-cursor.md +636 -0
- package/bin/docs/rename-yaml-extensions.md +207 -0
- package/bin/n-cursor.js +30 -3
- package/package.json +1 -1
- package/rules/abie/docs/fix.md +18 -0
- package/rules/abie/js/docs/applies.md +26 -0
- package/rules/abie/js/docs/env_dns.md +32 -0
- package/rules/abie/js/docs/firebase_hosting.md +23 -0
- package/rules/abie/js/docs/hc_pairing.md +35 -0
- package/rules/abie/js/docs/ua_http_route.md +28 -0
- package/rules/abie/js/docs/ua_node_selector.md +28 -0
- package/rules/abie/lib/docs/enabled.md +29 -0
- package/rules/abie/lib/docs/env-dns.md +35 -0
- package/rules/abie/lib/docs/hc-yaml.md +33 -0
- package/rules/abie/lib/docs/http-route.md +44 -0
- package/rules/abie/lib/docs/k8s-tree.md +40 -0
- package/rules/abie/lib/docs/kustomization-patches.md +47 -0
- package/rules/abie/lib/docs/overlay-paths.md +38 -0
- package/rules/abie/lib/docs/yaml.md +29 -0
- package/rules/adr/docs/fix.md +148 -0
- package/rules/adr/js/docs/hooks.md +259 -0
- package/rules/bun/docs/fix.md +156 -0
- package/rules/bun/js/docs/layout.md +393 -0
- package/rules/capacitor/docs/fix.md +121 -0
- package/rules/capacitor/js/docs/platforms.md +295 -0
- package/rules/changelog/changelog.mdc +4 -2
- package/rules/changelog/docs/fix.md +174 -0
- package/rules/changelog/js/consistency.mjs +114 -13
- package/rules/changelog/js/docs/consistency.md +387 -0
- package/rules/changelog/lib/docs/package-manifest.md +210 -0
- package/rules/ci4/docs/fix.md +179 -0
- package/rules/ci4/js/docs/marksman_config.md +128 -0
- package/rules/docker/docker.mdc +8 -3
- package/rules/docker/docs/fix.md +171 -0
- package/rules/docker/js/docs/lint.md +258 -0
- package/rules/docker/lib/docs/docker-hadolint.md +184 -0
- package/rules/docker/lib/docs/docker-mirror.md +247 -0
- package/rules/docker/lib/docs/docker-native-addon.md +170 -0
- package/rules/docker/lib/docs/docker-nginx-user.md +219 -0
- package/rules/docker/lint/docs/lint.md +193 -0
- package/rules/efes/docs/fix.md +203 -0
- package/rules/feedback/docs/fix.md +140 -0
- package/rules/flow/docs/fix.md +152 -0
- package/rules/ga/docs/fix.md +158 -0
- package/rules/ga/js/docs/lint.md +100 -0
- package/rules/ga/js/docs/workflows.md +217 -0
- package/rules/ga/lint/docs/lint.md +209 -0
- package/rules/ga/policy/clean_merged_branch/clean_merged_branch.rego +11 -2
- package/rules/ga/policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +3 -4
- package/rules/graphql/docs/fix.md +126 -0
- package/rules/graphql/js/docs/tooling.md +264 -0
- package/rules/graphql/lib/docs/graphql-gql-scan.md +219 -0
- package/rules/hasura/docs/fix.md +120 -0
- package/rules/hasura/hasura.mdc +14 -0
- package/rules/hasura/js/docs/internal_urls.md +326 -0
- package/rules/image-avif/docs/fix.md +132 -0
- package/rules/image-avif/js/docs/avif_generation.md +241 -0
- package/rules/image-compress/docs/fix.md +150 -0
- package/rules/image-compress/js/docs/package_setup.md +191 -0
- package/rules/js-bun-db/docs/fix.md +148 -0
- package/rules/js-bun-db/js/docs/safety.md +231 -0
- package/rules/js-bun-db/js-bun-db.mdc +42 -13
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +347 -0
- package/rules/js-bun-redis/docs/fix.md +123 -0
- package/rules/js-bun-redis/js/docs/imports.md +176 -0
- package/rules/js-bun-redis/lib/docs/redis-imports.md +223 -0
- package/rules/js-lint/docs/fix.md +117 -0
- package/rules/js-lint/js/docs/lint.md +250 -0
- package/rules/js-lint/js/docs/tooling.md +348 -0
- package/rules/js-lint/js/docs/utils_imports.md +207 -0
- package/rules/js-lint-ci/docs/fix.md +154 -0
- package/rules/js-lint-ci/js/docs/lint.md +144 -0
- package/rules/js-mssql/docs/fix.md +128 -0
- package/rules/js-mssql/js/docs/deps.md +263 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +367 -0
- package/rules/js-run/docs/fix.md +144 -0
- package/rules/js-run/js/docs/runtime.md +388 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +117 -0
- package/rules/js-run/lib/docs/check-env-scan.md +433 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +300 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +204 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +326 -0
- package/rules/k8s/docs/fix.md +129 -0
- package/rules/k8s/js/docs/manifests.md +344 -0
- package/rules/k8s/js/manifests.mjs +6 -2
- package/rules/k8s/k8s.mdc +4 -2
- package/rules/k8s/lint/docs/lint.md +411 -0
- package/rules/k8s/policy/network_policy/template/deployment.snippet.yaml +2 -0
- package/rules/k8s/policy/network_policy/template/stateful-set.snippet.yaml +2 -0
- package/rules/nginx-default-tpl/docs/fix.md +124 -0
- package/rules/nginx-default-tpl/js/docs/template.md +378 -0
- package/rules/npm-module/docs/fix.md +98 -0
- package/rules/npm-module/js/docs/package_structure.md +274 -0
- package/rules/npm-module/js/docs/rule_meta.md +137 -0
- package/rules/npm-module/js/docs/skill_meta.md +190 -0
- package/rules/php/docs/fix.md +107 -0
- package/rules/php/js/docs/tooling.md +152 -0
- package/rules/php/lint/docs/lint.md +215 -0
- package/rules/python/docs/fix.md +163 -0
- package/rules/python/js/docs/applies.md +108 -0
- package/rules/python/js/docs/tooling.md +153 -0
- package/rules/python/lint/docs/lint.md +322 -0
- package/rules/rego/docs/fix.md +121 -0
- package/rules/rego/js/docs/applies.md +174 -0
- package/rules/rego/js/docs/lint.md +118 -0
- package/rules/rego/lint/docs/lint.md +204 -0
- package/rules/release/docs/change.md +185 -0
- package/rules/release/docs/fix.md +119 -0
- package/rules/release/docs/release.md +222 -0
- package/rules/release/lib/docs/aggregate.md +246 -0
- package/rules/release/lib/docs/change-file.md +200 -0
- package/rules/release/lib/docs/fallback.md +203 -0
- package/rules/rust/docs/fix.md +129 -0
- package/rules/rust/js/docs/applies.md +140 -0
- package/rules/rust/lib/docs/has-cargo-toml.md +130 -0
- package/rules/security/docs/fix.md +86 -0
- package/rules/security/js/docs/lint.md +171 -0
- package/rules/security/js/docs/sample_secret.md +190 -0
- package/rules/security/js/docs/trufflehog.md +137 -0
- package/rules/security/js/lint.mjs +9 -1
- package/rules/style-lint/docs/fix.md +155 -0
- package/rules/style-lint/js/docs/lint.md +184 -0
- package/rules/style-lint/js/docs/tooling.md +194 -0
- package/rules/tauri/docs/fix.md +158 -0
- package/rules/tauri/js/docs/cargo_mutants_config.md +168 -0
- package/rules/tauri/js/docs/tooling.md +228 -0
- package/rules/test/coverage/coverage.mjs +15 -3
- package/rules/test/docs/fix.md +132 -0
- package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +138 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +134 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +160 -0
- package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +195 -0
- package/rules/test/js/docs/cargo_mutants_config.md +173 -0
- package/rules/test/js/docs/location.md +136 -0
- package/rules/test/js/docs/no-process-chdir.md +160 -0
- package/rules/test/js/docs/no-relative-fs-path.md +271 -0
- package/rules/test/js/docs/stryker_config.md +152 -0
- package/rules/test/js/docs/vitest-config-pool-forks.md +174 -0
- package/rules/text/docs/fix.md +118 -0
- package/rules/text/js/docs/forbidden-prettier.md +143 -0
- package/rules/text/js/docs/formatting.md +256 -0
- package/rules/text/js/docs/lint.md +122 -0
- package/rules/text/lint/docs/lint.md +220 -0
- package/rules/text/lint/docs/run-dotenv-linter.md +157 -0
- package/rules/text/lint/docs/run-shellcheck.md +212 -0
- package/rules/text/lint/docs/run-v8r.md +197 -0
- package/rules/vue/docs/fix.md +127 -0
- package/rules/vue/js/docs/packages.md +335 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +261 -0
- package/rules/worktree/docs/fix.md +161 -0
- package/schemas/rule-meta.json +5 -1
- package/scripts/auto-rules.mjs +7 -4
- package/scripts/coverage-classify/docs/apply.md +202 -0
- package/scripts/coverage-classify/docs/cache.md +203 -0
- package/scripts/coverage-classify/docs/index.md +218 -0
- package/scripts/coverage-classify/docs/prompt.md +132 -0
- package/scripts/coverage-classify/docs/verdict-schema.md +169 -0
- package/scripts/coverage-fix-extract.mjs +122 -0
- package/scripts/coverage-fix.mjs +1 -1
- package/scripts/dispatcher/docs/graph.md +346 -0
- package/scripts/dispatcher/docs/index.md +236 -0
- package/scripts/dispatcher/docs/trace.md +296 -0
- package/scripts/dispatcher/index.mjs +1 -1
- package/scripts/dispatcher/lib/active.mjs +4 -8
- package/scripts/dispatcher/lib/commands.mjs +7 -11
- package/scripts/dispatcher/lib/docs/active.md +348 -0
- package/scripts/dispatcher/lib/docs/artifact.md +232 -0
- package/scripts/dispatcher/lib/docs/budget.md +167 -0
- package/scripts/dispatcher/lib/docs/capability.md +196 -0
- package/scripts/dispatcher/lib/docs/commands.md +210 -0
- package/scripts/dispatcher/lib/docs/events.md +182 -0
- package/scripts/dispatcher/lib/docs/executor.md +190 -0
- package/scripts/dispatcher/lib/docs/flow-lock.md +161 -0
- package/scripts/dispatcher/lib/docs/flow-resolve.md +267 -0
- package/scripts/dispatcher/lib/docs/gate.md +231 -0
- package/scripts/dispatcher/lib/docs/level.md +335 -0
- package/scripts/dispatcher/lib/docs/plan-panel.md +181 -0
- package/scripts/dispatcher/lib/docs/plan.md +200 -0
- package/scripts/dispatcher/lib/docs/planner.md +269 -0
- package/scripts/dispatcher/lib/docs/review.md +255 -0
- package/scripts/dispatcher/lib/docs/reviewer.md +240 -0
- package/scripts/dispatcher/lib/docs/snapshot.md +247 -0
- package/scripts/dispatcher/lib/docs/spec.md +203 -0
- package/scripts/dispatcher/lib/docs/state-store.md +303 -0
- package/scripts/dispatcher/lib/docs/subagent-runner.md +173 -0
- package/scripts/dispatcher/lib/executor.mjs +6 -1
- package/scripts/dispatcher/lib/flow-resolve.mjs +3 -1
- package/scripts/dispatcher/lib/level.mjs +29 -3
- package/scripts/dispatcher/lib/review.mjs +1 -1
- package/scripts/dispatcher/lib/subagent-runner.mjs +5 -3
- package/scripts/docs/auto-rules.md +376 -0
- package/scripts/docs/auto-skills.md +173 -0
- package/scripts/docs/build-agents-commands.md +183 -0
- package/scripts/docs/cli-entry.md +153 -0
- package/scripts/docs/coverage-fix.md +177 -0
- package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +189 -0
- package/scripts/lib/changed-files.mjs +4 -1
- package/scripts/lib/docs/changed-files.md +149 -0
- package/scripts/lib/docs/check-mdc-template-refs.md +222 -0
- package/scripts/lib/docs/check-reporter.md +175 -0
- package/scripts/lib/docs/discover-check-rules-from-cursor.md +157 -0
- package/scripts/lib/docs/discover-checkable-rules.md +165 -0
- package/scripts/lib/docs/ensure-tool.md +254 -0
- package/scripts/lib/docs/generated-markdown.md +275 -0
- package/scripts/lib/docs/gha-workflow.md +326 -0
- package/scripts/lib/docs/inline-template-links.md +303 -0
- package/scripts/lib/docs/list-rule-ids.md +156 -0
- package/scripts/lib/docs/load-cursor-config.md +147 -0
- package/scripts/lib/docs/mirror-parity.md +167 -0
- package/scripts/lib/worktree.mjs +26 -0
- package/scripts/worktree-cli.mjs +12 -2
- package/skills/coverage-fix/SKILL.md +34 -45
- package/skills/docgen/SKILL.md +44 -23
- package/skills/docgen/bench/etalon/firebase_hosting.md +19 -0
- package/skills/docgen/bench/etalon/k8s-tree.md +24 -0
- package/skills/docgen/bench/etalon/overlay-paths.md +24 -0
- package/skills/docgen/js/docgen-ignore.mjs +54 -0
- package/skills/docgen/js/docgen-scan.mjs +37 -21
- package/skills/llm-patch/SKILL.md +23 -2
- package/skills/start-check/SKILL.md +26 -53
- package/skills/start-check/js/check.mjs +211 -0
- package/skills/taze/SKILL.md +9 -3
- package/skills/taze/js/diff.mjs +154 -0
- package/types/bin/n-cursor.d.ts +1 -1
- package/skills/fix-tests/SKILL.md +0 -119
- package/skills/fix-tests/meta.json +0 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# applies.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `applies.mjs` реалізує **applies-гейт** для правила `rust` у системі правил `n-cursor`. Його завдання — швидко відповісти на питання: «чи має взагалі сенс прогонити це правило в поточному репозиторії?». Якщо репозиторій не містить жодного Rust-кореня (тобто немає `Cargo.toml` ані в корені, ані в жодному вкладеному робочому каталозі), функція повертає `false`, і ранер `runStandardRule` пропускає всі концерни цього правила — як JS-перевірки, так і policy-концерни. Це уникає зайвої роботи в репозиторіях без Rust-коду.
|
|
6
|
+
|
|
7
|
+
Маркером застосовності є файл `Cargo.toml`:
|
|
8
|
+
|
|
9
|
+
- спершу швидка перевірка в `cwd` через `existsSync` (синхронно, без I/O-обходу);
|
|
10
|
+
- якщо в корені маркера немає — рекурсивний обхід дерева через `hasCargoTomlInTree`, який ігнорує штучні / службові директорії (`node_modules`, `.git`, `.next`, `.turbo`).
|
|
11
|
+
|
|
12
|
+
Окремо експортується функція `check()` — це **context-pass** репортер: коли applies повернув `true`, ранер усе одно очікує, що файл правила розрукує хоча б один контекст. `check()` просто друкує повідомлення «знайдено Cargo.toml — застосовуємо правила rust.mdc» і повертає exit-код `0`. Жодної реальної логіки лінтування / перевірок у цьому файлі немає — вся робота винесена в **policy-концерни** правила `rust`.
|
|
13
|
+
|
|
14
|
+
Файл написаний у форматі ESM (`.mjs`) та сумісний з Bun і Node.js (≥ модулів з підтримкою ESM та `node:` префіксу).
|
|
15
|
+
|
|
16
|
+
## Експорти / API
|
|
17
|
+
|
|
18
|
+
| Експорт | Тип | Призначення |
|
|
19
|
+
| --------- | ------------------------------------ | ------------------------------------------------------------------------ |
|
|
20
|
+
| `applies` | `(cwd?: string) => Promise<boolean>` | Гейт-функція: чи активувати правило `rust` для заданого `cwd`. |
|
|
21
|
+
| `check` | `() => number` | Context-pass репортер: друкує позитивне повідомлення, повертає exit-код. |
|
|
22
|
+
|
|
23
|
+
Інших публічних експортів немає. Константа `IGNORED_DIR_NAMES` — внутрішня (module-level), назовні не експортується.
|
|
24
|
+
|
|
25
|
+
## Функції
|
|
26
|
+
|
|
27
|
+
### `applies(cwd = process.cwd())`
|
|
28
|
+
|
|
29
|
+
**Сигнатура:** `applies(cwd?: string): Promise<boolean>`
|
|
30
|
+
|
|
31
|
+
**Параметри:**
|
|
32
|
+
|
|
33
|
+
- `cwd` _(string, опційний)_ — абсолютний (або відносний від процесу) шлях до кореня репозиторію, який треба перевірити. За замовчуванням — `process.cwd()` (поточний робочий каталог процесу Node/Bun).
|
|
34
|
+
|
|
35
|
+
**Повертає:** `Promise<boolean>`
|
|
36
|
+
|
|
37
|
+
- `true` — правило `rust` застосовне (знайдено хоча б один `Cargo.toml` у корені або десь у дереві);
|
|
38
|
+
- `false` — застосовувати правило не треба (Rust у репозиторії не знайдено).
|
|
39
|
+
|
|
40
|
+
**Алгоритм:**
|
|
41
|
+
|
|
42
|
+
1. Складає шлях `join(cwd, 'Cargo.toml')` і синхронно перевіряє його існування через `existsSync`.
|
|
43
|
+
2. Якщо файл є — одразу повертає `Promise.resolve(true)` (швидкий вихід; решта дерева не сканується).
|
|
44
|
+
3. Якщо в корені маркера немає — викликає `hasCargoTomlInTree(cwd, IGNORED_DIR_NAMES)` і повертає його результат, загорнутий у промісі (значення може бути проміс або bool — у будь-якому разі `Promise.resolve` нормалізує до `Promise<boolean>`).
|
|
45
|
+
|
|
46
|
+
**Side effects:**
|
|
47
|
+
|
|
48
|
+
- Один синхронний виклик `existsSync` (file-system stat) для шляху `<cwd>/Cargo.toml`.
|
|
49
|
+
- За потреби — рекурсивний обхід директорії `cwd` (виконує `hasCargoTomlInTree`); у межах цього обходу директорії з `IGNORED_DIR_NAMES` пропускаються.
|
|
50
|
+
- Не модифікує файлову систему, не пише в `stdout`/`stderr`, не виконує мережевих запитів.
|
|
51
|
+
|
|
52
|
+
**Не кидає винятків** у нормальних умовах: `existsSync` повертає `false` для неіснуючих/недоступних шляхів. Виняткові ситуації можуть прилетіти лише з глибин `hasCargoTomlInTree` (наприклад, помилка `readdir`), але це поведінка залежного модуля, а не самого `applies`.
|
|
53
|
+
|
|
54
|
+
### `check()`
|
|
55
|
+
|
|
56
|
+
**Сигнатура:** `check(): number`
|
|
57
|
+
|
|
58
|
+
**Параметри:** немає.
|
|
59
|
+
|
|
60
|
+
**Повертає:** `number` — exit-код:
|
|
61
|
+
|
|
62
|
+
- `0` — все OK (стандартний шлях; функція завжди йде цим шляхом, бо вона лише друкує `pass`);
|
|
63
|
+
- `1` — порушення (теоретично можливо, якщо репортер у майбутньому почне накопичувати помилки; зараз цей кейс не використовується).
|
|
64
|
+
|
|
65
|
+
**Алгоритм:**
|
|
66
|
+
|
|
67
|
+
1. Створює інстанс репортера через `createCheckReporter()`.
|
|
68
|
+
2. Викликає `reporter.pass('Знайдено Cargo.toml — застосовуємо правила rust.mdc')` — реєструє позитивний context-pass.
|
|
69
|
+
3. Повертає `reporter.getExitCode()`.
|
|
70
|
+
|
|
71
|
+
**Side effects:**
|
|
72
|
+
|
|
73
|
+
- Друкує повідомлення про `pass` у stdout у форматі, заданому `createCheckReporter` (зазвичай — рядок з префіксом / маркером).
|
|
74
|
+
- Не торкається файлової системи й мережі.
|
|
75
|
+
|
|
76
|
+
**Контракт із ранером:** `runStandardRule` викликає `check()` лише після того, як `applies()` повернув `true`. Тому повідомлення в стилі «знайдено Cargo.toml» завжди коректне на момент друку — applies-гейт уже відпрацював перед цим.
|
|
77
|
+
|
|
78
|
+
## Залежності
|
|
79
|
+
|
|
80
|
+
### Зовнішні (стандартна бібліотека Node.js)
|
|
81
|
+
|
|
82
|
+
- `node:fs` → `existsSync` — синхронна перевірка існування файлу.
|
|
83
|
+
- `node:path` → `join` — крос-платформне склеювання сегментів шляху.
|
|
84
|
+
|
|
85
|
+
### Внутрішні (відносні до файлу)
|
|
86
|
+
|
|
87
|
+
- `../../../scripts/lib/check-reporter.mjs` → `createCheckReporter` — фабрика репортера для context-pass / помилок із підрахунком exit-коду.
|
|
88
|
+
- `../lib/has-cargo-toml.mjs` → `hasCargoTomlInTree` — рекурсивний обхід дерева директорій з пошуком `Cargo.toml` і пропуском ігнор-папок.
|
|
89
|
+
|
|
90
|
+
### Module-level константи
|
|
91
|
+
|
|
92
|
+
- `IGNORED_DIR_NAMES: Set<string>` — `{ 'node_modules', '.git', '.next', '.turbo' }`. Передається в `hasCargoTomlInTree` як набір імен директорій, у які заходити не треба під час рекурсивного пошуку.
|
|
93
|
+
|
|
94
|
+
## Потік виконання / Використання
|
|
95
|
+
|
|
96
|
+
### Типовий сценарій (ранер правил)
|
|
97
|
+
|
|
98
|
+
1. `runStandardRule` для правила `rust` спершу імпортує модуль `applies.mjs`.
|
|
99
|
+
2. Викликає `await applies()` (без аргументів — буде використано `process.cwd()`).
|
|
100
|
+
3. Якщо результат `false` — ранер пропускає правило цілком: ні JS-концерни (`check`), ні policy-концерни не виконуються; правило вважається «not applicable».
|
|
101
|
+
4. Якщо результат `true` — ранер послідовно виконує всі концерни правила:
|
|
102
|
+
- викликає експортований `check()` як «context-pass» — щоб у звіті був видимий маркер «правило застосовано»;
|
|
103
|
+
- запускає policy-концерни (фактичні перевірки коду / структури), які живуть в інших файлах модуля `rust`.
|
|
104
|
+
|
|
105
|
+
### Приклад прямого виклику
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
import { applies, check } from './npm/rules/rust/js/applies.mjs'
|
|
109
|
+
|
|
110
|
+
const ok = await applies('/path/to/some/repo')
|
|
111
|
+
if (ok) {
|
|
112
|
+
const code = check()
|
|
113
|
+
process.exit(code)
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Ребра (edge cases)
|
|
118
|
+
|
|
119
|
+
- **`cwd` не існує / недоступний:** `existsSync` поверне `false`; далі `hasCargoTomlInTree` обходитиме неіснуючий шлях (поведінка визначається ним).
|
|
120
|
+
- **`Cargo.toml` лежить глибоко під ігнор-папкою** (наприклад, всередині `node_modules/.../Cargo.toml`): його **не** знайдуть — `IGNORED_DIR_NAMES` свідомо пропускає такі директорії, бо це не «справжній» Rust-корінь репозиторію.
|
|
121
|
+
- **`Cargo.toml` у корені `cwd`:** виконується лише `existsSync` — рекурсивний обхід не запускається (оптимізація).
|
|
122
|
+
- **Множинні `Cargo.toml` глибоко в дереві:** достатньо знайти хоча б один — `hasCargoTomlInTree` має повернути `true` при першому ж збігу.
|
|
123
|
+
|
|
124
|
+
### Інваріанти
|
|
125
|
+
|
|
126
|
+
- `applies()` завжди повертає `Promise<boolean>` — навіть для синхронного fast-path через `Promise.resolve(true)`.
|
|
127
|
+
- `check()` синхронна — не повертає Promise; завжди повертає число (exit-код).
|
|
128
|
+
- `applies()` ідемпотентна та не має станy між викликами (немає кешу).
|
|
129
|
+
|
|
130
|
+
## Rebuild Test
|
|
131
|
+
|
|
132
|
+
Якщо видалити файл `applies.mjs` і відновлювати його з нуля по цій документації, реалізація має:
|
|
133
|
+
|
|
134
|
+
- бути ESM-модулем (`.mjs`) з ES-імпортами `node:fs` / `node:path`;
|
|
135
|
+
- імпортувати `createCheckReporter` із `../../../scripts/lib/check-reporter.mjs` і `hasCargoTomlInTree` із `../lib/has-cargo-toml.mjs`;
|
|
136
|
+
- містити module-level `Set` з рівно цими іменами: `'node_modules'`, `'.git'`, `'.next'`, `'.turbo'`;
|
|
137
|
+
- експортувати **named** функцію `applies(cwd = process.cwd())` з логікою «`existsSync(join(cwd, 'Cargo.toml'))` → інакше делегат у `hasCargoTomlInTree(cwd, IGNORED_DIR_NAMES)`», результати загорнуті в `Promise.resolve`;
|
|
138
|
+
- експортувати **named** синхронну функцію `check()`, яка створює репортер, реєструє `pass('Знайдено Cargo.toml — застосовуємо правила rust.mdc')` і повертає `reporter.getExitCode()`;
|
|
139
|
+
- не мати default-експорту;
|
|
140
|
+
- не мати побічних ефектів на рівні модуля (тільки декларація `Set` — допустимо).
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# has-cargo-toml.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `has-cargo-toml.mjs` — rule-local утиліта для правил Rust, що визначає наявність маніфесту Cargo (`Cargo.toml`) у дереві файлової системи, починаючи з заданого кореневого каталогу. Використовується як applies-walker на рівні правила: дозволяє правилу Rust динамічно вирішувати, чи варто застосовуватися до конкретного workspace/cwd, шукаючи `Cargo.toml` у поточному каталозі або в будь-якому з підкаталогів.
|
|
6
|
+
|
|
7
|
+
Реалізація — синхронний рекурсивний обхід каталогів з раннім поверненням (`early return`) при першому ж знайденому `Cargo.toml`, без читання вмісту файлів. Імена директорій, у які заходити не треба (наприклад, `node_modules`, `.git`, `.next`, `.turbo`), передаються ззовні через `Set<string>` — модуль свідомо не містить власного списку, щоб список ігнорувань був єдиним з тим, що в `npm/scripts/auto-rules.mjs` (на рівні виклику).
|
|
8
|
+
|
|
9
|
+
Утиліта навмисно тримається rule-local (у каталозі правила `rust/lib/`), оскільки на момент написання її єдиний споживач — правило `rust`. У документуючому коментарі модуля зазначено: якщо з'явиться другий споживач — утиліту слід підняти у спільний `npm/scripts/lib/`.
|
|
10
|
+
|
|
11
|
+
## Експорти / API
|
|
12
|
+
|
|
13
|
+
Модуль експортує одну іменовану функцію:
|
|
14
|
+
|
|
15
|
+
- `hasCargoTomlInTree(root, ignoredDirNames)` — `function`, named export.
|
|
16
|
+
|
|
17
|
+
Інших експортів (default, типів, констант) у модулі немає.
|
|
18
|
+
|
|
19
|
+
## Функції
|
|
20
|
+
|
|
21
|
+
### `hasCargoTomlInTree(root, ignoredDirNames)`
|
|
22
|
+
|
|
23
|
+
**Сигнатура:**
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
export function hasCargoTomlInTree(root: string, ignoredDirNames: Set<string>): boolean
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Параметри:**
|
|
30
|
+
|
|
31
|
+
- `root` — `string`. Абсолютний шлях до кореневого каталогу, з якого починається пошук. Передається у внутрішню функцію `walk` як стартова точка обходу.
|
|
32
|
+
- `ignoredDirNames` — `Set<string>`. Множина імен директорій (саме базових імен — не шляхів), у які НЕ слід заходити під час обходу. Перевірка виконується через `ignoredDirNames.has(entry.name)`. Типові значення, які формує викликова сторона: `'node_modules'`, `'.git'`, `'.next'`, `'.turbo'`.
|
|
33
|
+
|
|
34
|
+
**Повертає:**
|
|
35
|
+
|
|
36
|
+
- `boolean` — `true`, якщо в піддереві з коренем `root` (включно з самим `root`) знайдено хоча б один файл з ім'ям `Cargo.toml`. `false` — якщо такого файла немає, або якщо корінь недоступний для читання.
|
|
37
|
+
|
|
38
|
+
**Алгоритм:**
|
|
39
|
+
|
|
40
|
+
1. Викликається внутрішня функція `walk(root)`.
|
|
41
|
+
2. Усередині `walk(dir)` робиться спроба отримати вміст каталогу через `readdirSync(dir, { withFileTypes: true })`, що повертає масив об'єктів `Dirent`.
|
|
42
|
+
3. Якщо `readdirSync` кидає виключення (наприклад, `EACCES`, `ENOENT`, `ENOTDIR`) — воно мовчки ловиться `catch {}` і функція повертає `false` для цього каталогу (обхід продовжується для інших гілок, якщо вони є).
|
|
43
|
+
4. Виконується ітерація по `entries`:
|
|
44
|
+
- Якщо запис — це файл і його ім'я дорівнює рівно `'Cargo.toml'`, негайно повертається `true` (early return).
|
|
45
|
+
- Якщо запис — це каталог і його ім'я не входить у `ignoredDirNames`, рекурсивно викликається `walk(join(dir, entry.name))`; при `true` від рекурсії — негайно повертається `true`.
|
|
46
|
+
5. Якщо жоден запис не дав `true`, повертається `false`.
|
|
47
|
+
|
|
48
|
+
Порядок обходу — детерміністичний у межах одного запуску і визначається порядком, який повертає `readdirSync` (як правило, залежить від ФС/ОС). Це означає, що логічний результат (boolean) детерміністичний, але швидкість виявлення `Cargo.toml` залежить від порядку записів.
|
|
49
|
+
|
|
50
|
+
Алгоритм має властивість «depth-first з раннім return»: знайшовши `Cargo.toml` у першій же підгілці, функція не обходить решту дерева.
|
|
51
|
+
|
|
52
|
+
**Side effects:**
|
|
53
|
+
|
|
54
|
+
- Виключно синхронні I/O-операції читання вмісту директорій через `readdirSync` із Node.js `fs`. Не читає вміст файлів, не пише на диск, не звертається до мережі, не змінює глобальний стан.
|
|
55
|
+
- При помилках доступу до окремого каталогу не пробрасує виключення назовні — глушить їх через `try/catch` і повертає `false` лише для проблемної гілки.
|
|
56
|
+
- Блокує event loop на час обходу (синхронне API). Для дуже великих дерев це може бути помітним; ризик частково мітигує ранній return і список `ignoredDirNames`, що відсікає типові важкі директорії на кшталт `node_modules`.
|
|
57
|
+
|
|
58
|
+
**Внутрішня функція `walk(dir)`:**
|
|
59
|
+
|
|
60
|
+
- `dir` — `string`, абсолютний шлях каталогу для обходу.
|
|
61
|
+
- Повертає `boolean` — `true`, якщо в піддереві `dir` є `Cargo.toml`.
|
|
62
|
+
- Замикається на параметр `ignoredDirNames` зовнішньої функції (тобто множина передається один раз і використовується на всіх рівнях рекурсії без повторної параметризації).
|
|
63
|
+
|
|
64
|
+
## Залежності
|
|
65
|
+
|
|
66
|
+
Зовнішні npm-залежності — відсутні. Модуль працює виключно на стандартній бібліотеці Node.js:
|
|
67
|
+
|
|
68
|
+
- `node:fs` — імпортується `readdirSync` для синхронного читання вмісту каталогу з опцією `withFileTypes: true`, що повертає `Dirent[]` (для уникнення додаткових `statSync` на кожен запис).
|
|
69
|
+
- `node:path` — імпортується `join` для безпечного склеювання шляхів `dir + entry.name` із врахуванням розділювача ОС.
|
|
70
|
+
|
|
71
|
+
Імпорти використовують префікс `node:` (`node:fs`, `node:path`) — це канонічна форма посилання на вбудовані модулі Node.js, що відрізняє їх від npm-пакунків.
|
|
72
|
+
|
|
73
|
+
Модуль написаний як ES Module (`.mjs`): використовуються `import` та `export`.
|
|
74
|
+
|
|
75
|
+
Внутрішніх залежностей від інших файлів проєкту немає — модуль повністю самодостатній.
|
|
76
|
+
|
|
77
|
+
## Потік виконання / Використання
|
|
78
|
+
|
|
79
|
+
### Контекст застосування
|
|
80
|
+
|
|
81
|
+
Функція проектована як applies-walker для правил Rust: правило викликає її, щоб дізнатися, чи має воно сенс у поточному репозиторії/workspace. Якщо `Cargo.toml` ніде не знайдено — правило застосовувати не треба.
|
|
82
|
+
|
|
83
|
+
### Типовий виклик
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
import { hasCargoTomlInTree } from './lib/has-cargo-toml.mjs'
|
|
87
|
+
|
|
88
|
+
const IGNORED = new Set(['node_modules', '.git', '.next', '.turbo'])
|
|
89
|
+
|
|
90
|
+
if (hasCargoTomlInTree(process.cwd(), IGNORED)) {
|
|
91
|
+
// правило Rust застосовується: десь у дереві є Cargo.toml
|
|
92
|
+
} else {
|
|
93
|
+
// Rust-проєкту не виявлено — правило пропускається
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Список ігнорованих директорій формується викликовою стороною й має співпадати зі списком, який використовує `npm/scripts/auto-rules.mjs` (це вимога з заголовного коментаря модуля — щоб поведінка обходу була узгоджена між скриптом авто-правил і rule-local утилітою).
|
|
98
|
+
|
|
99
|
+
### Розгортання потоку виконання
|
|
100
|
+
|
|
101
|
+
1. Споживач (правило Rust) формує `Set<string>` ігнорованих імен директорій.
|
|
102
|
+
2. Споживач викликає `hasCargoTomlInTree(root, ignoredDirNames)` із кореневим шляхом (зазвичай — поточний workspace або репозиторій).
|
|
103
|
+
3. Усередині функції стартує `walk(root)`:
|
|
104
|
+
- Якщо `root` сам містить `Cargo.toml` як файл першого рівня — повертається `true` без рекурсії далі по цьому ж рівню (інші записи не перевіряються після першого ж `Cargo.toml`).
|
|
105
|
+
- Якщо `Cargo.toml` на першому рівні немає — обхід заглиблюється у кожен підкаталог, якого немає в `ignoredDirNames`.
|
|
106
|
+
- Перший же `true` із рекурсії припиняє обхід і пробрасує `true` нагору.
|
|
107
|
+
4. Якщо все дерево пройдене без знахідки — повертається `false`.
|
|
108
|
+
5. Споживач використовує boolean-результат для прийняття рішення про застосування правила.
|
|
109
|
+
|
|
110
|
+
### Граничні випадки
|
|
111
|
+
|
|
112
|
+
- **Порожній `ignoredDirNames`** (`new Set()`): функція обходить геть усе дерево, включно з `node_modules` тощо — це коректно, але повільно.
|
|
113
|
+
- **Неіснуючий `root`**: `readdirSync` кидає `ENOENT`, помилка ловиться, функція повертає `false`.
|
|
114
|
+
- **`root` не є директорією** (наприклад, шлях до файлу): `readdirSync` кидає `ENOTDIR`, ловиться `catch`, повертається `false`.
|
|
115
|
+
- **Cargo.toml як директорія**: пропускається, бо перевірка йде через `entry.isFile() && entry.name === 'Cargo.toml'` — потрібен саме файл.
|
|
116
|
+
- **Симлінки**: `withFileTypes: true` повертає `Dirent` з власними методами; `entry.isFile()` для симлінка поверне `false`, `entry.isDirectory()` — теж `false` (метод `isSymbolicLink()` тут не перевіряється), тож симлінки фактично не переходяться. Це свідомий вибір — захист від циклів через симлінки.
|
|
117
|
+
- **Cargo.toml у самому корені**: знаходиться на першому ж рівні без рекурсії.
|
|
118
|
+
- **Cargo.toml виключно всередині `node_modules`** (за умови, що `node_modules` у `ignoredDirNames`): не знаходиться, функція повертає `false`. Це навмисна поведінка: ні правила, ні авто-обхід не повинні «бачити» вендоровані Rust-маніфести з npm-залежностей.
|
|
119
|
+
|
|
120
|
+
### Контекст у проєкті
|
|
121
|
+
|
|
122
|
+
Згідно з заголовним коментарем файлу:
|
|
123
|
+
|
|
124
|
+
- Утиліта живе у `npm/rules/rust/lib/` як rule-local — лише `rust`-правило її використовує.
|
|
125
|
+
- Список ігнорованих директорій має узгоджуватися зі списком у `npm/scripts/auto-rules.mjs`.
|
|
126
|
+
- Інваріант масштабування: при появі другого споживача — підняти модуль у спільний `npm/scripts/lib/`.
|
|
127
|
+
|
|
128
|
+
## Rebuild Test
|
|
129
|
+
|
|
130
|
+
За цією документацією модуль можна відтворити: один ES-модуль з імпортами `readdirSync` із `node:fs` та `join` із `node:path`, що експортує іменовану функцію `hasCargoTomlInTree(root, ignoredDirNames)`. Усередині — внутрішня рекурсивна функція `walk(dir)` із синхронним `readdirSync(dir, { withFileTypes: true })`, обгорнутим у `try/catch`, ітерацією по `Dirent`-записах, перевіркою `isFile() && name === 'Cargo.toml'` для негайного `true`, та рекурсією у підкаталоги через `join(dir, entry.name)` за умови `isDirectory() && !ignoredDirNames.has(name)`. Поведінкові інваріанти: ранній return при знахідці; повернення `false` при помилці читання каталогу; пропуск ігнорованих директорій; обхід не йде у симлінки (бо `isDirectory()` для симлінка повертає `false`); жодних побічних ефектів окрім читання структури директорій. Сигнатура: `(string, Set<string>) => boolean`.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# fix.mjs — entry-point правила `security`
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Файл `npm/rules/security/fix.mjs` — це канонічний entry-point правила з ідентифікатором `security` пакета `@nitra/cursor`. Він реалізує стандартний «двозадачний» патерн всіх правил у директорії `npm/rules/<id>/fix.mjs`:
|
|
6
|
+
|
|
7
|
+
1. **Library mode** — експортує функцію `run(ctx)`, яку викликає CLI-оркестратор пакета (`npx @nitra/cursor fix <id>` або агрегований прогон усіх правил). У цьому режимі правило ділить кеш обходу файлів (`walkCache`) та інший контекст із іншими правилами одного запуску.
|
|
8
|
+
2. **Standalone mode** — якщо файл запущено напряму (наприклад `bun rules/security/fix.mjs`), виконується повний CLI-цикл: завантаження конфігурації, застосування whitelist, друк summary та повернення коду виходу для CI/IDE.
|
|
9
|
+
|
|
10
|
+
Уся ділова логіка (фази `applies` → `JS-concerns` → `policy` → `mdc-refs`) делегується спільному раннеру `runStandardRule`, тому сам `fix.mjs` залишається тонким shim-ом, який знає лише власну директорію (`import.meta.dirname`) і передає її далі.
|
|
11
|
+
|
|
12
|
+
## Експорти / API
|
|
13
|
+
|
|
14
|
+
| Експорт | Тип | Призначення |
|
|
15
|
+
| ------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
16
|
+
| `run` | `function(ctx?): Promise<number>` | Іменований експорт — точка входу для library-режиму. Викликається CLI-оркестратором пакета `@nitra/cursor` під час прогону одного або всіх правил. |
|
|
17
|
+
|
|
18
|
+
Default-експорт не оголошено. Side-effect частина (CLI-bootstrap) виконується лише за умови, що файл є entry-point процесу (див. розділ «Потік виконання»).
|
|
19
|
+
|
|
20
|
+
## Функції
|
|
21
|
+
|
|
22
|
+
### `run(ctx)`
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
export function run(ctx)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- **Призначення.** Запустити стандартний пайплайн правила: послідовно виконати фази `applies` → `JS-concerns` → `policy` → `mdc-refs`, делегуючи всю реалізацію спільному модулю `runStandardRule`.
|
|
29
|
+
- **Параметри.**
|
|
30
|
+
- `ctx` _(опційно)_ — `RuleContext` із `../../scripts/lib/run-standard-rule.mjs`. Контекст одного прогону, який можуть розділяти кілька правил (наприклад, кеш обходу файлів `walkCache`, опції CLI, агрегатор результатів тощо). Якщо `ctx` не передано, `runStandardRule` має сам ініціалізувати дефолтний контекст.
|
|
31
|
+
- **Повертає.** `Promise<number>` — числовий exit-code:
|
|
32
|
+
- `0` — правило не знайшло порушень (OK);
|
|
33
|
+
- `1` — є порушення, які слід репортити вище (failure).
|
|
34
|
+
- **Side effects.** Усі побічні ефекти інкапсульовано в `runStandardRule`: читання файлів проєкту через `walkCache`, перевірки policy, можливі повідомлення в `stdout`/`stderr`, оновлення спільного контексту. Сам `run` лише прокидає `import.meta.dirname` поточного файла, щоб раннер знав, до якого правила належить директорія (структура `npm/rules/<id>/` визначає `id`).
|
|
35
|
+
- **Винятки.** Власних `throw` немає; будь-які помилки приходять із `runStandardRule` і не перехоплюються — їх обробляє верхній рівень (CLI-оркестратор чи `runRuleCli`).
|
|
36
|
+
|
|
37
|
+
## Залежності
|
|
38
|
+
|
|
39
|
+
Імпорти модуля:
|
|
40
|
+
|
|
41
|
+
| Модуль | Імпортовані символи | Роль |
|
|
42
|
+
| ----------------------------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `../../scripts/lib/run-rule-cli.mjs` | `isRunAsCli`, `runRuleCli` | Допоміжні функції для standalone-режиму: визначення, що файл запущений як entry-point процесу (`isRunAsCli`), та повний CLI-цикл (`runRuleCli`) із завантаженням конфігурації, whitelist та друком summary. |
|
|
44
|
+
| `../../scripts/lib/run-standard-rule.mjs` | `runStandardRule` | Спільний раннер, що реалізує канонічний пайплайн правила (`applies` → `JS-concerns` → `policy` → `mdc-refs`). Також надає тип `RuleContext`, який використовується в JSDoc. |
|
|
45
|
+
|
|
46
|
+
Жодних інших імпортів (зокрема Node-стандартних модулів) у файлі немає — він свідомо тонкий.
|
|
47
|
+
|
|
48
|
+
Опосередковано модуль покладається на:
|
|
49
|
+
|
|
50
|
+
- стандартну структуру каталогу правила (`npm/rules/<id>/`), яку розпізнає `runStandardRule` через переданий `import.meta.dirname`;
|
|
51
|
+
- глобальні Node-механізми `import.meta.url` / `import.meta.dirname` для визначення власного шляху;
|
|
52
|
+
- `process.exit` для термінального коду в standalone-режимі.
|
|
53
|
+
|
|
54
|
+
## Потік виконання / Використання
|
|
55
|
+
|
|
56
|
+
### Library-режим (виклик з оркестратора)
|
|
57
|
+
|
|
58
|
+
1. CLI-оркестратор пакета (`npx @nitra/cursor fix` або агрегований прогон усіх правил) робить `import('npm/rules/security/fix.mjs')` і отримує іменований експорт `run`.
|
|
59
|
+
2. Оркестратор готує спільний `RuleContext` (наприклад, ініціалізує `walkCache`, парсить аргументи) і викликає `await run(ctx)`.
|
|
60
|
+
3. `run` повертає `runStandardRule(import.meta.dirname, ctx)` — це promise з exit-code правила.
|
|
61
|
+
4. Оркестратор агрегує exit-коди всіх правил для фінального summary.
|
|
62
|
+
|
|
63
|
+
### Standalone-режим (`bun rules/security/fix.mjs` / `node ...`)
|
|
64
|
+
|
|
65
|
+
1. Після завершення імпортів виконується умовний блок `if (isRunAsCli(import.meta.url)) { ... }`.
|
|
66
|
+
2. `isRunAsCli(import.meta.url)` повертає `true`, лише якщо поточний модуль є entry-point процесу (`process.argv[1]` відповідає `import.meta.url`). Це гарантує, що CLI-логіка не спрацює при звичайному імпорті.
|
|
67
|
+
3. У цій гілці викликається `await runRuleCli(import.meta.dirname)` — повний CLI-цикл правила, еквівалентний `npx @nitra/cursor fix security`: завантаження конфігурації, застосування whitelist, друк summary, повернення exit-code.
|
|
68
|
+
4. Отриманий exit-code передається у `process.exit(...)`, щоб CI/IDE отримали коректний статус.
|
|
69
|
+
|
|
70
|
+
Top-level `await` всередині блоку дозволено лише тому, що файл — ESM (`.mjs`), а сам блок виконується тільки в standalone-режимі.
|
|
71
|
+
|
|
72
|
+
### Коментарі ESLint у файлі
|
|
73
|
+
|
|
74
|
+
Рядок із `process.exit(await runRuleCli(...))` має локальну директиву:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Це свідома відмова від загальної заборони `process.exit`: для CLI entry-point потрібно повертати числовий код, інакше CI/IDE не зможуть розрізнити OK і порушення. Заборона залишається активною для решти кодової бази.
|
|
81
|
+
|
|
82
|
+
### Місце у системі правил
|
|
83
|
+
|
|
84
|
+
- Директорія `npm/rules/security/` визначає правило з `id = security`. Сам `fix.mjs` не вшиває цей `id` рядком — він виводиться зі шляху через `import.meta.dirname`, який передається до `runStandardRule` / `runRuleCli`.
|
|
85
|
+
- Конкретна логіка перевірок (фази `applies` / `JS-concerns` / `policy` / `mdc-refs`) лежить у сусідніх файлах директорії правила та в `runStandardRule`; цей файл лише «склеює» їх із системою CLI пакета `@nitra/cursor`.
|
|
86
|
+
- Патерн «library `run` + standalone `process.exit(await runRuleCli(...))`» однаковий для всіх правил пакета — він дозволяє запускати правило і в межах агрегованого прогону, і окремо для відладки/CI.
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# lint.mjs — Security: TruffleHog filesystem scan
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Файл `npm/rules/security/js/lint.mjs` реалізує CI-крок правила **security** для пошуку секретів у репозиторії. Це обгортка над зовнішнім інструментом [TruffleHog](https://github.com/trufflesecurity/trufflehog), яка запускає сканування всього робочого дерева (`filesystem` mode) і повертає його exit-код як результат лінтингу.
|
|
6
|
+
|
|
7
|
+
Особливості:
|
|
8
|
+
|
|
9
|
+
- **Repo-wide, а не per-file.** Модуль свідомо ігнорує перший аргумент `_files` — TruffleHog у режимі `filesystem` обробляє кореневу теку цілком і завжди сканує однаковий набір шляхів, незалежно від того, які саме файли передає рушій правил. Тому пер-файлового різновиду немає; результат однаковий для будь-якого підмножина файлів.
|
|
10
|
+
- **Один прохід на сесію.** Сканер виконується синхронно через `spawnSync`, що відповідає правилу «без паралельних лінт-запусків» у монорепо.
|
|
11
|
+
- **Контрактний інтерфейс.** Експорт `lint(files, cwd)` сумісний із загальним контрактом per-rule lint-функцій (`(files?, cwd?) => Promise<number>`), що дозволяє інтегрувати security-правило в загальний раннер однаково з іншими.
|
|
12
|
+
|
|
13
|
+
Файл не має побічних ефектів на момент імпорту: запуск зовнішнього процесу відбувається лише при виклику `lint()`.
|
|
14
|
+
|
|
15
|
+
## Експорти / API
|
|
16
|
+
|
|
17
|
+
| Експорт | Тип | Призначення |
|
|
18
|
+
| ------- | ------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| `lint` | `function` (named export) | Запускає TruffleHog filesystem-скан із кореня `cwd` і повертає `Promise<number>` із exit-кодом процесу. |
|
|
20
|
+
|
|
21
|
+
Default-експорту немає. Імпорт відбувається у вигляді:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
import { lint } from './lint.mjs'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Функції
|
|
28
|
+
|
|
29
|
+
### `lint(_files, cwd?)`
|
|
30
|
+
|
|
31
|
+
**Сигнатура:**
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
export function lint(_files, cwd = process.cwd()) => Promise<number>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Параметри:**
|
|
38
|
+
|
|
39
|
+
| Параметр | Тип | Обовʼязковий | За замовчанням | Опис |
|
|
40
|
+
| -------- | ----------------------- | ------------ | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| `_files` | `string[] \| undefined` | ні | — | Список файлів від рушія правил. **Ігнорується**: TruffleHog у `filesystem`-режимі сканує дерево цілком. Префікс `_` у JSDoc сигналізує про навмисне ігнорування. |
|
|
42
|
+
| `cwd` | `string` | ні | `process.cwd()` | Робоча тека, у якій запускається TruffleHog. Сканування `.` виконуватиметься відносно неї. |
|
|
43
|
+
|
|
44
|
+
**Повертає:**
|
|
45
|
+
|
|
46
|
+
`Promise<number>` — exit-код процесу TruffleHog:
|
|
47
|
+
|
|
48
|
+
- `0` — секретів не знайдено (успіх).
|
|
49
|
+
- Будь-яке інше число — або знайдено секрети, що відповідають критеріям `--results=verified,unknown` (через флаг `--fail`), або TruffleHog не зміг запуститись/упав.
|
|
50
|
+
- `1` — fallback, який повертається, якщо `r.status` не є числом (наприклад, процес було вбито сигналом і `status === null`).
|
|
51
|
+
|
|
52
|
+
**Side effects:**
|
|
53
|
+
|
|
54
|
+
1. Запускає синхронний дочірній процес `trufflehog` через `node:child_process#spawnSync`.
|
|
55
|
+
2. `stdio: 'inherit'` — stdout/stderr/stdin успадковуються з батьківського процесу; вивід TruffleHog потрапляє безпосередньо в консоль CI-runner.
|
|
56
|
+
3. Блокує event loop до завершення процесу (`spawnSync` синхронний; результат лише обгортається у `Promise.resolve`).
|
|
57
|
+
4. Не записує файли, не модифікує env, не змінює стан модуля.
|
|
58
|
+
|
|
59
|
+
**Внутрішня механіка:**
|
|
60
|
+
|
|
61
|
+
Викликається TruffleHog із набором аргументів:
|
|
62
|
+
|
|
63
|
+
| Аргумент | Значення |
|
|
64
|
+
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
65
|
+
| Команда | `trufflehog` (резолвиться через `PATH`) |
|
|
66
|
+
| Subcommand | `filesystem` |
|
|
67
|
+
| Шлях | `.` (відносно `cwd`) |
|
|
68
|
+
| `--no-update` | Не намагатися автооновити сам TruffleHog у CI. |
|
|
69
|
+
| `--exclude-paths` | `.trufflehog-exclude` — файл зі шляхами/патернами, які виключити зі сканування. Має лежати в `cwd`. |
|
|
70
|
+
| `--results=verified,unknown` | Звітувати лише **верифіковані** секрети та секрети **невідомого** статусу верифікації (відсікти `unverified`, щоб зменшити шум). |
|
|
71
|
+
| `--fail` | Завершуватись із ненульовим exit-кодом, якщо знайдено результати, що відповідають фільтру вище. |
|
|
72
|
+
|
|
73
|
+
Параметри `spawnSync`:
|
|
74
|
+
|
|
75
|
+
- `cwd` — переданий або `process.cwd()`.
|
|
76
|
+
- `stdio: 'inherit'` — прямий проброс I/O.
|
|
77
|
+
|
|
78
|
+
Після завершення:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
return Promise.resolve(typeof r.status === 'number' ? r.status : 1)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
— безпечна нормалізація: якщо `status` відсутній (наприклад, процес перерваний сигналом → `r.status === null`, `r.signal === 'SIGTERM'`), функція повертає `1` як індикатор помилки.
|
|
85
|
+
|
|
86
|
+
**Контрактні нотатки:**
|
|
87
|
+
|
|
88
|
+
- Функція **не кидає виключення** для звичайних випадків (TruffleHog знайшов секрети або не запустився) — все нормалізується в exit-код через `Promise.resolve`. Якщо `spawnSync` сам кине синхронне виключення (наприклад, ENOENT через відсутність бінарника та некоректну поведінку runtime), воно пробʼється вгору — але стандартна поведінка Node для відсутнього виконуваного файлу — повернути `r.error`, а `r.status === null`, тому функція дасть `1`.
|
|
89
|
+
- Поверне `Promise`, навіть якщо все відбулось синхронно — це потрібно для уніфікованого `await` у виклику з рушія правил.
|
|
90
|
+
|
|
91
|
+
## Залежності
|
|
92
|
+
|
|
93
|
+
### Зовнішні (npm) бібліотеки
|
|
94
|
+
|
|
95
|
+
Жодних — модуль не імпортує сторонні пакети.
|
|
96
|
+
|
|
97
|
+
### Стандартна бібліотека Node.js
|
|
98
|
+
|
|
99
|
+
- **`node:child_process`** — використовується `spawnSync` для синхронного запуску `trufflehog`. Імпорт із префіксом `node:` обовʼязковий за правилами проекту.
|
|
100
|
+
|
|
101
|
+
### Системні залежності
|
|
102
|
+
|
|
103
|
+
- **`trufflehog`** (бінарник) — має бути доступний у `PATH` середовища, де запускається CI-крок. Інакше `spawnSync` поверне об'єкт із `error` (ENOENT) та `status === null`, і функція віддасть `1`.
|
|
104
|
+
- **Файл `.trufflehog-exclude`** — очікується у корені `cwd`. Це plaintext-файл із патернами шляхів для виключення (формат TruffleHog `--exclude-paths`). Якщо файл відсутній, TruffleHog може завершитись із ненульовим кодом — це теж буде повернуто як exit-код.
|
|
105
|
+
|
|
106
|
+
### Внутрішні модулі
|
|
107
|
+
|
|
108
|
+
- Зовнішніх внутрішньопроєктних імпортів модуль не має. Він самодостатній і викликається рушієм правил security (`npm/rules/security/...`), який інтегрує його у загальний lint-пайплайн.
|
|
109
|
+
|
|
110
|
+
## Потік виконання / Використання
|
|
111
|
+
|
|
112
|
+
### Загальний потік
|
|
113
|
+
|
|
114
|
+
1. Рушій правил (npm-пакет, що оркеструє лінтери) виявляє правило `security` для активного workspace.
|
|
115
|
+
2. Викликає `lint(files, cwd)` із `files` (списком змінених/вибраних файлів) та `cwd` (коренем workspace або репо).
|
|
116
|
+
3. `lint`:
|
|
117
|
+
- Ігнорує `files`.
|
|
118
|
+
- Викликає `spawnSync('trufflehog', [...], { cwd, stdio: 'inherit' })`.
|
|
119
|
+
- Користувач/CI бачить весь stdout/stderr TruffleHog у реальному часі.
|
|
120
|
+
- Після завершення процесу повертає `Promise<exitCode>`.
|
|
121
|
+
4. Рушій правил агрегує exit-коди всіх лінтерів і визначає загальний результат (зазвичай: будь-яке ненульове значення = провал CI).
|
|
122
|
+
|
|
123
|
+
### Типовий виклик з коду
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
import { lint } from './lint.mjs'
|
|
127
|
+
|
|
128
|
+
const exitCode = await lint(undefined, process.cwd())
|
|
129
|
+
if (exitCode !== 0) {
|
|
130
|
+
console.error('[security] TruffleHog знайшов секрети або не зміг виконатись')
|
|
131
|
+
process.exitCode = exitCode
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### CI-сценарій
|
|
136
|
+
|
|
137
|
+
1. CI-runner клонує репозиторій і встановлює бінарник `trufflehog` (через apt/brew/curl/прекомпільований release).
|
|
138
|
+
2. Запускається `n-cursor lint` (або еквівалент), який обходить правила.
|
|
139
|
+
3. Для правила `security` рушій кличе цю `lint`-функцію.
|
|
140
|
+
4. TruffleHog рекурсивно сканує всі файли під `cwd`, окрім перелічених у `.trufflehog-exclude`.
|
|
141
|
+
5. Якщо знайдено **верифіковані** або **unknown**-секрети — повертається ненульовий exit-код, флаг `--fail` гарантує цю поведінку.
|
|
142
|
+
6. CI завершується із помилкою, кроки нижче не виконуються.
|
|
143
|
+
|
|
144
|
+
### Граничні випадки
|
|
145
|
+
|
|
146
|
+
| Ситуація | Поведінка |
|
|
147
|
+
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
148
|
+
| `trufflehog` відсутній у `PATH` | `spawnSync` повертає `r.status === null`, функція віддає `1`. У stderr (через `inherit`) може зʼявитись повідомлення Node про відсутність бінарника, залежно від платформи. |
|
|
149
|
+
| `.trufflehog-exclude` відсутній | TruffleHog зазвичай помиляється з ненульовим кодом — функція поверне його як є. |
|
|
150
|
+
| Процес вбито сигналом | `r.status === null` → повертається `1`. |
|
|
151
|
+
| Секретів немає | TruffleHog повертає `0` → функція повертає `0`. |
|
|
152
|
+
| Передано `files=[...]` | Аргумент проігноровано; сканування все одно зачіпає всю теку `cwd`. |
|
|
153
|
+
| `cwd` не вказано | Використовується `process.cwd()` поточного Node-процесу. |
|
|
154
|
+
|
|
155
|
+
### Конфігураційні «крутилки»
|
|
156
|
+
|
|
157
|
+
Самі прапори зашиті в код і не параметризуються через аргументи функції. Якщо потрібно змінити поведінку (інший шлях `--exclude-paths`, інші типи результатів, увімкнути `--update`), правки робляться безпосередньо в `lint.mjs` — це навмисно, щоб уніфіковано тримати security-політику для всього монорепо.
|
|
158
|
+
|
|
159
|
+
## Rebuild Test
|
|
160
|
+
|
|
161
|
+
Перевірка контракту (для майбутніх refactor-ів — поведінка має зберігатись):
|
|
162
|
+
|
|
163
|
+
1. **Експорт** named `lint` присутній; default-експорту немає.
|
|
164
|
+
2. **Сигнатура** `lint(_files, cwd = process.cwd())` повертає `Promise<number>`.
|
|
165
|
+
3. **Викликана команда** — рівно `trufflehog` із аргументами у точному порядку:
|
|
166
|
+
`filesystem`, `.`, `--no-update`, `--exclude-paths`, `.trufflehog-exclude`, `--results=verified,unknown`, `--fail`.
|
|
167
|
+
4. **Options** для `spawnSync`: `{ cwd, stdio: 'inherit' }`.
|
|
168
|
+
5. **Нормалізація exit-коду**: число → пробрасується; не число → `1`.
|
|
169
|
+
6. **`_files` ігнорується** — наявність/відсутність/вміст не змінює викликаних аргументів TruffleHog.
|
|
170
|
+
7. **Імпорт `child_process`** — лише через `node:`-префікс.
|
|
171
|
+
8. **Жодних побічних ефектів на імпорті** — `lint` запускає процес лише при виклику.
|