@nitra/cursor 3.22.0 → 3.23.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/.pi-template/extensions/n-cursor-adr/docs/index.md +181 -0
- package/CHANGELOG.md +31 -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 +2 -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,200 @@
|
|
|
1
|
+
# `change-file.mjs`
|
|
2
|
+
|
|
3
|
+
Модуль для роботи з одиничним **change-файлом** релізного процесу — невеликою markdown-нотаткою, що описує одну зміну в межах workspace і агрегується пізніше при формуванні CHANGELOG / bump версії.
|
|
4
|
+
|
|
5
|
+
## Огляд
|
|
6
|
+
|
|
7
|
+
Файли change-нотаток лежать у `<ws>/.changes/<timestamp>-<rand>.md` (де `<ws>` — корінь окремого workspace монорепо) і мають мінімалістичний YAML-подібний frontmatter із рівно двома ключами:
|
|
8
|
+
|
|
9
|
+
- `bump` — рівень semver-бампу (`major` | `minor` | `patch`);
|
|
10
|
+
- `section` — секція Keep a Changelog (`Added` | `Changed` | `Fixed` | `Removed`).
|
|
11
|
+
|
|
12
|
+
Після `---` йде довільний markdown-опис зміни.
|
|
13
|
+
|
|
14
|
+
Модуль повністю **самодостатній**: без зовнішніх npm-залежностей; парсер frontmatter — простий рядковий розбір на `:`, без YAML-бібліотеки. Це навмисне обмеження — підтримуються тільки два передбачувані ключі, що знімає клас помилок (мультирядкові значення, типи, escape).
|
|
15
|
+
|
|
16
|
+
Сценарії використання:
|
|
17
|
+
|
|
18
|
+
- запис нової нотатки з CLI/агента (`newChangeFileName` + `serializeChangeFile`);
|
|
19
|
+
- читання усіх нотаток workspace для агрегації (`readChangeFiles`);
|
|
20
|
+
- одинична валідація/розбір (`parseChangeFile`).
|
|
21
|
+
|
|
22
|
+
## Експорти / API
|
|
23
|
+
|
|
24
|
+
| Експорт | Тип | Призначення |
|
|
25
|
+
| ----------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
+
| `VALID_BUMPS` | `readonly string[]` | Дозволені semver-бампи `['major', 'minor', 'patch']` у порядку від найбільшого до найменшого (порядок є частиною контракту — використовується зовнішнім агрегатором для обчислення `max`). Заморожено `Object.freeze`. |
|
|
27
|
+
| `VALID_SECTIONS` | `readonly string[]` | Дозволені секції Keep a Changelog: `['Added', 'Changed', 'Fixed', 'Removed']`. Заморожено `Object.freeze`. |
|
|
28
|
+
| `CHANGES_DIR` | `string` | Назва підкаталогу з change-файлами в межах workspace — `.changes`. |
|
|
29
|
+
| `parseChangeFile(text)` | `function` | Парсить вміст change-файлу у структурований запис. Кидає `Error` при будь-яких відхиленнях. |
|
|
30
|
+
| `serializeChangeFile(entry)` | `function` | Серіалізує запис назад у текст change-файлу (frontmatter + опис + завершальний `\n`). |
|
|
31
|
+
| `changeFileName(timestamp, suffix)` | `function` | Формує імʼя файлу `<timestamp>-<suffix>.md`. |
|
|
32
|
+
| `newChangeFileName()` | `function` | Згенерувати унікальне імʼя для нового change-файлу (`Date.now()` + 3 байти hex). |
|
|
33
|
+
| `readChangeFiles(ws, cwd?)` | `async function` | Прочитати й розпарсити всі `.md`-файли з `<cwd>/<ws>/.changes/`. |
|
|
34
|
+
|
|
35
|
+
Внутрішнє (не експортується):
|
|
36
|
+
|
|
37
|
+
- `FRONTMATTER_RE` — regexp `/^---\n([\s\S]*?)\n---\n([\s\S]*)$/` для виокремлення frontmatter і тіла.
|
|
38
|
+
- `parseFrontmatterBlock(block)` — допоміжний парсер «ключ: значення» по рядках.
|
|
39
|
+
|
|
40
|
+
## Функції
|
|
41
|
+
|
|
42
|
+
### `parseFrontmatterBlock(block)` (внутрішня)
|
|
43
|
+
|
|
44
|
+
- **Сигнатура:** `(block: string) => Record<string, string>`
|
|
45
|
+
- **Параметри:**
|
|
46
|
+
- `block` — тіло frontmatter (рядки між `---`).
|
|
47
|
+
- **Повертає:** обʼєкт із ключами/значеннями, кожен взятий із рядка через перший `:`; обидві частини `.trim()`-нуті. Рядки без `:` ігноруються.
|
|
48
|
+
- **Side effects:** немає (чиста функція).
|
|
49
|
+
- **Обмеження:** не підтримує квоти, escape, мультирядкові значення; коментарі `#` не розпізнаються (рядок із `#` обробляється як звичайна пара ключ-значення, якщо містить `:`).
|
|
50
|
+
|
|
51
|
+
### `parseChangeFile(text)`
|
|
52
|
+
|
|
53
|
+
- **Сигнатура:** `(text: string) => { bump: string, section: string, description: string }`
|
|
54
|
+
- **Параметри:**
|
|
55
|
+
- `text` — повний вміст change-файлу (frontmatter + опис).
|
|
56
|
+
- **Повертає:** обʼєкт із трьома полями: `bump`, `section`, `description` (опис — з `.trim()`).
|
|
57
|
+
- **Кидає `Error`:**
|
|
58
|
+
- `change-файл: відсутній frontmatter \`---\``— якщо текст не відповідає`FRONTMATTER_RE`.
|
|
59
|
+
- `change-файл: bump має бути одним із major|minor|patch (отримано «…»)` — якщо `bump` поза `VALID_BUMPS` (включно з відсутнім ключем — тоді в підстановці буде порожній рядок).
|
|
60
|
+
- `change-файл: section має бути одним із Added|Changed|Fixed|Removed (отримано «…»)` — якщо `section` поза `VALID_SECTIONS`.
|
|
61
|
+
- `change-файл: порожній опис` — якщо тіло після frontmatter порожнє після `.trim()`.
|
|
62
|
+
- **Side effects:** немає (чиста функція).
|
|
63
|
+
- **Примітка:** повідомлення помилок українською — використовуються користувачем як діагностика прямо в CLI.
|
|
64
|
+
|
|
65
|
+
### `serializeChangeFile(entry)`
|
|
66
|
+
|
|
67
|
+
- **Сигнатура:** `(entry: { bump: string, section: string, description: string }) => string`
|
|
68
|
+
- **Параметри:**
|
|
69
|
+
- `entry.bump` — один із `VALID_BUMPS` (функція **не** валідує — припускається, що валідація вже зроблена або значення підконтрольне виклику).
|
|
70
|
+
- `entry.section` — один із `VALID_SECTIONS` (без валідації).
|
|
71
|
+
- `entry.description` — текст опису.
|
|
72
|
+
- **Повертає:** рядок виду:
|
|
73
|
+
```
|
|
74
|
+
---
|
|
75
|
+
bump: <bump>
|
|
76
|
+
section: <section>
|
|
77
|
+
---
|
|
78
|
+
<description>
|
|
79
|
+
```
|
|
80
|
+
із завершальним `\n` у кінці.
|
|
81
|
+
- **Side effects:** немає (чиста функція).
|
|
82
|
+
- **Round-trip:** `parseChangeFile(serializeChangeFile(entry))` повертає еквівалентний запис, якщо `entry.description` уже трімнутий.
|
|
83
|
+
|
|
84
|
+
### `changeFileName(timestamp, suffix)`
|
|
85
|
+
|
|
86
|
+
- **Сигнатура:** `(timestamp: number, suffix: string) => string`
|
|
87
|
+
- **Параметри:**
|
|
88
|
+
- `timestamp` — епоха у мс (зазвичай `Date.now()`).
|
|
89
|
+
- `suffix` — короткий випадковий суфікс (як правило hex).
|
|
90
|
+
- **Повертає:** `\`${timestamp}-${suffix}.md\``.
|
|
91
|
+
- **Side effects:** немає (чиста функція).
|
|
92
|
+
|
|
93
|
+
### `newChangeFileName()`
|
|
94
|
+
|
|
95
|
+
- **Сигнатура:** `() => string`
|
|
96
|
+
- **Параметри:** немає.
|
|
97
|
+
- **Повертає:** `\`<Date.now()>-<rand6hex>.md\``, де `rand6hex`—`randomBytes(3).toString('hex')` (6 hex-символів).
|
|
98
|
+
- **Side effects:** виклик `Date.now()` і CSPRNG `crypto.randomBytes(3)` — результат недетермінований.
|
|
99
|
+
- **Призначення:** анти-колізія для випадку, коли кілька паралельних агентів у різних git-worktree пишуть нову нотатку в ту саму мілісекунду. Порядкове сортування за timestamp залишається стабільним; випадковий хвіст лише розриває нічию.
|
|
100
|
+
|
|
101
|
+
### `readChangeFiles(ws, cwd = process.cwd())`
|
|
102
|
+
|
|
103
|
+
- **Сигнатура:** `async (ws: string, cwd?: string) => Array<{ file: string, entry: { bump: string, section: string, description: string } }>`
|
|
104
|
+
- **Параметри:**
|
|
105
|
+
- `ws` — шлях workspace (як правило відносний відносно `cwd`, наприклад `npm/foo`).
|
|
106
|
+
- `cwd` — корінь репозиторію; за замовчуванням `process.cwd()`.
|
|
107
|
+
- **Повертає:** масив записів `{ file, entry }`, відсортований за іменем файлу через `Array#toSorted()` (лексикографічно; завдяки префіксу `<timestamp>` відповідає хронологічному порядку для timestamp однакової довжини).
|
|
108
|
+
- **Side effects:**
|
|
109
|
+
- синхронна перевірка існування каталогу `<cwd>/<ws>/.changes/` через `existsSync`;
|
|
110
|
+
- читання списку директорії `readdir` (async);
|
|
111
|
+
- послідовне (не паралельне) читання кожного `.md`-файлу через `readFile(…, 'utf8')`.
|
|
112
|
+
- **Поведінка крайових випадків:**
|
|
113
|
+
- якщо каталог `.changes` відсутній — повертає `[]` без помилок;
|
|
114
|
+
- якщо каталог існує, але порожній або не містить `.md` — повертає `[]`;
|
|
115
|
+
- якщо будь-який файл містить невалідний frontmatter / bump / section / опис — пробросає `Error` із `parseChangeFile` (не глушиться).
|
|
116
|
+
- **Сортування:** `.toSorted()` — імʼя файлу як рядок, тому порядок: чим менший timestamp — тим раніше; для однакового timestamp вирішує hex-суфікс.
|
|
117
|
+
|
|
118
|
+
## Залежності
|
|
119
|
+
|
|
120
|
+
Тільки стандартна бібліотека Node.js (без npm-залежностей):
|
|
121
|
+
|
|
122
|
+
- `node:crypto` — `randomBytes` (генерація випадкового суфікса для імені файлу).
|
|
123
|
+
- `node:fs` — `existsSync` (швидка перевірка існування `.changes`).
|
|
124
|
+
- `node:fs/promises` — `readdir`, `readFile` (читання каталогу і вмісту файлів).
|
|
125
|
+
- `node:path` — `join` (склейка шляху `cwd/ws/.changes`).
|
|
126
|
+
|
|
127
|
+
Внутрішні залежності модуля від інших файлів проєкту відсутні. Це робить модуль безпечно імпортованим із будь-якого контексту (CLI, тест, агент, ESLint-плагін).
|
|
128
|
+
|
|
129
|
+
## Потік виконання / Використання
|
|
130
|
+
|
|
131
|
+
### Створення нової нотатки
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
import { writeFile, mkdir } from 'node:fs/promises'
|
|
135
|
+
import { join } from 'node:path'
|
|
136
|
+
import { CHANGES_DIR, newChangeFileName, serializeChangeFile } from './change-file.mjs'
|
|
137
|
+
|
|
138
|
+
const ws = 'npm/some-pkg'
|
|
139
|
+
const dir = join(process.cwd(), ws, CHANGES_DIR)
|
|
140
|
+
await mkdir(dir, { recursive: true })
|
|
141
|
+
|
|
142
|
+
const text = serializeChangeFile({
|
|
143
|
+
bump: 'patch',
|
|
144
|
+
section: 'Fixed',
|
|
145
|
+
description: 'Виправлено падіння CLI при відсутньому `.changes`.'
|
|
146
|
+
})
|
|
147
|
+
await writeFile(join(dir, newChangeFileName()), text)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Читання й агрегація
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
import { readChangeFiles, VALID_BUMPS } from './change-file.mjs'
|
|
154
|
+
|
|
155
|
+
const items = await readChangeFiles('npm/some-pkg')
|
|
156
|
+
// items: [{ file: '1700000000000-ab12cd.md', entry: { bump, section, description } }, …]
|
|
157
|
+
|
|
158
|
+
// Обчислити максимальний bump: VALID_BUMPS впорядковані від major → patch,
|
|
159
|
+
// тож менший індекс = «більший» bump.
|
|
160
|
+
const maxIdx = items.reduce((acc, { entry }) => Math.min(acc, VALID_BUMPS.indexOf(entry.bump)), VALID_BUMPS.length)
|
|
161
|
+
const finalBump = VALID_BUMPS[maxIdx] // або undefined, якщо items порожній
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Валідація одного файлу
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
import { readFile } from 'node:fs/promises'
|
|
168
|
+
import { parseChangeFile } from './change-file.mjs'
|
|
169
|
+
|
|
170
|
+
const text = await readFile('npm/some-pkg/.changes/1700000000000-ab12cd.md', 'utf8')
|
|
171
|
+
const entry = parseChangeFile(text) // кине Error із локалізованим повідомленням, якщо файл невалідний
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Контракт frontmatter
|
|
175
|
+
|
|
176
|
+
Точний формат, який очікує `FRONTMATTER_RE`:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
---
|
|
180
|
+
bump: patch
|
|
181
|
+
section: Fixed
|
|
182
|
+
---
|
|
183
|
+
<довільний markdown-опис>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Особливості:
|
|
187
|
+
|
|
188
|
+
- frontmatter відкривається/закривається саме рядком `---` (3 дефіси, без пробілів);
|
|
189
|
+
- символ розділювача — `\n` (LF); CRLF не підтримується regexp-ом без додаткової нормалізації;
|
|
190
|
+
- ключі `bump` і `section` мають бути в нижньому регістрі точно так, як у `VALID_BUMPS`/`VALID_SECTIONS`;
|
|
191
|
+
- значення секції — у титульному регістрі (`Added`, не `added`), оскільки воно безпосередньо використовується як заголовок `### {section}` у CHANGELOG;
|
|
192
|
+
- зайві ключі у frontmatter не забороняються парсером (вони просто потрапляють у `out`-обʼєкт і ігноруються верхнім рівнем), але контракт модуля передбачає лише два;
|
|
193
|
+
- після `---` має бути непорожній (після `trim`) markdown-опис, інакше — помилка.
|
|
194
|
+
|
|
195
|
+
### Інваріанти, корисні викликачу
|
|
196
|
+
|
|
197
|
+
- `parseChangeFile(serializeChangeFile(e)) ≡ e` для будь-якого валідного `e` із трімнутим `description`.
|
|
198
|
+
- `newChangeFileName()` завжди відповідає шаблону `^\d+-[0-9a-f]{6}\.md$`.
|
|
199
|
+
- `readChangeFiles` ніколи не повертає шляхи поза `<cwd>/<ws>/.changes/` — лише імена файлів у полі `file`.
|
|
200
|
+
- Відсутність `.changes` — не помилка, а сигнал «змін немає».
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# fallback.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `fallback.mjs` реалізує **третє рішення** з ADR `n-cursor-release-design`: коли в певному workspace монорепо виявлено релевантні зміни, але **жодного change-файлу** від розробника не з’явилося, релізний пайплайн все одно повинен мати запис у CHANGELOG. Для цього модуль **синтезує** один штучний запис `change` із commit-subject-ів, які з’явилися від моменту останнього релізного тегу формату `<name>@*` до `HEAD`, обмежуючи журнал git pathspec-ом самого workspace.
|
|
6
|
+
|
|
7
|
+
Файл експортує дві функції:
|
|
8
|
+
|
|
9
|
+
- `defaultRunGit(cwd)` — фабрика «тихого» git-раннера: повертає stdout або `null` у разі будь-якої помилки (без кидання винятків).
|
|
10
|
+
- `synthesizeChangeFromCommits(name, ws, opts?)` — асинхронна функція, що повертає одну синтетичну change-запис `{ bump, section, description }` або `null`, якщо синтез неможливий (bootstrap-релізу немає тегу або немає комітів у діапазоні).
|
|
11
|
+
|
|
12
|
+
Усі git-виклики **навмисно** йдуть через інжектований раннер (`opts.runGit`), щоб тести могли мокати git без реальних `execFile`-викликів.
|
|
13
|
+
|
|
14
|
+
## Експорти / API
|
|
15
|
+
|
|
16
|
+
| Експорт | Тип | Призначення |
|
|
17
|
+
| ---------------------------------------------- | -------------------- | --------------------------------------------------------------- |
|
|
18
|
+
| `defaultRunGit(cwd)` | named function | Створює дефолтний git-раннер, прив’язаний до конкретного `cwd`. |
|
|
19
|
+
| `synthesizeChangeFromCommits(name, ws, opts?)` | named async function | Синтезує один change-запис із git-історії або повертає `null`. |
|
|
20
|
+
|
|
21
|
+
Default-експортів немає.
|
|
22
|
+
|
|
23
|
+
### TypeScript-подібна сигнатура
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
defaultRunGit(cwd: string): (args: string[]) => Promise<string | null>
|
|
27
|
+
|
|
28
|
+
synthesizeChangeFromCommits(
|
|
29
|
+
name: string,
|
|
30
|
+
ws: string,
|
|
31
|
+
opts?: { runGit?: (args: string[]) => Promise<string | null> }
|
|
32
|
+
): Promise<{ bump: 'patch'; section: 'Changed'; description: string } | null>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Функції
|
|
36
|
+
|
|
37
|
+
### `defaultRunGit(cwd)`
|
|
38
|
+
|
|
39
|
+
**Сигнатура:** `function defaultRunGit(cwd: string): (args: string[]) => Promise<string | null>`
|
|
40
|
+
|
|
41
|
+
**Параметри:**
|
|
42
|
+
|
|
43
|
+
- `cwd` _(string)_ — абсолютний або відносний шлях до робочого каталогу, у якому виконуються git-команди.
|
|
44
|
+
|
|
45
|
+
**Повертає:** функцію-раннер `async (args: string[]) => Promise<string | null>`. Раннер:
|
|
46
|
+
|
|
47
|
+
- виконує `git <...args>` у заданому `cwd` через `execFile` (без shell, без інтерполяції аргументів);
|
|
48
|
+
- у разі успіху повертає **повний stdout** як рядок (без обрізання, без декодування);
|
|
49
|
+
- у разі будь-якої помилки (non-zero exit, ENOENT, відсутність git, repo not found тощо) повертає `null` — **жоден виняток не пробивається назовні**.
|
|
50
|
+
|
|
51
|
+
**Side effects:**
|
|
52
|
+
|
|
53
|
+
- Спавнить дочірній процес `git` через `node:child_process.execFile`.
|
|
54
|
+
- Читає файлову систему репозиторію (через сам git).
|
|
55
|
+
- Не пише у stdout/stderr батьківського процесу, не змінює стан репо.
|
|
56
|
+
|
|
57
|
+
**Дизайнерські рішення:**
|
|
58
|
+
|
|
59
|
+
- `execFile` (а не `exec`) — захист від shell-injection; аргументи передаються як масив.
|
|
60
|
+
- `try/catch` обгортає виклик, перетворюючи помилку на `null`. Це дозволяє викликачу не дбати про обробку винятків і однаково реагувати на «команда не виконалась» та «команда повернула порожньо».
|
|
61
|
+
|
|
62
|
+
### `synthesizeChangeFromCommits(name, ws, opts?)`
|
|
63
|
+
|
|
64
|
+
**Сигнатура:**
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
async function synthesizeChangeFromCommits(
|
|
68
|
+
name: string,
|
|
69
|
+
ws: string,
|
|
70
|
+
opts?: { runGit?: (args: string[]) => Promise<string | null> }
|
|
71
|
+
): Promise<{ bump: string; section: string; description: string } | null>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Параметри:**
|
|
75
|
+
|
|
76
|
+
- `name` _(string)_ — ім’я npm-пакета. Використовується для побудови патерну тегу `<name>@*` у `git describe --match`.
|
|
77
|
+
- `ws` _(string)_ — workspace, що виступає pathspec-ом для `git log`. Спеціальне значення `'.'` означає «весь репозиторій, без обмеження шляху»; будь-яке інше значення інтерпретується як директорія і додається до команди як `-- <ws>/`.
|
|
78
|
+
- `opts` _(object, optional)_ — опції:
|
|
79
|
+
- `opts.runGit` _((args) => Promise\<string|null\>)_ — кастомний git-раннер (ін’єкція для тестів). Якщо не задано, використовується `defaultRunGit(process.cwd())`.
|
|
80
|
+
|
|
81
|
+
**Повертає:** `Promise` що резолвиться в:
|
|
82
|
+
|
|
83
|
+
- **об’єкт** `{ bump: 'patch', section: 'Changed', description: <string> }` — синтетичний change-запис, де `description` є склейкою всіх commit-subject-ів через роздільник `'; '`;
|
|
84
|
+
- **`null`** — у двох випадках:
|
|
85
|
+
1. **Bootstrap-кейс:** `git describe` не знайшов жодного попереднього тегу `<name>@*` (повернув `null` або порожній рядок після `trim`). Перший реліз робиться вручну, fallback не повинен дублювати bump.
|
|
86
|
+
2. **No-op кейс:** теги є, але `git log <lastTag>..HEAD` для даного workspace не повернув жодного непорожнього subject-а (немає змін у скоупі workspace після останнього релізу).
|
|
87
|
+
|
|
88
|
+
**Side effects:**
|
|
89
|
+
|
|
90
|
+
- Через `runGit` спавнить **до двох** git-процесів (`git describe`, потім `git log`).
|
|
91
|
+
- Не пише на диск, не мутує жодних аргументів.
|
|
92
|
+
|
|
93
|
+
**Алгоритм покроково:**
|
|
94
|
+
|
|
95
|
+
1. Розв’язати раннер: `runGit = opts.runGit ?? defaultRunGit(process.cwd())`.
|
|
96
|
+
2. Викликати `git describe --tags --abbrev=0 --match <name>@* HEAD`. Якщо результат — `null`/порожньо/whitespace після `trim()` — повернути `null` (bootstrap).
|
|
97
|
+
3. Сформувати pathspec: `[]` для `ws === '.'`, інакше `['--', `${ws}/`]`.
|
|
98
|
+
4. Викликати `git log --no-merges --format=%s <lastTag>..HEAD [-- <ws>/]`.
|
|
99
|
+
5. Розділити stdout по `\n`, обрізати кожен рядок, відкинути порожні (`filter(Boolean)`).
|
|
100
|
+
6. Якщо масив subjects порожній — повернути `null`.
|
|
101
|
+
7. Інакше — повернути `{ bump: 'patch', section: 'Changed', description: subjects.join('; ') }`.
|
|
102
|
+
|
|
103
|
+
**Інваріанти / контракти:**
|
|
104
|
+
|
|
105
|
+
- `bump` завжди дорівнює рядку `'patch'` — fallback не вгадує `minor`/`major`; підвищити рівень bump-у можна лише явним change-файлом.
|
|
106
|
+
- `section` завжди `'Changed'` — без класифікації за типом коміту (Added/Fixed/тощо).
|
|
107
|
+
- `description` ніколи не порожній (інакше функція повертає `null`).
|
|
108
|
+
- Якщо `logRaw` дорівнює `null` (git-помилка) — це трактується **тотожно** з порожнім логом: `(logRaw ?? '').split('\n')` дасть `['']`, що після фільтрів стане `[]` → результат `null`. Тобто помилка `git log` свідомо **не** кидається назовні; це частина контракту «тихого» раннера.
|
|
109
|
+
|
|
110
|
+
## Залежності
|
|
111
|
+
|
|
112
|
+
### Зовнішні (стандартна бібліотека Node.js)
|
|
113
|
+
|
|
114
|
+
- **`node:child_process.execFile`** — запуск дочірнього процесу `git` без shell.
|
|
115
|
+
- **`node:util.promisify`** — перетворення `execFile` callback-стилю на `Promise`-сумісний `execFileAsync`.
|
|
116
|
+
|
|
117
|
+
### Внутрішні
|
|
118
|
+
|
|
119
|
+
Файл **не імпортує** жодних внутрішніх модулів проєкту. Він самодостатній і використовується іншими модулями релізного пайплайна (зокрема `aggregate.mjs` у тому ж каталозі) як інструмент синтезу запису.
|
|
120
|
+
|
|
121
|
+
### Передумови середовища
|
|
122
|
+
|
|
123
|
+
- У `PATH` має бути доступний бінарник `git`.
|
|
124
|
+
- `cwd` повинен вказувати на git-репозиторій (інакше `git describe` поверне `null` і функція коректно деградує до bootstrap-кейсу).
|
|
125
|
+
- Теги релізів мають іти за конвенцією `<pkg-name>@<version>` — інакше `--match <name>@*` нічого не знайде.
|
|
126
|
+
|
|
127
|
+
## Потік виконання / Використання
|
|
128
|
+
|
|
129
|
+
### Типова інтеграція в релізному пайплайні
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
import { synthesizeChangeFromCommits } from './fallback.mjs'
|
|
133
|
+
|
|
134
|
+
// В межах одного workspace монорепо:
|
|
135
|
+
const change = await synthesizeChangeFromCommits('@scope/pkg', 'packages/pkg')
|
|
136
|
+
if (change) {
|
|
137
|
+
// Додаємо як єдиний запис у агрегацію change-ів цього релізу
|
|
138
|
+
applyChange(change)
|
|
139
|
+
} else {
|
|
140
|
+
// Або bootstrap-реліз (тегу ще не існує), або нічого не змінилося в скоупі workspace
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### З кастомним git-раннером (тестовий сценарій)
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
const fakeGit = async args => {
|
|
148
|
+
if (args[0] === 'describe') return '@scope/pkg@1.2.3\n'
|
|
149
|
+
if (args[0] === 'log') return 'fix: foo\nchore: bar\n\n'
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
const change = await synthesizeChangeFromCommits('@scope/pkg', 'packages/pkg', { runGit: fakeGit })
|
|
153
|
+
// => { bump: 'patch', section: 'Changed', description: 'fix: foo; chore: bar' }
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Сценарій «весь репо» (root-workspace)
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
// ws === '.' → pathspec не додається, log читає історію всього репо
|
|
160
|
+
await synthesizeChangeFromCommits('root', '.')
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Діаграма послідовності
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
caller
|
|
167
|
+
│
|
|
168
|
+
├─ runGit(['describe', '--tags', '--abbrev=0', '--match', `<name>@*`, 'HEAD'])
|
|
169
|
+
│ └─→ null ───► return null (bootstrap)
|
|
170
|
+
│ └─→ '<tag>\n'
|
|
171
|
+
│
|
|
172
|
+
├─ runGit(['log', '--no-merges', '--format=%s', '<tag>..HEAD', '--', '<ws>/'])
|
|
173
|
+
│ └─→ null | '' ───► return null (no commits in scope)
|
|
174
|
+
│ └─→ 'subj1\nsubj2\n…'
|
|
175
|
+
│
|
|
176
|
+
└─ return { bump: 'patch', section: 'Changed', description: 'subj1; subj2; …' }
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Ключові випадки повернення `null`
|
|
180
|
+
|
|
181
|
+
| Випадок | Причина | Реакція пайплайна |
|
|
182
|
+
| --------- | ------------------------------------------------ | ------------------------------------------------------- |
|
|
183
|
+
| Bootstrap | Немає жодного тегу `<name>@*` | Не синтезувати fallback; перший реліз — вручну. |
|
|
184
|
+
| No-op | Тег є, але `git log` у скоупі workspace порожній | У workspace немає релевантних змін → реліз не потрібен. |
|
|
185
|
+
| Git error | Будь-яка помилка `git` (через тихий раннер) | Інтерпретується як «нічого синтезувати», `null`. |
|
|
186
|
+
|
|
187
|
+
## Rebuild Test
|
|
188
|
+
|
|
189
|
+
Перевірка контекстної повноти документа: за описом вище можна **відновити** ключові властивості реалізації без перегляду коду:
|
|
190
|
+
|
|
191
|
+
1. Модуль ES (`.mjs`), імпортує `execFile` з `node:child_process` і `promisify` з `node:util`; створює `execFileAsync = promisify(execFile)`.
|
|
192
|
+
2. Експортує дві іменовані функції: `defaultRunGit(cwd)` і `synthesizeChangeFromCommits(name, ws, opts)`. Default-експорту немає.
|
|
193
|
+
3. `defaultRunGit(cwd)` повертає async-функцію `args => …`, що викликає `execFileAsync('git', args, { cwd })` у `try/catch`; в успіху — `stdout`, у будь-якій помилці — `null`.
|
|
194
|
+
4. `synthesizeChangeFromCommits`:
|
|
195
|
+
- бере `runGit` з `opts.runGit ?? defaultRunGit(process.cwd())`;
|
|
196
|
+
- `git describe --tags --abbrev=0 --match \`${name}@\*\` HEAD`→`lastTagRaw`; `lastTag = lastTagRaw?.trim()`; якщо falsy — `return null`;
|
|
197
|
+
- `pathspec = ws === '.' ? [] : ['--', \`${ws}/\`]`;
|
|
198
|
+
- `git log --no-merges --format=%s \`${lastTag}..HEAD\` ...pathspec`→`logRaw`;
|
|
199
|
+
- `subjects = (logRaw ?? '').split('\n').map(s => s.trim()).filter(Boolean)`;
|
|
200
|
+
- якщо `subjects.length === 0` → `return null`;
|
|
201
|
+
- інакше `return { bump: 'patch', section: 'Changed', description: subjects.join('; ') }`.
|
|
202
|
+
5. `bump` зашитий як `'patch'`, `section` — як `'Changed'`; роздільник опису — `'; '`.
|
|
203
|
+
6. Жодні винятки з git не пробиваються назовні — раннер обгортає їх у `null`, а `synthesizeChangeFromCommits` коректно деградує до `null` у всіх граничних кейсах.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# `fix.mjs` — entry-point правила `rust`
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Файл `npm/rules/rust/fix.mjs` — мінімальний adapter, який реєструє правило `rust` у двох ролях одночасно:
|
|
6
|
+
|
|
7
|
+
- **library mode** — модуль експортує функцію `run(ctx)`, яку викликає CLI-оркестратор пакета `@nitra/cursor` (через `import + run(ctx)`), передаючи спільний контекст прогону (наприклад, `walkCache` для шерингу обходу файлів між правилами).
|
|
8
|
+
- **standalone mode** — якщо файл запущено напряму (`bun npm/rules/rust/fix.mjs`), він поводиться як повний еквівалент команди `npx @nitra/cursor fix rust`: підвантажує конфіг, застосовує whitelist, друкує summary та повертає процесу exit-code.
|
|
9
|
+
|
|
10
|
+
Уся реальна логіка правила (порядок фаз: `applies → JS-concerns → policy → mdc-refs`) інкапсульована у двох helper-модулях зі спільної бібліотеки `npm/scripts/lib/`. Цей файл — лише тонкий wrapper, що з'єднує конвенцію розташування (`npm/rules/<id>/fix.mjs`) із цими helpers і не містить власної бізнес-логіки правила `rust`.
|
|
11
|
+
|
|
12
|
+
Файл слідує усталеному в репозиторії патерну "двох ролей `fix.mjs`": library + standalone — тому самий код використовується і коли правило виконується як частина мульти-rule прогону, і коли його запускають окремо для дебагу/CI.
|
|
13
|
+
|
|
14
|
+
## Експорти / API
|
|
15
|
+
|
|
16
|
+
Модуль експортує одну іменовану функцію:
|
|
17
|
+
|
|
18
|
+
| Експорт | Тип | Призначення |
|
|
19
|
+
| ------- | ---------------------------------------- | ------------------------------------------------------------------ |
|
|
20
|
+
| `run` | `(ctx?: RuleContext) => Promise<number>` | Library-mode entry: запуск правила `rust` зі стандартним pipeline. |
|
|
21
|
+
|
|
22
|
+
Default-експорту немає. Імпортний шлях для зовнішніх споживачів — `@nitra/cursor/rules/rust/fix.mjs` (або еквівалентний relative-шлях усередині монорепо).
|
|
23
|
+
|
|
24
|
+
Тип `RuleContext` визначений у `npm/scripts/lib/run-standard-rule.mjs` і реекспортується через JSDoc-`import('...')`-тип у сигнатурі `run`.
|
|
25
|
+
|
|
26
|
+
## Функції
|
|
27
|
+
|
|
28
|
+
### `run(ctx)`
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
export function run(ctx)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- **Сигнатура:** `run(ctx?: RuleContext): Promise<number>`
|
|
35
|
+
- **Параметри:**
|
|
36
|
+
- `ctx` — _(опційний)_ контекст прогону, який передає CLI-оркестратор. Структура задана в `run-standard-rule.mjs` (`RuleContext`); типове поле — `walkCache`, що дозволяє декільком правилам розділяти один обхід файлової системи в межах однієї сесії. Якщо `ctx` не передано (наприклад, у standalone), `runStandardRule` сам ініціалізує внутрішні структури.
|
|
37
|
+
- **Повертає:** `Promise<number>`
|
|
38
|
+
- `0` — правило завершилось успішно, порушень не знайдено.
|
|
39
|
+
- `1` — знайдено порушення (стандартний exit-code для CI/IDE).
|
|
40
|
+
- **Алгоритм:** делегує виконання у `runStandardRule(import.meta.dirname, ctx)`. Перший аргумент — абсолютний шлях до каталогу правила (`npm/rules/rust/`), завдяки якому `runStandardRule` сам читає `meta.json`, виявляє підкаталоги `js/`, `policy/`, `coverage/`, `lib/` і відповідний `.mdc`-файл (`rust.mdc`) для розв'язання refs.
|
|
41
|
+
- **Side effects:**
|
|
42
|
+
- Власних side effects не має; усі ефекти (I/O, лог, walk-cache) виникають усередині `runStandardRule`.
|
|
43
|
+
- Не модифікує `process.exit`, не пише в `process.stderr/stdout` напряму — це робить уже helper або стандартний CLI summary.
|
|
44
|
+
|
|
45
|
+
### Standalone entry-block
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
if (isRunAsCli(import.meta.url)) {
|
|
49
|
+
process.exit(await runRuleCli(import.meta.dirname))
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- **Тип:** top-level side-effect блок (виконується при імпорті модуля).
|
|
54
|
+
- **Умова входу:** `isRunAsCli(import.meta.url)` повертає `true`, тобто файл виконано як головний модуль (`bun fix.mjs` / `node fix.mjs`), а не імпортовано з іншого модуля.
|
|
55
|
+
- **Що робить:** викликає `runRuleCli(import.meta.dirname)` — повний CLI-pipeline пакета `@nitra/cursor` для одного правила: завантаження конфіга, застосування whitelist, друк summary, повернення числового exit-code. Результат передається у `process.exit(...)`, щоб процес завершився з відповідним кодом для CI/IDE.
|
|
56
|
+
- **Чому два eslint-disable:**
|
|
57
|
+
- `n/no-process-exit` (плагін `eslint-plugin-n`) — забороняє виклик `process.exit` у бібліотечному коді.
|
|
58
|
+
- `unicorn/no-process-exit` (плагін `eslint-plugin-unicorn`) — теж забороняє `process.exit`.
|
|
59
|
+
Тут вони відключені свідомо: standalone entry-point має повертати exit-code, інакше неможливо коректно інтегруватися з CI/IDE runners (вони чекають саме на код виходу процесу).
|
|
60
|
+
- **Side effects:** завершує процес викликом `process.exit(code)`.
|
|
61
|
+
|
|
62
|
+
## Залежності
|
|
63
|
+
|
|
64
|
+
### Внутрішні (relative imports)
|
|
65
|
+
|
|
66
|
+
| Шлях | Що використовується | Роль |
|
|
67
|
+
| ----------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
68
|
+
| `../../scripts/lib/run-rule-cli.mjs` | `isRunAsCli`, `runRuleCli` | Helpers для standalone-режиму: детекція "запущено як CLI" та повний CLI-pipeline одного правила. |
|
|
69
|
+
| `../../scripts/lib/run-standard-rule.mjs` | `runStandardRule` | Стандартний рантайм правила: оркестрація фаз `applies → JS-concerns → policy → mdc-refs`. JSDoc-тип `RuleContext` теж імпортується звідси. |
|
|
70
|
+
|
|
71
|
+
### Зовнішні
|
|
72
|
+
|
|
73
|
+
Прямих залежностей від npm-пакетів немає. Усі external-залежності (наприклад, `fast-glob`, ESLint API тощо) інкапсульовані всередині `runStandardRule` / `runRuleCli` і сюди не "протікають".
|
|
74
|
+
|
|
75
|
+
### Runtime/Node API
|
|
76
|
+
|
|
77
|
+
- `import.meta.dirname` — стандартний Node.js / Bun API; використовується для передачі абсолютного шляху до каталогу правила в helpers.
|
|
78
|
+
- `import.meta.url` — використовується `isRunAsCli` для порівняння з `process.argv[1]`.
|
|
79
|
+
- `process.exit(code)` — завершення процесу в standalone-режимі.
|
|
80
|
+
- `await` на top level — потребує Node ≥ 14.8 (ESM top-level await) або Bun (підтримує з коробки).
|
|
81
|
+
|
|
82
|
+
### Конвенції розташування
|
|
83
|
+
|
|
84
|
+
Файл лежить за конвенцією `npm/rules/<id>/fix.mjs`, де `<id> = rust`. Поряд із ним мають існувати:
|
|
85
|
+
|
|
86
|
+
- `meta.json` — метадані правила (читається `runStandardRule`/`runRuleCli`).
|
|
87
|
+
- `rust.mdc` — людинозрозумілий опис правила (для mdc-refs).
|
|
88
|
+
- Підкаталоги `js/`, `policy/`, `coverage/`, `lib/` — фази правила.
|
|
89
|
+
- `docs/` — каталог із згенерованою документацією (включно з цим файлом).
|
|
90
|
+
|
|
91
|
+
## Потік виконання / Використання
|
|
92
|
+
|
|
93
|
+
### Сценарій A: виклик з CLI-оркестратора (library mode)
|
|
94
|
+
|
|
95
|
+
1. Оркестратор `@nitra/cursor` сканує `npm/rules/*/fix.mjs`.
|
|
96
|
+
2. Для правила `rust` виконує `import('npm/rules/rust/fix.mjs')`.
|
|
97
|
+
3. Викликає `run(ctx)`, передаючи спільний `RuleContext` (включно з `walkCache`).
|
|
98
|
+
4. `run` повертає `runStandardRule(import.meta.dirname, ctx)`, який послідовно виконує фази:
|
|
99
|
+
- **applies** — фільтрує файли, до яких правило застосовне.
|
|
100
|
+
- **JS-concerns** — запускає JS-аспекти правила (`js/`-фолдер).
|
|
101
|
+
- **policy** — застосовує policy-checks (`policy/`-фолдер).
|
|
102
|
+
- **mdc-refs** — валідує посилання в `rust.mdc`.
|
|
103
|
+
5. Результуючий `Promise<number>` (`0` або `1`) повертається оркестратору.
|
|
104
|
+
6. Оркестратор агрегує exit-коди всіх правил і повертає єдиний summary.
|
|
105
|
+
|
|
106
|
+
### Сценарій B: standalone-запуск
|
|
107
|
+
|
|
108
|
+
1. Користувач/CI виконує `bun npm/rules/rust/fix.mjs` (або `node npm/rules/rust/fix.mjs`).
|
|
109
|
+
2. Модуль завантажується; `isRunAsCli(import.meta.url)` повертає `true`.
|
|
110
|
+
3. Виконується `await runRuleCli(import.meta.dirname)`:
|
|
111
|
+
- читає конфіг проєкту,
|
|
112
|
+
- застосовує whitelist шляхів,
|
|
113
|
+
- усередині сам викликає `runStandardRule` (або еквівалент),
|
|
114
|
+
- друкує summary в stdout.
|
|
115
|
+
4. Отриманий числовий exit-code передається в `process.exit(code)`.
|
|
116
|
+
5. Процес завершується кодом `0` (ok) або `1` (порушення) — CI/IDE підхоплюють статус.
|
|
117
|
+
|
|
118
|
+
### Як додавати/змінювати поведінку
|
|
119
|
+
|
|
120
|
+
- **НЕ** додавати бізнес-логіку правила прямо в цей файл — він має лишатись тонким wrapper'ом.
|
|
121
|
+
- Зміни порядку фаз або їх складу — у `runStandardRule` (спільно для всіх стандартних правил).
|
|
122
|
+
- Зміни CLI-флагів / summary / whitelist — у `runRuleCli`.
|
|
123
|
+
- Зміни специфічні для правила `rust` — у відповідних підкаталогах (`js/`, `policy/`, `coverage/`, `lib/`) і файлі `rust.mdc`.
|
|
124
|
+
|
|
125
|
+
### Контракт із оркестратором
|
|
126
|
+
|
|
127
|
+
- Експорт `run` повинен бути **функцією** (не stream/generator) і повертати `Promise<number>`.
|
|
128
|
+
- Не повинен викидати unhandled rejections — усі помилки обгортаються всередині `runStandardRule`.
|
|
129
|
+
- Exit-коди суворо `0` або `1` (інші значення оркестратор може інтерпретувати як збій).
|