@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,189 @@
|
|
|
1
|
+
# ensure-nitra-cursor-dev-dependencies.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `ensure-nitra-cursor-dev-dependencies.mjs` забезпечує, що пакет `@nitra/cursor` буде оголошений у `devDependencies` workspace-root `package.json` проєкту, в якому виконується CLI `n-cursor`. Якщо запис відсутній і в `devDependencies`, і в `dependencies`, модуль дописує його з діапазоном `^<version>`, узятим з поля `version` з `package.json` фактично завантаженого пакету `@nitra/cursor`.
|
|
6
|
+
|
|
7
|
+
Призначення: коли користувач викликає `npx @nitra/cursor` (зокрема команду `check`), node-кеш npx містить пакет, але після наступного `bun install` / `npm install` цей кеш може не відтворити пакет у проєкті. Дописавши пакет у `devDependencies` workspace-root, модуль гарантує, що `n-cursor` і його допоміжні скрипти з `node_modules/@nitra/cursor/scripts/` стануть відтвореною частиною проєкту й не залежатимуть від кешу npx.
|
|
8
|
+
|
|
9
|
+
Workspace-root визначається мінімалістично: береться `package.json` поруч зі стартовою директорією (зазвичай `process.cwd()`) і вважається workspace-root, якщо в ньому є поле `workspaces`. Підйом по дереву директорій не виконується.
|
|
10
|
+
|
|
11
|
+
Модуль написаний як ESM (`.mjs`), використовує лише стандартну бібліотеку Node.js, не має сторонніх залежностей і виконує синхронні файлові операції лише для перевірки існування (`existsSync`). Решта IO — асинхронна через `node:fs/promises`.
|
|
12
|
+
|
|
13
|
+
## Експорти / API
|
|
14
|
+
|
|
15
|
+
Модуль експортує дві асинхронні функції:
|
|
16
|
+
|
|
17
|
+
- `readBundledPackageVersion()` — повертає версію встановленого пакету `@nitra/cursor` або `null`.
|
|
18
|
+
- `ensureNitraCursorInRootDevDependencies(root, options?)` — головна точка входу: перевіряє стан проєкту й, за потреби, мутує його `package.json`. Повертає булеве значення про факт запису на диск.
|
|
19
|
+
|
|
20
|
+
Внутрішні (не експортовані) функції модуля:
|
|
21
|
+
|
|
22
|
+
- `readJsonObject(path)` — м’який парсер JSON-обʼєкта з диска.
|
|
23
|
+
- `readAdjacentWorkspaceRootPackageJson(startDir)` — читання `package.json` поряд зі стартовою директорією за умови, що це workspace-root.
|
|
24
|
+
|
|
25
|
+
Константа модульного рівня:
|
|
26
|
+
|
|
27
|
+
- `PACKAGE_NAME` (`'@nitra/cursor'`) — імʼя пакету, який забезпечується у `devDependencies`.
|
|
28
|
+
|
|
29
|
+
Внутрішній стан модуля (обчислюється один раз під час імпорту):
|
|
30
|
+
|
|
31
|
+
- `scriptDir` — абсолютна директорія, в якій лежить сам файл (отримана з `import.meta.url`).
|
|
32
|
+
- `bundledPkgPath` — обчислений шлях до `package.json` пакету `@nitra/cursor`: на один рівень вище `scriptDir`, бо файл лежить у каталозі `scripts/` всередині пакету.
|
|
33
|
+
|
|
34
|
+
## Функції
|
|
35
|
+
|
|
36
|
+
### `readBundledPackageVersion()`
|
|
37
|
+
|
|
38
|
+
Сигнатура: `readBundledPackageVersion(): Promise<string | null>` (експортується).
|
|
39
|
+
|
|
40
|
+
Параметри: відсутні.
|
|
41
|
+
|
|
42
|
+
Поведінка:
|
|
43
|
+
|
|
44
|
+
1. Перевіряє існування файлу `bundledPkgPath` через `existsSync`. Якщо файлу немає — повертає `null` без подальшої роботи.
|
|
45
|
+
2. Читає вміст файлу як UTF-8 через `readFile`.
|
|
46
|
+
3. Парсить вміст як JSON.
|
|
47
|
+
4. Якщо поле `version` у розпарсеному обʼєкті є рядком — повертає його; інакше — повертає `null`.
|
|
48
|
+
5. Будь-яка помилка читання чи парсингу глушиться `try/catch` і дає `null`.
|
|
49
|
+
|
|
50
|
+
Повертає: `Promise<string | null>` — текстова версія (наприклад, `'1.11.14'`) або `null` за відсутності файлу / некоректного JSON / нерядкового `version`.
|
|
51
|
+
|
|
52
|
+
Side effects: лише read-IO (один read від диска). Нічого не пише й не логує.
|
|
53
|
+
|
|
54
|
+
### `readJsonObject(path)`
|
|
55
|
+
|
|
56
|
+
Сигнатура: `readJsonObject(path: string): Promise<Record<string, unknown> | null>` (внутрішня).
|
|
57
|
+
|
|
58
|
+
Параметри:
|
|
59
|
+
|
|
60
|
+
- `path` — абсолютний шлях до JSON-файлу.
|
|
61
|
+
|
|
62
|
+
Поведінка:
|
|
63
|
+
|
|
64
|
+
1. Намагається прочитати файл як UTF-8. Помилка читання повертає `null`.
|
|
65
|
+
2. Намагається розпарсити вміст як JSON. Помилка парсингу повертає `null`.
|
|
66
|
+
3. Перевіряє, що розпарсене значення — це не `null`, типу `object`, і не масив. Якщо умова не виконана — повертає `null`. Інакше повертає сам обʼєкт.
|
|
67
|
+
|
|
68
|
+
Повертає: `Promise<Record<string, unknown> | null>` — JSON-обʼєкт або `null`.
|
|
69
|
+
|
|
70
|
+
Side effects: read-IO.
|
|
71
|
+
|
|
72
|
+
### `readAdjacentWorkspaceRootPackageJson(startDir)`
|
|
73
|
+
|
|
74
|
+
Сигнатура: `readAdjacentWorkspaceRootPackageJson(startDir: string): Promise<{ path: string, pkg: Record<string, unknown> } | null>` (внутрішня).
|
|
75
|
+
|
|
76
|
+
Параметри:
|
|
77
|
+
|
|
78
|
+
- `startDir` — директорія, від якої починається пошук (зазвичай `process.cwd()` процесу CLI).
|
|
79
|
+
|
|
80
|
+
Поведінка:
|
|
81
|
+
|
|
82
|
+
1. Будує `pkgPath = join(startDir, 'package.json')`.
|
|
83
|
+
2. Через `existsSync` перевіряє наявність файлу. Якщо файлу немає — повертає `null`.
|
|
84
|
+
3. Викликає `readJsonObject(pkgPath)`. Якщо результат — не обʼєкт, повертає `null`.
|
|
85
|
+
4. Через `Object.hasOwn(pkg, 'workspaces')` перевіряє наявність поля `workspaces` (саме власне поле, а не з прототипа). Якщо поле є — повертає `{ path, pkg }`; інакше — `null`.
|
|
86
|
+
|
|
87
|
+
Повертає: `Promise<{ path, pkg } | null>` — пара «шлях/обʼєкт» для workspace-root або `null` для не-workspace-root.
|
|
88
|
+
|
|
89
|
+
Side effects: read-IO. Жодних мутацій диска / логів.
|
|
90
|
+
|
|
91
|
+
### `ensureNitraCursorInRootDevDependencies(root, options?)`
|
|
92
|
+
|
|
93
|
+
Сигнатура: `ensureNitraCursorInRootDevDependencies(root: string, options?: { bundledVersion?: string | null, silent?: boolean }): Promise<boolean>` (експортується).
|
|
94
|
+
|
|
95
|
+
Параметри:
|
|
96
|
+
|
|
97
|
+
- `root` — стартова директорія проєкту, зазвичай `process.cwd()` процесу CLI `n-cursor`.
|
|
98
|
+
- `options` — необовʼязковий обʼєкт:
|
|
99
|
+
- `bundledVersion` — попередньо задана версія для тестів; якщо передано — використовується замість виклику `readBundledPackageVersion()`. `null` тут інтерпретується як «викликати fallback» через оператор `??`.
|
|
100
|
+
- `silent` — якщо `true`, не друкувати повідомлення про оновлення у `stdout`.
|
|
101
|
+
|
|
102
|
+
Алгоритм:
|
|
103
|
+
|
|
104
|
+
1. Викликає `readAdjacentWorkspaceRootPackageJson(root)`. Якщо результат `null` (немає workspace-root) — повертає `false`.
|
|
105
|
+
2. Деструктурує `{ path: pkgPath, pkg }`.
|
|
106
|
+
3. Якщо в `pkg.devDependencies` (за умови, що це обʼєкт) присутній ключ `PACKAGE_NAME` — повертає `false` (вже є).
|
|
107
|
+
4. Якщо в `pkg.dependencies` (за умови, що це обʼєкт) присутній ключ `PACKAGE_NAME` — повертає `false` (вже є в runtime deps; додавати дубль до dev не потрібно).
|
|
108
|
+
5. Визначає версію: `options.bundledVersion ?? await readBundledPackageVersion()`. Якщо результат фолсі (`null`, порожній рядок) — повертає `false`.
|
|
109
|
+
6. Гарантує, що `pkg.devDependencies` — це валідний обʼєкт. Якщо там відсутнє поле, `null`, не-обʼєкт або масив — перезаписує його на `{}`.
|
|
110
|
+
7. Записує `pkg.devDependencies[PACKAGE_NAME] = ` `^<ver>`.
|
|
111
|
+
8. Серіалізує `pkg` з відступом 2 пробіли через `JSON.stringify(pkg, null, 2)`, додає завершальний перевід рядка `\n`, пише через `writeFile` у `pkgPath` у UTF-8.
|
|
112
|
+
9. Якщо `options.silent` не встановлено — друкує `📝 Додано <PACKAGE_NAME>@^<ver> у devDependencies у package.json\n` у `stdout` через `console.log`.
|
|
113
|
+
10. Повертає `true`.
|
|
114
|
+
|
|
115
|
+
Повертає: `Promise<boolean>` — `true`, якщо `package.json` дійсно змінено на диску; `false` у всіх no-op-ситуаціях (немає workspace-root, пакет вже задекларовано, версія недоступна).
|
|
116
|
+
|
|
117
|
+
Side effects:
|
|
118
|
+
|
|
119
|
+
- Читання `package.json` workspace-root.
|
|
120
|
+
- Можливий запис `package.json` workspace-root (мутація вмісту).
|
|
121
|
+
- Можливий запис у `stdout` через `console.log` (можна заглушити через `options.silent`).
|
|
122
|
+
|
|
123
|
+
Особливості:
|
|
124
|
+
|
|
125
|
+
- Перевірка «вже є в deps» поблажлива: якщо `devDependencies` / `dependencies` присутнє, але не є обʼєктом (некоректний `package.json`), модуль трактує це як «нема» і йде далі.
|
|
126
|
+
- Перезапис `pkg.devDependencies` на `{}` у випадку, коли поле не є обʼєктом, спрямований на корекцію некоректного стану `package.json`. Це означає, що нечитабельне поле буде втрачено.
|
|
127
|
+
- Серіалізація використовує `JSON.stringify` із відступом 2 і завершальним `\n`. Будь-яке стилістичне форматування з оригіналу (наприклад, табуляції чи tab-width=4) буде нормалізоване до 2 пробілів.
|
|
128
|
+
|
|
129
|
+
## Залежності
|
|
130
|
+
|
|
131
|
+
Зовнішніх npm-залежностей немає. Використовуються лише вбудовані модулі Node.js:
|
|
132
|
+
|
|
133
|
+
- `node:fs`
|
|
134
|
+
- `existsSync` — синхронна перевірка існування файлу.
|
|
135
|
+
- `node:fs/promises`
|
|
136
|
+
- `readFile` — асинхронне читання файлу як UTF-8.
|
|
137
|
+
- `writeFile` — асинхронний запис файлу як UTF-8.
|
|
138
|
+
- `node:path`
|
|
139
|
+
- `dirname` — отримати директорію з шляху.
|
|
140
|
+
- `join` — побудувати шлях.
|
|
141
|
+
- `node:url`
|
|
142
|
+
- `fileURLToPath` — конвертувати `file://`-URL у файловий шлях.
|
|
143
|
+
|
|
144
|
+
Зовнішні споживачі модуля:
|
|
145
|
+
|
|
146
|
+
- CLI `@nitra/cursor` (`bin`), що викликає `ensureNitraCursorInRootDevDependencies(process.cwd())` під час кожного запуску (зокрема перед `check`), щоб довести стан `package.json` проєкту до бажаного.
|
|
147
|
+
- Тестовий код може передавати `bundledVersion` у `options`, щоб не залежати від реальної версії з диска.
|
|
148
|
+
|
|
149
|
+
## Потік виконання / Використання
|
|
150
|
+
|
|
151
|
+
Типовий потік під час `npx @nitra/cursor check` у workspace-root:
|
|
152
|
+
|
|
153
|
+
1. CLI імпортує модуль і викликає `await ensureNitraCursorInRootDevDependencies(process.cwd())`.
|
|
154
|
+
2. Модуль читає `<cwd>/package.json`. Якщо там немає поля `workspaces` — це не workspace-root, повертається `false`, файл не змінюється.
|
|
155
|
+
3. Якщо `package.json` має `workspaces` і вже містить `@nitra/cursor` у `devDependencies` або `dependencies` — повертається `false`, файл не змінюється.
|
|
156
|
+
4. Інакше модуль читає `version` з `package.json` встановленого пакету `@nitra/cursor` (на рівень вище за `scripts/`), формує діапазон `^<version>` і записує його в `pkg.devDependencies['@nitra/cursor']`.
|
|
157
|
+
5. Серіалізований JSON записується назад у `<cwd>/package.json`.
|
|
158
|
+
6. У `stdout` логується повідомлення про додавання (якщо не задано `silent: true`).
|
|
159
|
+
7. Повертається `true`.
|
|
160
|
+
|
|
161
|
+
Приклад використання:
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
import { ensureNitraCursorInRootDevDependencies } from '@nitra/cursor/scripts/ensure-nitra-cursor-dev-dependencies.mjs'
|
|
165
|
+
|
|
166
|
+
const changed = await ensureNitraCursorInRootDevDependencies(process.cwd())
|
|
167
|
+
if (changed) {
|
|
168
|
+
// package.json було оновлено — можна підказати користувачу зробити bun install
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Приклад використання з тестового сетапу:
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
import { ensureNitraCursorInRootDevDependencies } from '../scripts/ensure-nitra-cursor-dev-dependencies.mjs'
|
|
176
|
+
|
|
177
|
+
await ensureNitraCursorInRootDevDependencies(tmpDir, {
|
|
178
|
+
bundledVersion: '9.9.9',
|
|
179
|
+
silent: true
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Передумови, на які покладається модуль:
|
|
184
|
+
|
|
185
|
+
- Файл `ensure-nitra-cursor-dev-dependencies.mjs` лежить у `<package>/scripts/`, а `package.json` пакету `@nitra/cursor` — у `<package>/package.json`. Якщо структуру переміщено, шлях `bundledPkgPath` стане некоректним.
|
|
186
|
+
- Робоче дерево, передане як `root`, має містити `package.json` із полем `workspaces`. Якщо CLI стартовано всередині workspace-пакету (а не в корені) — модуль нічого не зробить (`false`).
|
|
187
|
+
- Поле `version` у `package.json` пакету `@nitra/cursor` має бути рядком; інакше додавання не відбудеться.
|
|
188
|
+
|
|
189
|
+
Інваріант: повторні виклики модуля у тому самому проєкті стають no-op (другий виклик бачить `@nitra/cursor` у `devDependencies` і повертає `false`).
|
|
@@ -15,7 +15,10 @@ import { spawnSync } from 'node:child_process'
|
|
|
15
15
|
function gitLines(args, cwd) {
|
|
16
16
|
const r = spawnSync('git', args, { cwd, encoding: 'utf8' })
|
|
17
17
|
if (r.status !== 0 || r.error) return []
|
|
18
|
-
return r.stdout
|
|
18
|
+
return r.stdout
|
|
19
|
+
.split('\n')
|
|
20
|
+
.map(s => s.trim())
|
|
21
|
+
.filter(Boolean)
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
/**
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# changed-files.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `changed-files.mjs` — допоміжна бібліотека для збору переліку змінених файлів у робочому дереві git-репозиторію. Використовується lint-оркестратором у quick-режимі та `coverage --changed` для визначення scope-у файлів, на яких потрібно прогнати перевірки.
|
|
6
|
+
|
|
7
|
+
Логіка модуля:
|
|
8
|
+
|
|
9
|
+
- збирає tracked-modified та staged файли через `git diff` (з `--diff-filter=ACMR`, тобто Added/Copied/Modified/Renamed — без Deleted);
|
|
10
|
+
- додає untracked файли через `git ls-files --others --exclude-standard` (з повагою до `.gitignore`);
|
|
11
|
+
- дедуплікує об'єднаний список через `Set`;
|
|
12
|
+
- повертає relative-posix шляхи відносно `cwd`;
|
|
13
|
+
- поза git-репо або при помилці git мовчки повертає порожній список (для `gitLines`);
|
|
14
|
+
- для режиму "since base" — fail-closed: якщо базовий комміт недосяжний, кидає явну помилку, щоб gate не пройшов мовчки.
|
|
15
|
+
|
|
16
|
+
Видалені файли свідомо не включаються — лінтити неіснуючий файл немає сенсу.
|
|
17
|
+
|
|
18
|
+
## Експорти / API
|
|
19
|
+
|
|
20
|
+
Модуль експортує дві функції:
|
|
21
|
+
|
|
22
|
+
| Експорт | Тип | Призначення |
|
|
23
|
+
| -------------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| `collectChangedFiles` | `(cwd?: string) => string[]` | Список змінених + untracked файлів робочого дерева відносно `HEAD`. |
|
|
25
|
+
| `collectChangedFilesSince` | `(base: string \| null, cwd?: string) => string[]` | Список змінених + untracked файлів **відносно довільного базового комміту**. Без `base` — fallback на `collectChangedFiles`. |
|
|
26
|
+
|
|
27
|
+
Внутрішня (неекспортована) функція:
|
|
28
|
+
|
|
29
|
+
| Внутрішнє | Тип | Призначення |
|
|
30
|
+
| ---------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
31
|
+
| `gitLines` | `(args: string[], cwd: string) => string[]` | Виконує `git <args>` у `cwd` і повертає непорожні trim-нуті рядки stdout або `[]` при помилці. |
|
|
32
|
+
|
|
33
|
+
## Функції
|
|
34
|
+
|
|
35
|
+
### `gitLines(args, cwd)` (internal)
|
|
36
|
+
|
|
37
|
+
Виклик git-команди з парсингом stdout у масив рядків.
|
|
38
|
+
|
|
39
|
+
- **Сигнатура:** `function gitLines(args: string[], cwd: string): string[]`
|
|
40
|
+
- **Параметри:**
|
|
41
|
+
- `args` — масив аргументів для `git` (наприклад `['diff', 'HEAD', '--name-only']`).
|
|
42
|
+
- `cwd` — робоча директорія для процесу `git`.
|
|
43
|
+
- **Повертає:** масив непорожніх рядків stdout (після `trim`). Якщо `r.status !== 0` або є `r.error` — повертає `[]`.
|
|
44
|
+
- **Side effects:**
|
|
45
|
+
- Синхронно спавнить процес `git` через `spawnSync` з `node:child_process`;
|
|
46
|
+
- не пише в stdout/stderr батьківського процесу (всі потоки збираються через `encoding: 'utf8'`);
|
|
47
|
+
- не кидає винятків — fail-silent.
|
|
48
|
+
|
|
49
|
+
### `collectChangedFiles(cwd?)`
|
|
50
|
+
|
|
51
|
+
Збирає список змінених + untracked файлів робочого дерева відносно `HEAD`.
|
|
52
|
+
|
|
53
|
+
- **Сигнатура:** `function collectChangedFiles(cwd?: string): string[]`
|
|
54
|
+
- **Параметри:**
|
|
55
|
+
- `cwd` (опційно, дефолт `process.cwd()`) — корінь git-репо.
|
|
56
|
+
- **Алгоритм:**
|
|
57
|
+
1. `git diff HEAD --name-only --diff-filter=ACMR` — повертає всі tracked файли, які відрізняються від `HEAD` (staged + unstaged), фільтр `ACMR` відсікає Deleted.
|
|
58
|
+
2. `git ls-files --others --exclude-standard` — повертає untracked файли, які не ігноруються `.gitignore`.
|
|
59
|
+
3. Об'єднання обох списків через `new Set([...modified, ...untracked])` для дедуплікації.
|
|
60
|
+
- **Повертає:** `string[]` — унікальні relative-posix шляхи без видалених файлів.
|
|
61
|
+
- **Поведінка поза git-репо / при помилці:** `gitLines` мовчки повертає `[]` для обох викликів, тож результат — порожній масив.
|
|
62
|
+
|
|
63
|
+
### `collectChangedFilesSince(base, cwd?)`
|
|
64
|
+
|
|
65
|
+
Збирає список змінених + untracked файлів відносно довільного базового комміту. Призначено для сценаріїв, де базовий комміт зафіксовано у стані flow-турнікета (executor комітить кожен крок, тож потрібно ловити зміни «від base», а не «від HEAD»).
|
|
66
|
+
|
|
67
|
+
- **Сигнатура:** `function collectChangedFilesSince(base: string | null, cwd?: string): string[]`
|
|
68
|
+
- **Параметри:**
|
|
69
|
+
- `base` — SHA/ref базового комміту (типово `metadata.base_commit` зі стану flow). Якщо `null`/`undefined`/порожній — fallback на `collectChangedFiles(cwd)`.
|
|
70
|
+
- `cwd` (опційно, дефолт `process.cwd()`) — корінь git-репо.
|
|
71
|
+
- **Алгоритм:**
|
|
72
|
+
1. Якщо `base` falsy — повертає результат `collectChangedFiles(cwd)`.
|
|
73
|
+
2. Перевіряє досяжність base через `git rev-parse --verify --quiet <base>^{commit}`.
|
|
74
|
+
3. Якщо verify не успішний (`status !== 0` або `error`) — **кидає `Error`** з повідомленням: `collectChangedFilesSince: base-комміт «<base>» недосяжний у <cwd> (rebase/force-update?) — coverage --changed не може визначити scope`.
|
|
75
|
+
4. `git diff <base> --name-only --diff-filter=ACMR` — **без** `..`/`...`, **без** `HEAD`. Така форма порівнює base-комміт із поточним **робочим деревом**, тобто ловить одночасно: закомічене від base, staged та незакомічені модифікації.
|
|
76
|
+
5. `git ls-files --others --exclude-standard` — untracked файли (як у `collectChangedFiles`).
|
|
77
|
+
6. Дедуплікація через `Set`.
|
|
78
|
+
- **Повертає:** `string[]` — унікальні relative-posix шляхи без видалених файлів.
|
|
79
|
+
- **Контракт fail-closed:** на відміну від `collectChangedFiles`, ця функція **навмисно** не маскує помилку недосяжного base. Інакше `git diff` повернув би exit 128, `gitLines` дав би `[]`, і gate мовчки пройшов би без перевірки — це порушує безпеку coverage-перевірки.
|
|
80
|
+
- **Side effects:**
|
|
81
|
+
- спавнить два-три git-процеси (один verify + два data-збори, або один fallback);
|
|
82
|
+
- може кинути `Error` (єдина точка throw у модулі).
|
|
83
|
+
|
|
84
|
+
## Залежності
|
|
85
|
+
|
|
86
|
+
### Зовнішні (built-in Node.js)
|
|
87
|
+
|
|
88
|
+
- `node:child_process` — імпорт `spawnSync` для синхронного виклику git-команд із захопленням stdout.
|
|
89
|
+
|
|
90
|
+
### Системні (runtime)
|
|
91
|
+
|
|
92
|
+
- `git` — має бути доступний у `PATH` процесу. За відсутності `gitLines` поверне `[]` (через `r.error`/ненульовий статус).
|
|
93
|
+
|
|
94
|
+
### Внутрішні модулі
|
|
95
|
+
|
|
96
|
+
Файл не імпортує жодного локального модуля проєкту (zero-dependency бібліотечний листок).
|
|
97
|
+
|
|
98
|
+
## Потік виконання / Використання
|
|
99
|
+
|
|
100
|
+
### Quick-lint (lint-оркестратор)
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
import { collectChangedFiles } from './changed-files.mjs'
|
|
104
|
+
|
|
105
|
+
const files = collectChangedFiles()
|
|
106
|
+
// → лінт тільки цих файлів, замість обходу всього монорепо
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Quick-режим прагне мінімізувати IO: лінтить лише те, що користувач щойно змінив у дереві (modified + staged + untracked), ігноруючи решту репо.
|
|
110
|
+
|
|
111
|
+
### Coverage / flow-турнікет
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
import { collectChangedFilesSince } from './changed-files.mjs'
|
|
115
|
+
|
|
116
|
+
const base = metadata.base_commit // SHA, зафіксований при старті flow-кроку
|
|
117
|
+
const scope = collectChangedFilesSince(base, repoRoot)
|
|
118
|
+
// → файли, які змінилися з моменту base, незалежно від того,
|
|
119
|
+
// чи їх вже закомічено в проміжних кроках executor-а
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Чому саме `git diff <base>` без `..`/`...`:
|
|
123
|
+
|
|
124
|
+
- `git diff A..B` — порівнює дерева A та B, **ігнорує** робоче дерево;
|
|
125
|
+
- `git diff A...B` — порівнює B з merge-base(A,B), теж ігнорує робоче дерево;
|
|
126
|
+
- `git diff <base>` (один аргумент) — порівнює `<base>` із **робочим деревом** (включно з unstaged змінами).
|
|
127
|
+
|
|
128
|
+
Це критично для flow-турнікета, де executor може як комітити проміжні зміни, так і залишати їх unstaged — gate повинен побачити всі зміни від `base` однаково.
|
|
129
|
+
|
|
130
|
+
### Поведінка при недосяжному base
|
|
131
|
+
|
|
132
|
+
Якщо у репозиторії стався rebase, force-push або shallow clone обрізав історію — `<base>` може стати недосяжним. У такому разі:
|
|
133
|
+
|
|
134
|
+
- `collectChangedFiles` (без base) — поведінка незмінна, працює від `HEAD`;
|
|
135
|
+
- `collectChangedFilesSince(base, cwd)` — **кидає Error** до того, як викликати `git diff`, щоб coverage-gate явно впав, а не пройшов на порожньому scope.
|
|
136
|
+
|
|
137
|
+
### Формат шляхів
|
|
138
|
+
|
|
139
|
+
Усі повернені шляхи — POSIX-style relative до `cwd` (як їх віддає git за замовчуванням). На Windows git також віддає forward slashes, тож додаткової нормалізації немає.
|
|
140
|
+
|
|
141
|
+
### Точки помилок (summary)
|
|
142
|
+
|
|
143
|
+
| Сценарій | Поведінка |
|
|
144
|
+
| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
145
|
+
| Поза git-репо | `collectChangedFiles` → `[]`; `collectChangedFilesSince(null)` → `[]`; `collectChangedFilesSince('SHA')` → `Error` (verify впаде). |
|
|
146
|
+
| `git` відсутній у PATH | Усі виклики `gitLines` → `[]`; `collectChangedFilesSince` із `base` → `Error` (verify впаде). |
|
|
147
|
+
| `base` досяжний | `collectChangedFilesSince` повертає список (можливо порожній). |
|
|
148
|
+
| `base` недосяжний (rebase/force-push/shallow) | `collectChangedFilesSince` кидає `Error`. |
|
|
149
|
+
| `base` falsy (`null`/`undefined`/`''`/`0`) | `collectChangedFilesSince` → результат `collectChangedFiles(cwd)`. |
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# check-mdc-template-refs.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `check-mdc-template-refs.mjs` — це невелика утиліта, призначена для перевірки цілісності посилань між файлом правила `<id>.mdc` та шаблонами, що лежать у підкаталогах `template/` цього самого правила. Він обходить структуру каталогів правила (`fix/<concern>/template/`, `policy/<concern>/template/`), збирає всі знайдені файли й порівнює їхні відносні шляхи з вмістом основного файлу правила `<id>.mdc`. Результатом є перелік шаблонних файлів, на які у `.mdc` немає жодного markdown-посилання, тобто «осиротілі» (orphaned) шаблони.
|
|
6
|
+
|
|
7
|
+
Типовий контекст застосування: модуль використовується в утилітах перевірки/лінтингу правил репозиторію (`npm/rules/<id>/`), де `.mdc`-файл описує правило людською мовою й має посилатися на свої шаблони. Якщо шаблон існує, але не згаданий у `.mdc`, цей модуль повідомить про нього, щоб супровідник правила або додав посилання, або видалив зайвий шаблон.
|
|
8
|
+
|
|
9
|
+
Модуль повністю асинхронний (використовує `node:fs/promises`), стандартної залежності від сторонніх бібліотек не має, працює у середовищі Node.js (а також сумісне з Bun) як ES-модуль (`.mjs`).
|
|
10
|
+
|
|
11
|
+
## Експорти / API
|
|
12
|
+
|
|
13
|
+
Модуль експортує єдину функцію:
|
|
14
|
+
|
|
15
|
+
- `findMissingMdcRefs(ruleDir, ruleId)` — публічний експорт (`export async function`).
|
|
16
|
+
|
|
17
|
+
Дві внутрішні (не експортовані) допоміжні функції:
|
|
18
|
+
|
|
19
|
+
- `walkTemplateDirs(ruleDir)` — обходить підкаталоги `fix/*/template/` і `policy/*/template/`.
|
|
20
|
+
- `collectFiles(dir)` — рекурсивно збирає всі файли в заданому каталозі.
|
|
21
|
+
|
|
22
|
+
Імпорти з ядра Node:
|
|
23
|
+
|
|
24
|
+
- `existsSync` з `node:fs` — синхронна перевірка існування шляху.
|
|
25
|
+
- `readdir`, `readFile`, `stat` з `node:fs/promises` — асинхронні операції з файловою системою.
|
|
26
|
+
- `join`, `relative` з `node:path` — нормалізація та побудова шляхів.
|
|
27
|
+
|
|
28
|
+
## Функції
|
|
29
|
+
|
|
30
|
+
### findMissingMdcRefs
|
|
31
|
+
|
|
32
|
+
Сигнатура:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
export async function findMissingMdcRefs(ruleDir, ruleId): Promise<string[]>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Параметри:
|
|
39
|
+
|
|
40
|
+
- `ruleDir` — `string`, абсолютний шлях до каталогу правила (за конвенцією `npm/rules/<id>/`).
|
|
41
|
+
- `ruleId` — `string`, basename правила (наприклад, `"security"`). Використовується для побудови імені `.mdc`-файлу: `${ruleId}.mdc`.
|
|
42
|
+
|
|
43
|
+
Що робить:
|
|
44
|
+
|
|
45
|
+
1. Будує очікуваний шлях до файлу правила: `mdcPath = join(ruleDir, `${ruleId}.mdc`)`.
|
|
46
|
+
2. Якщо файл `.mdc` не існує — повертає порожній масив `[]` (нічого перевіряти).
|
|
47
|
+
3. Читає текст `.mdc` у пам'ять як UTF-8 рядок.
|
|
48
|
+
4. Викликає `walkTemplateDirs(ruleDir)`, щоб отримати масив усіх файлів із `template/`-каталогів правила (як шляхи, відносні до `ruleDir`).
|
|
49
|
+
5. Фільтрує отриманий масив, залишаючи лише ті файли, посилань на які НЕ виявлено в тексті `.mdc`. Перевірка наявності посилання — підрядкова: файл вважається «посиланим», якщо в тексті `.mdc` зустрічається будь-яке з двох вкраплень:
|
|
50
|
+
- `./<rel>` — типова форма markdown-посилання `[label](./path)`.
|
|
51
|
+
- `(<rel>)` — також зловить голу форму `(path)` у круглих дужках.
|
|
52
|
+
|
|
53
|
+
Повертає:
|
|
54
|
+
|
|
55
|
+
- `Promise<string[]>` — масив відносних шляхів (відносно `ruleDir`) файлів шаблонів, на які в `.mdc` немає посилання.
|
|
56
|
+
|
|
57
|
+
Side effects:
|
|
58
|
+
|
|
59
|
+
- Виключно read-only доступ до файлової системи: `existsSync`, `readFile`, `readdir`, `stat`. Файли або каталоги не створюються, не змінюються, не видаляються.
|
|
60
|
+
- Не виводить нічого в `stdout`/`stderr`. Не має побічних ефектів на process state.
|
|
61
|
+
|
|
62
|
+
Помилки:
|
|
63
|
+
|
|
64
|
+
- Може кинути помилку, якщо `readFile`/`readdir`/`stat` зазнають невдачі з причин, відмінних від «файл не існує» (наприклад, права доступу). Перед читанням існування `mdcPath` перевіряється явно, тому відсутність `.mdc` помилки не дає.
|
|
65
|
+
|
|
66
|
+
### walkTemplateDirs (внутрішня)
|
|
67
|
+
|
|
68
|
+
Сигнатура:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
async function walkTemplateDirs(ruleDir): Promise<string[]>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Параметри:
|
|
75
|
+
|
|
76
|
+
- `ruleDir` — `string`, абсолютний шлях до каталогу правила.
|
|
77
|
+
|
|
78
|
+
Що робить:
|
|
79
|
+
|
|
80
|
+
1. Ітерується по двох жорстко закодованих «kind»-категоріях: `'fix'` та `'policy'`.
|
|
81
|
+
2. Для кожного `kind` будує шлях `kindDir = join(ruleDir, kind)`. Якщо такого каталогу немає — пропускає.
|
|
82
|
+
3. Для кожного `concern` (підкаталога або файлу всередині `kindDir`, який повертає `readdir`) будує шлях `tpl = join(kindDir, concern, 'template')`.
|
|
83
|
+
4. Якщо `tpl` не існує — пропускає. Якщо існує, але не є каталогом (`stat(tpl).isDirectory() === false`) — також пропускає.
|
|
84
|
+
5. Викликає `collectFiles(tpl)` для рекурсивного збору всіх файлів і додає їх до результуючого масиву.
|
|
85
|
+
6. На виході перетворює абсолютні шляхи у шляхи, відносні до `ruleDir` (`relative(ruleDir, p)`).
|
|
86
|
+
|
|
87
|
+
Повертає:
|
|
88
|
+
|
|
89
|
+
- `Promise<string[]>` — відносні (від `ruleDir`) шляхи всіх файлів у `fix/*/template/` і `policy/*/template/`.
|
|
90
|
+
|
|
91
|
+
Side effects:
|
|
92
|
+
|
|
93
|
+
- Лише read-only ФС-операції.
|
|
94
|
+
|
|
95
|
+
Примітка: значення `readdir(kindDir)` (без `withFileTypes`) повертає масив імен і файлів, і каталогів. Перевірка `existsSync(tpl)` та `stat(tpl).isDirectory()` коректно відсіює випадки, коли `concern` — це файл, а не каталог.
|
|
96
|
+
|
|
97
|
+
### collectFiles (внутрішня)
|
|
98
|
+
|
|
99
|
+
Сигнатура:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
async function collectFiles(dir): Promise<string[]>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Параметри:
|
|
106
|
+
|
|
107
|
+
- `dir` — `string`, абсолютний шлях каталогу, який треба обійти.
|
|
108
|
+
|
|
109
|
+
Що робить:
|
|
110
|
+
|
|
111
|
+
1. Викликає `readdir(dir, { withFileTypes: true })` і отримує `Dirent[]`.
|
|
112
|
+
2. Для кожного запису будує повний шлях `full = join(dir, entry.name)`.
|
|
113
|
+
3. Якщо запис — каталог (`entry.isDirectory()`), рекурсивно занурюється в нього й приєднує знайдене до результату.
|
|
114
|
+
4. Якщо запис — файл (або symlink/інше, що НЕ каталог), додає `full` як є.
|
|
115
|
+
|
|
116
|
+
Повертає:
|
|
117
|
+
|
|
118
|
+
- `Promise<string[]>` — абсолютні шляхи всіх файлів усередині `dir` (з усіх рівнів вкладеності).
|
|
119
|
+
|
|
120
|
+
Side effects:
|
|
121
|
+
|
|
122
|
+
- Read-only обхід ФС.
|
|
123
|
+
|
|
124
|
+
Зауваження щодо поведінки: символьні посилання й сокети не каталогізуються як директорії (`isDirectory()` поверне `false`), отже потрапляють у список як «файли». Гранична глибина рекурсії не обмежена — для зацикленої структури symlinks теоретично можливе нескінченне занурення; на практиці шаблонні каталоги правил такого не мають.
|
|
125
|
+
|
|
126
|
+
## Залежності
|
|
127
|
+
|
|
128
|
+
Стандартна бібліотека Node.js (ESM):
|
|
129
|
+
|
|
130
|
+
- `node:fs` — `existsSync` (синхронна перевірка існування).
|
|
131
|
+
- `node:fs/promises` — `readdir`, `readFile`, `stat` (асинхронні ФС-операції).
|
|
132
|
+
- `node:path` — `join`, `relative` (нормалізація шляхів).
|
|
133
|
+
|
|
134
|
+
Зовнішніх npm-залежностей немає. Модуль не використовує жодних глобальних змінних, конфігів, env-vars чи CLI-аргументів. Не звертається до мережі.
|
|
135
|
+
|
|
136
|
+
Передумови оточення:
|
|
137
|
+
|
|
138
|
+
- Node.js версія, що підтримує ES Modules (`.mjs`) і `node:`-protocol-imports (Node 14+; на практиці Node 18+/Bun).
|
|
139
|
+
- Доступ на читання до каталогу `ruleDir` та його підкаталогів.
|
|
140
|
+
|
|
141
|
+
## Потік виконання / Використання
|
|
142
|
+
|
|
143
|
+
### Очікувана структура каталогу правила
|
|
144
|
+
|
|
145
|
+
Модуль розрахований на конвенцію розкладки правил:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
<ruleDir>/
|
|
149
|
+
<ruleId>.mdc ← текст правила з markdown-посиланнями
|
|
150
|
+
fix/
|
|
151
|
+
<concern-A>/
|
|
152
|
+
template/
|
|
153
|
+
file1.ext
|
|
154
|
+
nested/file2.ext
|
|
155
|
+
<concern-B>/
|
|
156
|
+
template/
|
|
157
|
+
...
|
|
158
|
+
policy/
|
|
159
|
+
<concern-C>/
|
|
160
|
+
template/
|
|
161
|
+
...
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Перелічуються лише файли всередині `fix/*/template/` і `policy/*/template/`. Будь-який інший контент (`check-*.mjs`, `README`, інші підкаталоги) ігнорується.
|
|
165
|
+
|
|
166
|
+
### Алгоритм у двох словах
|
|
167
|
+
|
|
168
|
+
1. Зібрати всі файли з `fix/*/template/` і `policy/*/template/`, перетворити у відносні шляхи від `ruleDir`.
|
|
169
|
+
2. Прочитати `<ruleId>.mdc`.
|
|
170
|
+
3. Для кожного відносного шляху перевірити, чи зустрічається в тексті `.mdc` хоча б одна з форм `./<rel>` або `(<rel>)`. Якщо НЕ зустрічається — додати у вихідний список.
|
|
171
|
+
|
|
172
|
+
### Типове використання
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
import { findMissingMdcRefs } from './check-mdc-template-refs.mjs'
|
|
176
|
+
|
|
177
|
+
const missing = await findMissingMdcRefs('/abs/path/to/npm/rules/security', 'security')
|
|
178
|
+
if (missing.length > 0) {
|
|
179
|
+
console.error('Orphaned template files (no markdown link in .mdc):')
|
|
180
|
+
for (const rel of missing) console.error(` - ${rel}`)
|
|
181
|
+
process.exit(1)
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Цей виклик часто є частиною check-скрипта правила (`check-<id>.mjs`) або агрегованого валідатора, який обходить усі правила репозиторію.
|
|
186
|
+
|
|
187
|
+
### Граничні випадки
|
|
188
|
+
|
|
189
|
+
- `.mdc` відсутній — повертається `[]`. Це навмисно: правило без `.mdc` не валідуємо «на покинуті шаблони».
|
|
190
|
+
- Каталоги `fix/`, `policy/`, `template/` відсутні — обходяться silent, результат для них порожній.
|
|
191
|
+
- Шаблонів немає взагалі — повертається `[]`.
|
|
192
|
+
- `.mdc` посилається на шаблон, якого фізично немає, — цей сценарій модуль НЕ перевіряє (зворотний напрям перевірки тут не реалізовано).
|
|
193
|
+
- Перевірка посилань — підрядкова, без парсингу markdown. Якщо файл містить `./foo/bar.md` як випадковий збіг у документації, шаблон `foo/bar.md` буде вважатися посиланим, навіть якщо це не власне markdown-link. На практиці це не проблема, бо шляхи всередині `template/` достатньо унікальні.
|
|
194
|
+
- Сепаратор шляхів — платформозалежний (`path.join`/`path.relative` використовують `\` на Windows). Усередині `.mdc` посилання, як правило, пишуть зі слешем `/` — на Windows це може давати хибно-позитивні результати (тобто файл буде вважатися «без посилання»). У цільовому оточенні (macOS/Linux/Docker) проблема не виникає.
|
|
195
|
+
|
|
196
|
+
### Складність
|
|
197
|
+
|
|
198
|
+
- Час: O(N + M), де N — сумарна кількість файлів у `template/`-каталогах, а M — довжина тексту `.mdc` (через `String.prototype.includes`, що виконується для кожного з N файлів двічі).
|
|
199
|
+
- Пам'ять: лінійна щодо розміру `.mdc` плюс масиву шляхів.
|
|
200
|
+
|
|
201
|
+
## Rebuild Test
|
|
202
|
+
|
|
203
|
+
Файл `check-mdc-template-refs.mjs` можна повністю відновити, дотримуючись опису вище, якщо забезпечити такі властивості:
|
|
204
|
+
|
|
205
|
+
1. ES-модуль (`.mjs`), без `default export`. Єдиний `export` — `async function findMissingMdcRefs(ruleDir, ruleId)`.
|
|
206
|
+
2. Імпорти лише з `node:fs` (`existsSync`), `node:fs/promises` (`readdir`, `readFile`, `stat`) і `node:path` (`join`, `relative`).
|
|
207
|
+
3. Дві внутрішні async-функції: `walkTemplateDirs(ruleDir)` та `collectFiles(dir)`.
|
|
208
|
+
4. `walkTemplateDirs` ітерується по двох категоріях у фіксованому порядку: спочатку `'fix'`, потім `'policy'`. Пропускає неіснуючі `kindDir`. Для кожного `concern` перевіряє існування й те, що `template` — каталог; у разі успіху делегує до `collectFiles`. На виході конвертує абсолютні шляхи у відносні від `ruleDir`.
|
|
209
|
+
5. `collectFiles` використовує `readdir(dir, { withFileTypes: true })`, рекурсивно занурюється тільки в каталоги, інші записи додає в результат як абсолютні шляхи.
|
|
210
|
+
6. `findMissingMdcRefs` спочатку перевіряє існування `<ruleId>.mdc`, читає його у UTF-8, отримує всі файли через `walkTemplateDirs`, потім фільтрує, залишаючи лише ті `rel`, для яких у тексті `.mdc` ОДНОЧАСНО немає `./<rel>` і немає `(<rel>)` (логічне AND через подвійне заперечення з `||` під `!`).
|
|
211
|
+
7. Поведінка строго read-only: жодних запитів у мережу, жодного запису на диск, жодного консольного виводу.
|
|
212
|
+
8. Повертає `Promise<string[]>`, відносні шляхи (з POSIX/платформозалежним сепаратором у міру `path.relative`).
|
|
213
|
+
|
|
214
|
+
Тестові сценарії для smoke-перевірки:
|
|
215
|
+
|
|
216
|
+
- Каталог без `.mdc` → `[]`.
|
|
217
|
+
- `.mdc` є, але `fix/`, `policy/` відсутні → `[]`.
|
|
218
|
+
- `fix/x/template/a.txt` існує, у `.mdc` згадано `./fix/x/template/a.txt` → `[]`.
|
|
219
|
+
- Той самий шаблон, але `.mdc` посилається як `(fix/x/template/a.txt)` (без `./`) → `[]`.
|
|
220
|
+
- Той самий шаблон, але в `.mdc` посилання немає → `['fix/x/template/a.txt']`.
|
|
221
|
+
- Вкладений `fix/x/template/sub/b.txt`, посилання немає → містить `'fix/x/template/sub/b.txt'`.
|
|
222
|
+
- Запис `concern`, що насправді є файлом, а не каталогом → пропускається без помилки.
|