@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,274 @@
|
|
|
1
|
+
# package_structure.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `package_structure.mjs` — це checker правила `npm-module.mdc` у пакеті `@nitra/cursor`. Він валідує структуру npm-модуля в монорепо: наявність каталогу `npm/`, файлу `npm/package.json`, конфігурації hk (`hk.pkl` або `.config/hk.pkl`), workflow `npm-publish.yml`, та узгодженість TypeScript-emit (поле `types`, генерація `index.d.ts`, виклик `tsc` у pre-commit hook).
|
|
6
|
+
|
|
7
|
+
Модуль підтримує два альтернативні layout-и npm-модуля:
|
|
8
|
+
|
|
9
|
+
1. **`npm/src` + `.js`-файли** — канонічний layout зі згенерованим `npm/types/index.d.ts` через `tsc` з прапорцями `--declaration --allowJs --emitDeclarationOnly --outDir types --skipLibCheck`.
|
|
10
|
+
2. **`npm/tsconfig.emit-types.json`** — коли `.js`-файлів під `npm/src` немає; типи виганяються через `tsc -p tsconfig.emit-types.json`.
|
|
11
|
+
|
|
12
|
+
Окремо модуль контролює, щоб опублікований пакет (`npm pack`) не містив тестів і фікстур: сканує каталог `npm/` за полем `"files"` з `package.json` (з урахуванням негативних glob-патернів) і відсіює test-style каталоги, імена файлів та AST-імпорти test-фреймворків.
|
|
13
|
+
|
|
14
|
+
Деякі перевірки (структура `npm/package.json`, валідація `compilerOptions` у `tsconfig.emit-types.json`, валідація полів `npm-publish.yml`) делеговані Rego-полісі у `npm/policy/npm_module/`. У цьому файлі лишається лише cross-file / FS / AST-частина: чи реально існує файл на диску, чи містить опублікований tarball тести, чи має `hk.pkl` правильні підрядки команди `tsc`.
|
|
15
|
+
|
|
16
|
+
Узгодженість `version`/`CHANGELOG.md` у файлі **не** перевіряється — це робить `changelog/js/consistency.mjs` за моделлю `n-changelog.mdc`.
|
|
17
|
+
|
|
18
|
+
## Експорти / API
|
|
19
|
+
|
|
20
|
+
| Експорт | Тип | Призначення |
|
|
21
|
+
| ----------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
22
|
+
| `globToRegex(glob)` | `function` | Перетворює glob-патерн на `RegExp` із якорями `^` / `$`; підтримує `**`, `*`, `?`, `{a,b,c}`. |
|
|
23
|
+
| `findTestFrameworkImport(content, virtualPath)` | `async function` | Парсить JS/TS через `oxc-parser` і повертає назву модуля test-фреймворку, якщо знайдено import / require / dynamic import. |
|
|
24
|
+
| `classifyPublishedFileAsTest(relPath, cwd?)` | `async function` | Класифікує файл як test/fixture за каталогом, basename або AST-імпортом. |
|
|
25
|
+
| `check(cwd?)` | `async function` | Головна точка входу checker-а правила `npm-module.mdc`; повертає exit-code `0` / `1`. |
|
|
26
|
+
|
|
27
|
+
Решта функцій (`npmSrcTreeHasJsFile`, `readHkConfig`, `missingHkSrcLayoutFragments`, `missingHkEmitTypesConfigFragments`, `npmTypesFileFromPackageField`, `checkNpmPackageJson`, `checkEmitTypesConfig`, `checkPublishWorkflow`, `collectPublishedFiles`, `checkNoTestsInPublishedFiles`, `checkNpmModuleBasicStructure`) — приватні для модуля.
|
|
28
|
+
|
|
29
|
+
## Константи
|
|
30
|
+
|
|
31
|
+
| Константа | Значення / Опис |
|
|
32
|
+
| ------------------------ | -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `EMIT_TYPES_CONFIG` | `'npm/tsconfig.emit-types.json'` — шлях до TS-config для emit без `src`. |
|
|
34
|
+
| `TEST_DIR_NAMES` | `Set` з `'tests'`, `'__tests__'`, `'fixtures'`, `'__fixtures__'`, `'spec'`, `'test'`. |
|
|
35
|
+
| `TEST_FILE_PATTERNS` | `[/^.+\.(test | spec)\.[cm]?[jt]sx?$/iu]` — патерн test-файлів за basename. Rego-файли (`\*\_test.rego`) свідомо не входять (conftest-конвенція). |
|
|
36
|
+
| `JS_LIKE_EXT_RE` | `/\.[cm]?[jt]sx?$/iu` — розширення, у яких сканується AST на імпорти test-фреймворків. |
|
|
37
|
+
| `TEST_FRAMEWORK_MODULES` | `Set` з `'bun:test'`, `'node:test'`, `'vitest'`, `'@jest/globals'`, `'jest'`, `'mocha'`, `'ava'`, `'tap'`, `'tape'`. |
|
|
38
|
+
| `REGEX_SPECIAL_IN_GLOB` | `Set` спецсимволів regex, які екрануються у glob-сегменті (без `*`/`?`). |
|
|
39
|
+
| `GLOBSTAR_LEADING_RE` | `/^__GLOBSTAR__\//u` — маркер `**/` на початку. |
|
|
40
|
+
| `GLOBSTAR_TRAILING_RE` | `/\/__GLOBSTAR__$/u` — маркер `/**` у кінці. |
|
|
41
|
+
|
|
42
|
+
## Функції
|
|
43
|
+
|
|
44
|
+
### `npmSrcTreeHasJsFile(cwd, ignorePaths = [])`
|
|
45
|
+
|
|
46
|
+
- **Сигнатура:** `async (cwd: string, ignorePaths?: string[]) => Promise<boolean>`
|
|
47
|
+
- **Параметри:**
|
|
48
|
+
- `cwd` — корінь репозиторію.
|
|
49
|
+
- `ignorePaths` — абсолютні шляхи каталогів, повністю виключених з обходу (з `loadCursorIgnorePaths`).
|
|
50
|
+
- **Повертає:** `true`, якщо хоча б один `.js` під `npm/src` (рекурсивно); інакше `false`. Якщо `npm/src` не існує — одразу `false`.
|
|
51
|
+
- **Side effects:** жодних (тільки FS-читання).
|
|
52
|
+
|
|
53
|
+
### `readHkConfig(cwd)`
|
|
54
|
+
|
|
55
|
+
- **Сигнатура:** `async (cwd: string) => Promise<{ path: string, text: string } | null>`
|
|
56
|
+
- **Параметри:** `cwd` — корінь репозиторію.
|
|
57
|
+
- **Повертає:** обʼєкт із relative-шляхом (`hk.pkl` або `.config/hk.pkl`) і повним текстом файлу; `null`, якщо жоден кандидат не існує.
|
|
58
|
+
- **Side effects:** читання файлу через `readFile`.
|
|
59
|
+
|
|
60
|
+
### `missingHkSrcLayoutFragments(hkText)`
|
|
61
|
+
|
|
62
|
+
- **Сигнатура:** `(hkText: string) => string[]`
|
|
63
|
+
- **Параметри:** `hkText` — текст hk-конфігурації.
|
|
64
|
+
- **Повертає:** список фрагментів, яких немає в тексті. Очікувані фрагменти: `["pre-commit"]`, `bunx -p typescript tsc`, `src/**/*.js`, `--declaration`, `--allowJs`, `--emitDeclarationOnly`, `--outDir types`, `--skipLibCheck`.
|
|
65
|
+
- **Side effects:** немає.
|
|
66
|
+
|
|
67
|
+
### `missingHkEmitTypesConfigFragments(hkText)`
|
|
68
|
+
|
|
69
|
+
- **Сигнатура:** `(hkText: string) => string[]`
|
|
70
|
+
- **Параметри:** `hkText` — текст hk-конфігурації.
|
|
71
|
+
- **Повертає:** список відсутніх фрагментів для layout-у через `tsconfig.emit-types.json`: `["pre-commit"]`, `bunx -p typescript tsc`, `tsconfig.emit-types.json`.
|
|
72
|
+
- **Side effects:** немає.
|
|
73
|
+
|
|
74
|
+
### `npmTypesFileFromPackageField(typesField)`
|
|
75
|
+
|
|
76
|
+
- **Сигнатура:** `(typesField: unknown) => string | null`
|
|
77
|
+
- **Параметри:** `typesField` — значення поля `types` з `npm/package.json`.
|
|
78
|
+
- **Повертає:** posix-шлях `npm/<rel>` (наприклад, `npm/types/bin/x.d.ts`) або `null`, якщо значення не починається з `./types/` чи не є рядком.
|
|
79
|
+
- **Side effects:** немає.
|
|
80
|
+
|
|
81
|
+
### `checkNpmPackageJson(useSrcJsLayout, passFn, failFn, cwd)`
|
|
82
|
+
|
|
83
|
+
- **Сигнатура:** `async (useSrcJsLayout: boolean, passFn, failFn, cwd: string) => Promise<void>`
|
|
84
|
+
- **Поведінка:**
|
|
85
|
+
- Якщо `npm/package.json` відсутній — нічого не робить (вище у потоці це вже відловлено).
|
|
86
|
+
- Для layout `src+js`: очікує існування `npm/types/index.d.ts`.
|
|
87
|
+
- Для layout `emit-types`: бере `npm/<types-field>` і перевіряє існування.
|
|
88
|
+
- **Викликає:** `passFn(msg)` при успіху, `failFn(msg)` при помилці.
|
|
89
|
+
- **Side effects:** читає `npm/package.json`.
|
|
90
|
+
|
|
91
|
+
### `checkEmitTypesConfig(passFn, failFn, cwd)`
|
|
92
|
+
|
|
93
|
+
- **Сигнатура:** `(passFn, failFn, cwd: string) => void`
|
|
94
|
+
- **Поведінка:** перевіряє лише існування `npm/tsconfig.emit-types.json`. Структуру `compilerOptions` валідує Rego-полісі `npm_module/emit_types_config`.
|
|
95
|
+
|
|
96
|
+
### `checkPublishWorkflow(passFn, failFn, cwd)`
|
|
97
|
+
|
|
98
|
+
- **Сигнатура:** `(passFn, failFn, cwd: string) => void`
|
|
99
|
+
- **Поведінка:** перевіряє лише існування `.github/workflows/npm-publish.yml`. Структуру полів workflow валідує Rego-полісі `npm_module/npm_publish_yml`.
|
|
100
|
+
|
|
101
|
+
### `globToRegex(glob)` (експортована)
|
|
102
|
+
|
|
103
|
+
- **Сигнатура:** `(glob: string) => RegExp`
|
|
104
|
+
- **Параметри:** `glob` — posix-шлях у glob-нотації.
|
|
105
|
+
- **Повертає:** `RegExp` з якорями `^` / `$` і прапорцем `u`.
|
|
106
|
+
- **Підтримка синтаксису:**
|
|
107
|
+
- `**` — нуль або більше сегментів (`(?:/.*/|/)` між сегментами, `(?:.*/)?` на початку, `(?:/.*)?` у кінці, `.*` як єдиний токен).
|
|
108
|
+
- `*` — будь-які символи без `/` (`[^/]*`).
|
|
109
|
+
- `?` — один символ без `/` (`[^/]`).
|
|
110
|
+
- `{a,b,c}` — brace-альтернативи (`(?:a|b|c)`).
|
|
111
|
+
- **Не підтримує:** клас `[…]` (для негативних патернів `files` цього достатньо).
|
|
112
|
+
- **Safety:** усі спецсимволи екрануються через `REGEX_SPECIAL_IN_GLOB`; eslint правило `security/detect-non-literal-regexp` явно вимкнено, бо вхід контрольований (поле `files` з `npm/package.json`).
|
|
113
|
+
|
|
114
|
+
### `collectPublishedFiles(filesField, cwd)`
|
|
115
|
+
|
|
116
|
+
- **Сигнатура:** `async (filesField: string[], cwd: string) => Promise<string[]>`
|
|
117
|
+
- **Параметри:**
|
|
118
|
+
- `filesField` — значення поля `files` з `npm/package.json`.
|
|
119
|
+
- `cwd` — корінь репозиторію.
|
|
120
|
+
- **Алгоритм:**
|
|
121
|
+
1. Розділяє patterns на позитивні і негативні (за префіксом `!`).
|
|
122
|
+
2. Для кожного позитивного pattern: якщо це файл — додає до `collected`; якщо директорія — рекурсивно через `walkDir` додає всі знайдені файли (posix-шляхи без `npm/` префікса).
|
|
123
|
+
3. Фільтрує: викидає файли, які матчать будь-який негативний `globToRegex`.
|
|
124
|
+
4. Сортує `[].sort()` (лексикографічно) і повертає.
|
|
125
|
+
- **Side effects:** `stat()` для кожного позитивного pattern, `walkDir` для директорій.
|
|
126
|
+
- **Обмеження:** не дублює всю логіку `npm pack` (LICENSE / README / mandatory files); сканує лише простір імен `files`.
|
|
127
|
+
|
|
128
|
+
### `findTestFrameworkImport(content, virtualPath)` (експортована)
|
|
129
|
+
|
|
130
|
+
- **Сигнатура:** `(content: string, virtualPath: string) => string | null`
|
|
131
|
+
- **Параметри:**
|
|
132
|
+
- `content` — повний текст файлу.
|
|
133
|
+
- `virtualPath` — шлях файлу (для вибору `lang` через `langFromPath`).
|
|
134
|
+
- **Повертає:** ім'я модуля test-фреймворку (з `TEST_FRAMEWORK_MODULES`), якщо знайдено; `null` інакше.
|
|
135
|
+
- **Алгоритм:**
|
|
136
|
+
1. Парсить через `parseSync` із `oxc-parser`; при помилці парсингу — повертає `null` (це не AST-checker для синтаксису).
|
|
137
|
+
2. Якщо `result.errors.length` ≠ 0 — повертає `null`.
|
|
138
|
+
3. Спочатку перевіряє `result.module.staticImports`.
|
|
139
|
+
4. Якщо в static-import не знайдено — обходить AST через `walkAstWithAncestors` і шукає `require(...)` (через `requireCallModule`) та `import(...)` dynamic (через `dynamicImportModule`).
|
|
140
|
+
- **Side effects:** немає.
|
|
141
|
+
|
|
142
|
+
### `classifyPublishedFileAsTest(relPath, cwd = process.cwd())` (експортована)
|
|
143
|
+
|
|
144
|
+
- **Сигнатура:** `async (relPath: string, cwd?: string) => Promise<string | null>`
|
|
145
|
+
- **Параметри:**
|
|
146
|
+
- `relPath` — posix-шлях відносно `npm/`.
|
|
147
|
+
- `cwd` — корінь репозиторію (за замовчуванням `process.cwd()`).
|
|
148
|
+
- **Повертає:** рядок-причину порушення або `null`, якщо файл валідний.
|
|
149
|
+
- **Класифікація (за пріоритетом):**
|
|
150
|
+
1. У path є сегмент із `TEST_DIR_NAMES` → `'test-style каталог "<seg>/"'`.
|
|
151
|
+
2. Basename матчить `TEST_FILE_PATTERNS` → `"test-style ім'я файлу"`.
|
|
152
|
+
3. Розширення JS-like і AST містить імпорт test-фреймворку → `'імпорт test-фреймворку "<mod>"'`.
|
|
153
|
+
- **Carve-out:** для `rules/<rule-name>/...` сегмент `<rule-name>` (індекс 1) ігнорується, бо це ім'я правила (наприклад, правило з id `test` саме описує конвенцію тестів і не є fixture-каталогом). Подальші сегменти (`rules/<r>/js/<c>/tests/`) продовжують перевірятись.
|
|
154
|
+
- **Side effects:** для JS-like розширень — `readFile(join(cwd, 'npm', relPath))`.
|
|
155
|
+
|
|
156
|
+
### `checkNoTestsInPublishedFiles(pass, fail, cwd)`
|
|
157
|
+
|
|
158
|
+
- **Сигнатура:** `async (pass, fail, cwd: string) => Promise<void>`
|
|
159
|
+
- **Поведінка:**
|
|
160
|
+
- Якщо `npm/package.json` відсутній або поле `files` не масив — нічого не робить.
|
|
161
|
+
- Інакше збирає файли через `collectPublishedFiles` і прогонить кожен через `classifyPublishedFileAsTest`.
|
|
162
|
+
- На порушення викликає `fail(...)` з підказкою додати негативний glob у `files`.
|
|
163
|
+
- На повну чистоту — `pass(...)` з кількістю перевірених файлів.
|
|
164
|
+
|
|
165
|
+
### `checkNpmModuleBasicStructure(pass, fail, cwd)`
|
|
166
|
+
|
|
167
|
+
- **Сигнатура:** `async (pass, fail, cwd: string) => Promise<void>`
|
|
168
|
+
- **Поведінка:** перевіряє наявність `package.json`, директорії `npm/` і `npm/package.json`. Поле `workspaces ∋ "npm"` у кореневому `package.json` валідує Rego.
|
|
169
|
+
|
|
170
|
+
### `check(cwd = process.cwd())` (експортована, головна)
|
|
171
|
+
|
|
172
|
+
- **Сигнатура:** `async (cwd?: string) => Promise<number>`
|
|
173
|
+
- **Повертає:** `0` — все OK, `1` — є проблеми (через `reporter.getExitCode()`).
|
|
174
|
+
- **Алгоритм:**
|
|
175
|
+
1. Створює `createCheckReporter()`, дістає `pass`, `fail`.
|
|
176
|
+
2. `checkNpmModuleBasicStructure` — `package.json`, `npm/`, `npm/package.json`.
|
|
177
|
+
3. `checkNoTestsInPublishedFiles` — компактність tarball.
|
|
178
|
+
4. `loadCursorIgnorePaths(cwd)` для подальшого скану `npm/src`.
|
|
179
|
+
5. `npmSrcTreeHasJsFile` → визначає `useSrcJsLayout`.
|
|
180
|
+
6. `checkNpmPackageJson(useSrcJsLayout, ...)` — поле `types` і відповідний файл на диску.
|
|
181
|
+
7. Якщо НЕ `useSrcJsLayout` — `checkEmitTypesConfig`.
|
|
182
|
+
8. `readHkConfig` → знайти hk; перевірити pre-commit-фрагменти через `missingHkSrcLayoutFragments` або `missingHkEmitTypesConfigFragments` залежно від layout.
|
|
183
|
+
9. `.github/workflows/` існує.
|
|
184
|
+
10. `checkPublishWorkflow` — `npm-publish.yml`.
|
|
185
|
+
11. `return reporter.getExitCode()`.
|
|
186
|
+
|
|
187
|
+
## Залежності
|
|
188
|
+
|
|
189
|
+
### Node.js core
|
|
190
|
+
|
|
191
|
+
- `node:fs` — `existsSync`.
|
|
192
|
+
- `node:fs/promises` — `readFile`, `stat`.
|
|
193
|
+
- `node:path` — `join`, `sep`.
|
|
194
|
+
|
|
195
|
+
### Зовнішні npm
|
|
196
|
+
|
|
197
|
+
- `oxc-parser` — `parseSync` для AST-парсингу JS/TS у `findTestFrameworkImport`.
|
|
198
|
+
|
|
199
|
+
### Внутрішні (relative)
|
|
200
|
+
|
|
201
|
+
- `../../../scripts/utils/ast-scan-utils.mjs` — `dynamicImportModule`, `langFromPath`, `requireCallModule`, `walkAstWithAncestors`.
|
|
202
|
+
- `../../../scripts/lib/check-reporter.mjs` — `createCheckReporter` (повертає `{ pass, fail, getExitCode }`).
|
|
203
|
+
- `../../../scripts/lib/load-cursor-config.mjs` — `loadCursorIgnorePaths` (читає `.cursorignore`-подібні шляхи).
|
|
204
|
+
- `../../../scripts/utils/walkDir.mjs` — `walkDir(root, callback, ignorePaths?)` для рекурсивного обходу.
|
|
205
|
+
|
|
206
|
+
## Потік виконання / Використання
|
|
207
|
+
|
|
208
|
+
### Базовий потік `check(cwd)`
|
|
209
|
+
|
|
210
|
+
```text
|
|
211
|
+
check(cwd)
|
|
212
|
+
├── createCheckReporter() → { pass, fail, getExitCode }
|
|
213
|
+
├── checkNpmModuleBasicStructure(pass, fail, cwd)
|
|
214
|
+
│ ├── existsSync(cwd/package.json) ? pass : fail
|
|
215
|
+
│ ├── existsSync(cwd/npm) && stat(...).isDirectory() ? pass : fail
|
|
216
|
+
│ └── existsSync(cwd/npm/package.json) ? pass : fail
|
|
217
|
+
├── checkNoTestsInPublishedFiles(pass, fail, cwd)
|
|
218
|
+
│ ├── readFile(npm/package.json) → pkg
|
|
219
|
+
│ ├── if !Array.isArray(pkg.files) → return
|
|
220
|
+
│ ├── files = collectPublishedFiles(pkg.files, cwd)
|
|
221
|
+
│ └── for rel of files: classifyPublishedFileAsTest(rel, cwd)
|
|
222
|
+
├── ignorePaths = loadCursorIgnorePaths(cwd)
|
|
223
|
+
├── useSrcJsLayout = npmSrcTreeHasJsFile(cwd, ignorePaths)
|
|
224
|
+
├── checkNpmPackageJson(useSrcJsLayout, pass, fail, cwd)
|
|
225
|
+
├── if !useSrcJsLayout → checkEmitTypesConfig(pass, fail, cwd)
|
|
226
|
+
├── hk = readHkConfig(cwd)
|
|
227
|
+
│ ├── hk == null → fail (потрібен hk.pkl)
|
|
228
|
+
│ └── inakshe →
|
|
229
|
+
│ missing = useSrcJsLayout
|
|
230
|
+
│ ? missingHkSrcLayoutFragments(hk.text)
|
|
231
|
+
│ : missingHkEmitTypesConfigFragments(hk.text)
|
|
232
|
+
│ missing.length === 0 ? pass : fail
|
|
233
|
+
├── existsSync(.github/workflows/) ? pass : fail
|
|
234
|
+
├── checkPublishWorkflow(pass, fail, cwd)
|
|
235
|
+
└── return reporter.getExitCode()
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Точка інтеграції
|
|
239
|
+
|
|
240
|
+
Цей файл — частина пакету `@nitra/cursor` (`npm/rules/npm-module/js/`). Він викликається CLI `npx @nitra/cursor fix` (або `n-cursor`) у режимі checker правила `npm-module`. Експортована функція `check(cwd?)` — стандартний контракт для checker-ів правил у каталозі `npm/rules/<rule>/js/`.
|
|
241
|
+
|
|
242
|
+
### Розподіл відповідальностей із Rego
|
|
243
|
+
|
|
244
|
+
- **Цей JS:** FS-existence (чи є файл / директорія), AST-сканування (test-imports), glob-обчислення для `files`, cross-file (`package.json` ↔ файли на диску).
|
|
245
|
+
- **Rego (`npm/policy/npm_module/`):**
|
|
246
|
+
- `npm_package_json` — структура `npm/package.json` (whitelist `files`, заборона `devDependencies`, тощо).
|
|
247
|
+
- `emit_types_config` — `compilerOptions` у `npm/tsconfig.emit-types.json`.
|
|
248
|
+
- `npm_publish_yml` — поля workflow (`on.push.paths`, `branches`, `id-token: write`, кроки JS-DevTools/npm-publish), парсяться після YAML-parse.
|
|
249
|
+
- `root_package_json` — `workspaces ∋ "npm"` у кореневому `package.json`.
|
|
250
|
+
|
|
251
|
+
### Приклад інтеграції
|
|
252
|
+
|
|
253
|
+
```js
|
|
254
|
+
import { check } from '@nitra/cursor/rules/npm-module/js/package_structure.mjs'
|
|
255
|
+
|
|
256
|
+
const exitCode = await check(process.cwd())
|
|
257
|
+
process.exit(exitCode)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Що НЕ робить файл
|
|
261
|
+
|
|
262
|
+
- Не перевіряє узгодженість `version` ↔ `CHANGELOG.md` — це `changelog/js/consistency.mjs` (правило `n-changelog.mdc`).
|
|
263
|
+
- Не валідує сам формат AST/синтаксис коду — за помилки парсингу `findTestFrameworkImport` мовчки повертає `null`.
|
|
264
|
+
- Не дублює логіку `npm pack` (LICENSE / README / mandatory files) — сканує лише простір імен `files`.
|
|
265
|
+
- Не перевіряє вміст Rego-полісі — лише дискову наявність / FS / AST.
|
|
266
|
+
|
|
267
|
+
## Rebuild Test
|
|
268
|
+
|
|
269
|
+
Файл можна повністю відтворити з опису вище:
|
|
270
|
+
|
|
271
|
+
- Імпорти: `node:fs` (`existsSync`), `node:fs/promises` (`readFile`, `stat`), `node:path` (`join`, `sep`), `oxc-parser` (`parseSync`), і чотири внутрішні модулі (`ast-scan-utils.mjs`, `check-reporter.mjs`, `load-cursor-config.mjs`, `walkDir.mjs`).
|
|
272
|
+
- Константи: `EMIT_TYPES_CONFIG`, `TEST_DIR_NAMES`, `TEST_FILE_PATTERNS`, `JS_LIKE_EXT_RE`, `TEST_FRAMEWORK_MODULES`, `REGEX_SPECIAL_IN_GLOB`, `GLOBSTAR_LEADING_RE`, `GLOBSTAR_TRAILING_RE`.
|
|
273
|
+
- Експорти: `globToRegex`, `findTestFrameworkImport`, `classifyPublishedFileAsTest`, `check`.
|
|
274
|
+
- Алгоритми описано у секціях "Функції" і "Потік виконання".
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# rule_meta.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `rule_meta.mjs` реалізує перевірку метаданих правил пакета `@nitra/cursor`. Він є частиною концерну правила `npm-module` й обслуговує валідацію файлів `npm/rules/<id>/meta.json` у репозиторії пакета.
|
|
6
|
+
|
|
7
|
+
Логіка перевірки покриває такі інваріанти для кожного підкаталогу `npm/rules/<id>/`:
|
|
8
|
+
|
|
9
|
+
- наявність валідного `meta.json`;
|
|
10
|
+
- коректність поля `auto`, якщо воно присутнє (розпізнаваність специфікації через `parseRuleAutoSpec`: значення `"завжди"`, масив, `{glob}` або `{predicate}`);
|
|
11
|
+
- для специфікації `{predicate}` — наявність відповідного імені у реєстрі `RULE_PREDICATES`;
|
|
12
|
+
- коректність поля `lint`, якщо воно присутнє (`"quick"` чи `"ci"`), з обовʼязковою наявністю файлу `js/lint.mjs` у каталозі правила;
|
|
13
|
+
- відсутність застарілого `auto.md` поряд із `meta.json` (міграція метаданих у `meta.json` вже завершена).
|
|
14
|
+
|
|
15
|
+
Перевірка застосовна виключно у репо самого пакета `@nitra/cursor`, де існує каталог `npm/rules/`. У споживацьких репо такого каталогу немає, тож модуль м’яко рапортує `pass` і завершується кодом виходу `0`.
|
|
16
|
+
|
|
17
|
+
Модуль експортує єдину функцію `check`, яка повертає `Promise<number>` із POSIX-кодом виходу: `0` — порушень немає, `1` — є хоча б одне порушення. Звітування реалізоване через інстанс репортера, отриманий від `createCheckReporter`.
|
|
18
|
+
|
|
19
|
+
## Експорти / API
|
|
20
|
+
|
|
21
|
+
| Експорт | Тип | Призначення |
|
|
22
|
+
| -------------------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
23
|
+
| `check(cwd?: string): Promise<number>` | іменований експорт (функція) | Валідує всі `npm/rules/<id>/meta.json` у переданому корені репозиторію та повертає підсумковий код виходу. |
|
|
24
|
+
|
|
25
|
+
Інших експортів (default, типи, константи) модуль не має.
|
|
26
|
+
|
|
27
|
+
## Функції
|
|
28
|
+
|
|
29
|
+
### `check(cwd = process.cwd())`
|
|
30
|
+
|
|
31
|
+
Валідує всі `meta.json` у `<cwd>/npm/rules/<id>/`.
|
|
32
|
+
|
|
33
|
+
- **Сигнатура:** `export function check(cwd?: string): Promise<number>`
|
|
34
|
+
- **Параметри:**
|
|
35
|
+
- `cwd` — рядок із абсолютним або відносним шляхом до кореня репозиторію. Необовʼязковий. Якщо не вказано, використовується `process.cwd()` (поточний робочий каталог процесу Node).
|
|
36
|
+
- **Повертає:** `Promise<number>`, що резолвиться у код виходу від репортера (`reporter.getExitCode()`). Конвенція:
|
|
37
|
+
- `0` — усі знайдені правила пройшли валідацію, або каталог `npm/rules/` відсутній (у такому випадку додатково логиться `pass`-повідомлення `"npm/rules/ відсутній — немає правил для валідації"`);
|
|
38
|
+
- `1` — зафіксовано принаймні одне порушення (зокрема: залишковий `auto.md`, відсутній/невалідний `meta.json`, нерозпізнаний `auto`, невідомий `predicate`, нерозпізнаний `lint`, або `lint` без `js/lint.mjs`).
|
|
39
|
+
- **Side effects:**
|
|
40
|
+
- Виконує синхронне читання файлової системи: `existsSync` для перевірки наявності `npm/rules/`, `npm/rules/<id>/auto.md`, `npm/rules/<id>/js/lint.mjs`; `readdirSync` зі списком підкаталогів `npm/rules/`.
|
|
41
|
+
- Делегує читання та парсинг `meta.json` у `readRuleMetaRaw` (також синхронне читання файла).
|
|
42
|
+
- Викликає методи репортера (`reporter.pass`, `reporter.fail`), які за домовленістю виводять статусні повідомлення (зазвичай у stdout/stderr — залежно від реалізації `createCheckReporter`).
|
|
43
|
+
- Не змінює файли на диску, не запускає дочірніх процесів, не звертається до мережі.
|
|
44
|
+
- **Алгоритм:**
|
|
45
|
+
1. Створює репортер: `reporter = createCheckReporter()`.
|
|
46
|
+
2. Обчислює `rulesDir = join(cwd, 'npm', 'rules')`.
|
|
47
|
+
3. Якщо `rulesDir` не існує — рапортує `pass` і резолвить `Promise.resolve(reporter.getExitCode())` (зазвичай `0`).
|
|
48
|
+
4. Інакше перебирає вміст `rulesDir` через `readdirSync(rulesDir, { withFileTypes: true })` і для кожного запису:
|
|
49
|
+
- пропускає, якщо це не каталог (`!entry.isDirectory()`) або імʼя починається з крапки (`entry.name.startsWith('.')`);
|
|
50
|
+
- встановлює локальний прапор `ruleOk = true`;
|
|
51
|
+
- якщо знайдено залишковий `auto.md` у каталозі правила — `reporter.fail(...)`, `ruleOk = false`;
|
|
52
|
+
- читає сирий обʼєкт метаданих через `readRuleMetaRaw(ruleDir)`. Якщо повернуто falsy — `reporter.fail("rules/<id>: відсутній або невалідний meta.json")` і `continue` (наступне правило);
|
|
53
|
+
- якщо `raw.auto !== undefined` — парсить через `parseRuleAutoSpec(raw.auto)`:
|
|
54
|
+
- якщо повернуто `null` — `reporter.fail(...)`, `ruleOk = false`;
|
|
55
|
+
- інакше якщо в `spec` є ключ `'predicate'` і його значення немає у `RULE_PREDICATES` — `reporter.fail(...)`, `ruleOk = false`;
|
|
56
|
+
- якщо `raw.lint !== undefined` — валідує через `parseRuleLintPhase(raw.lint)`:
|
|
57
|
+
- якщо повернуто `null` — `reporter.fail(...)`, `ruleOk = false`;
|
|
58
|
+
- інакше якщо немає файла `<ruleDir>/js/lint.mjs` — `reporter.fail(...)`, `ruleOk = false`;
|
|
59
|
+
- наприкінці ітерації, якщо `ruleOk` усе ще `true` — `reporter.pass("rules/<id>: meta.json валідний")`.
|
|
60
|
+
5. Резолвить `Promise.resolve(reporter.getExitCode())` із підсумковим кодом виходу.
|
|
61
|
+
- **Особливості реалізації:**
|
|
62
|
+
- Усі гілки порушень для одного правила фіксуються незалежно одна від одної (наприклад, у тому ж проході може бути зафіксовано і нерозпізнаний `auto`, і нерозпізнаний `lint`). Виняток — повністю відсутній/невалідний `meta.json`: тоді решта перевірок для цього правила пропускається через `continue`.
|
|
63
|
+
- Функція синхронно виконує всю роботу, але повертає `Promise<number>` через `Promise.resolve(...)` — для уніфікованої сигнатури з іншими `check`-модулями.
|
|
64
|
+
- Перевірка `'predicate' in spec` дозволяє валідувати лише гілку `{predicate}`-специфікації; інші форми (`"завжди"`, масив, `{glob}`) уже визнані валідними самим `parseRuleAutoSpec`.
|
|
65
|
+
|
|
66
|
+
## Залежності
|
|
67
|
+
|
|
68
|
+
### Зовнішні (Node.js standard library)
|
|
69
|
+
|
|
70
|
+
- `node:fs` — `existsSync` (перевірка існування файлів і каталогів), `readdirSync` (читання вмісту каталогу `npm/rules/`).
|
|
71
|
+
- `node:path` — `join` (формування шляхів до каталогів і файлів).
|
|
72
|
+
|
|
73
|
+
### Внутрішні (репо-локальні)
|
|
74
|
+
|
|
75
|
+
- `../../../scripts/lib/check-reporter.mjs` — `createCheckReporter()`. Створює репортер з методами `pass(msg)`, `fail(msg)`, `getExitCode()`; останній використовується для отримання підсумкового коду виходу.
|
|
76
|
+
- `../../../scripts/lib/rule-meta.mjs`:
|
|
77
|
+
- `readRuleMetaRaw(ruleDir)` — читає та парсить сирий `meta.json` із каталогу правила; повертає обʼєкт або falsy (за відсутності/невалідності файла).
|
|
78
|
+
- `parseRuleAutoSpec(value)` — парсить значення поля `auto`; повертає валідовану специфікацію або `null` за нерозпізнаним вводом.
|
|
79
|
+
- `parseRuleLintPhase(value)` — парсить значення поля `lint`; повертає валідовану фазу (`"quick"`/`"ci"`) або `null`.
|
|
80
|
+
- `../../../scripts/lib/rule-predicates.mjs` — `RULE_PREDICATES`: обʼєкт-реєстр відомих імен предикатів; ключі використовуються для перевірки наявності предиката через `Object.hasOwn`.
|
|
81
|
+
|
|
82
|
+
### Глобальні
|
|
83
|
+
|
|
84
|
+
- `process.cwd()` — використовується як дефолтний `cwd`.
|
|
85
|
+
|
|
86
|
+
## Потік виконання / Використання
|
|
87
|
+
|
|
88
|
+
Модуль є check-функцією конкретного правила і підпорядкований конвенції `check-{id}` (тут — `rule_meta`). Очікуваний сценарій використання:
|
|
89
|
+
|
|
90
|
+
1. Виклик з вищерівневого скрипта (наприклад, агрегованої перевірки правил пакета):
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
import { check } from './npm/rules/npm-module/js/rule_meta.mjs'
|
|
94
|
+
|
|
95
|
+
const exit = await check(process.cwd())
|
|
96
|
+
process.exit(exit)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
У такому сценарії функцію викликають із кореня репозиторію пакета `@nitra/cursor`.
|
|
100
|
+
|
|
101
|
+
2. Запуск у репо-споживачі (де `npm/rules/` відсутній) — функція тихо рапортує `pass` і повертає `0`, не блокуючи pipeline.
|
|
102
|
+
3. Сценарій порушення: будь-який із описаних інваріантів спричиняє `fail`-звіт і код виходу `1`, що сигналізує CI зупинку.
|
|
103
|
+
|
|
104
|
+
Типові точки інтеграції:
|
|
105
|
+
|
|
106
|
+
- CI lint-фаза пакета `@nitra/cursor` (виклик в одному з кореневих скриптів `bun run lint` / спеціалізованої перевірки правил).
|
|
107
|
+
- Локальний запуск через CLI з кореня репозиторію.
|
|
108
|
+
|
|
109
|
+
Файл не має побічних ефектів під час імпорту: експортована функція не виконується самовільно — її викликає консьюмер.
|
|
110
|
+
|
|
111
|
+
## Rebuild Test
|
|
112
|
+
|
|
113
|
+
Контрольні питання, чиї відповіді достатні, щоб реконструювати модуль без перегляду коду:
|
|
114
|
+
|
|
115
|
+
1. Який каталог сканує функція `check`?
|
|
116
|
+
- `<cwd>/npm/rules/`, де `cwd` — необовʼязковий параметр (дефолт `process.cwd()`).
|
|
117
|
+
2. Як обробляється відсутність цього каталогу?
|
|
118
|
+
- Рапортується `pass("npm/rules/ відсутній — немає правил для валідації")`, повертається `0`.
|
|
119
|
+
3. Які записи в `npm/rules/` пропускаються?
|
|
120
|
+
- Не-каталоги та елементи, чиє імʼя починається з `.`.
|
|
121
|
+
4. За наявності якого файла поряд із `meta.json` фіксується порушення?
|
|
122
|
+
- `auto.md` (повідомлення `"rules/<id>: залишковий auto.md — видали (метадані тепер у meta.json)"`).
|
|
123
|
+
5. Що відбувається, якщо `meta.json` відсутній або невалідний?
|
|
124
|
+
- `reporter.fail("rules/<id>: відсутній або невалідний meta.json")` і `continue` до наступного правила.
|
|
125
|
+
6. Які гілки валідації поля `auto`?
|
|
126
|
+
- Якщо `parseRuleAutoSpec` повертає `null` — fail (нерозпізнане).
|
|
127
|
+
- Якщо специфікація містить ключ `predicate`, але його значення відсутнє в `RULE_PREDICATES` — fail (`невідомий predicate`).
|
|
128
|
+
7. Які гілки валідації поля `lint`?
|
|
129
|
+
- Якщо `parseRuleLintPhase` повертає `null` — fail (`нерозпізнане, очікується "quick"|"ci"`).
|
|
130
|
+
- Якщо валідне, але немає `js/lint.mjs` — fail (`lint:"..." але немає js/lint.mjs`).
|
|
131
|
+
8. Як формується підсумковий код виходу?
|
|
132
|
+
- Через `reporter.getExitCode()`, обгорнутий у `Promise.resolve(...)`.
|
|
133
|
+
9. Чи синхронна реалізація?
|
|
134
|
+
- Так: усі IO операції — синхронні (`existsSync`, `readdirSync`, `readRuleMetaRaw`); проміс використано лише для уніфікації сигнатури.
|
|
135
|
+
10. Які `pass`-повідомлення можливі?
|
|
136
|
+
- `"npm/rules/ відсутній — немає правил для валідації"` (за відсутності каталогу).
|
|
137
|
+
- `"rules/<id>: meta.json валідний"` (за успішної валідації окремого правила).
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# skill_meta.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `skill_meta.mjs` — перевірка валідності метаданих скілів (`meta.json`) пакета `@nitra/cursor`. Належить до концерну правила `npm-module` (тека `npm/rules/npm-module/js/`).
|
|
6
|
+
|
|
7
|
+
Перевірка застосовується **виключно в репозиторії пакета**, де є каталог `npm/skills/`. Якщо каталог відсутній (наприклад, у проєктах-споживачах залежності), перевірка пропускається без помилки (повертає `pass`).
|
|
8
|
+
|
|
9
|
+
Для кожного підкаталогу `npm/skills/<id>/` модуль виконує контракт:
|
|
10
|
+
|
|
11
|
+
- забороняється залишковий файл `auto.md` (метадані мігровані на `meta.json`);
|
|
12
|
+
- файл `meta.json` має бути присутнім і валідним JSON-об'єктом з очікуваною структурою;
|
|
13
|
+
- поле `worktree` обов'язкове і має бути `boolean`;
|
|
14
|
+
- поле `auto` опціональне, але якщо є — має бути або рядком `"завжди"`, або непорожнім масивом рядків.
|
|
15
|
+
|
|
16
|
+
Результат акумулюється через `check-reporter` і повертається кодом виходу `0` (OK) або `1` (порушення).
|
|
17
|
+
|
|
18
|
+
## Експорти / API
|
|
19
|
+
|
|
20
|
+
| Експорт | Тип | Призначення |
|
|
21
|
+
| ------- | ---------- | -------------------------------------------------------------------------- |
|
|
22
|
+
| `check` | `function` | Іменований експорт; функція-перевірка метаданих скілів у вказаному корені. |
|
|
23
|
+
|
|
24
|
+
Модуль є ESM (`.mjs`), без `default` експорту.
|
|
25
|
+
|
|
26
|
+
## Функції
|
|
27
|
+
|
|
28
|
+
### `check(cwd?)`
|
|
29
|
+
|
|
30
|
+
**Сигнатура:**
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
export function check(cwd = process.cwd()): Promise<number>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Параметри:**
|
|
37
|
+
|
|
38
|
+
| Параметр | Тип | Обов'язковий | За замовчуванням | Опис |
|
|
39
|
+
| -------- | -------- | ------------ | ---------------- | ---------------------------------------------------------------- |
|
|
40
|
+
| `cwd` | `string` | ні | `process.cwd()` | Корінь репозиторію; від нього резолвиться шлях до `npm/skills/`. |
|
|
41
|
+
|
|
42
|
+
**Повертає:**
|
|
43
|
+
|
|
44
|
+
`Promise<number>` — код виходу від `reporter.getExitCode()`:
|
|
45
|
+
|
|
46
|
+
- `0` — усі перевірені скіли валідні (або каталог `npm/skills/` відсутній);
|
|
47
|
+
- `1` — щонайменше одне порушення зафіксовано через `reporter.fail(...)`.
|
|
48
|
+
|
|
49
|
+
Хоча всередині немає `async`/`await`, функція обертає результат у `Promise.resolve(...)` для однорідного API з іншими `check`-функціями в цьому проєкті.
|
|
50
|
+
|
|
51
|
+
**Алгоритм:**
|
|
52
|
+
|
|
53
|
+
1. Створюється локальний звітник: `reporter = createCheckReporter()`.
|
|
54
|
+
2. Будується шлях `skillsDir = join(cwd, 'npm', 'skills')`.
|
|
55
|
+
3. Якщо `skillsDir` не існує (`existsSync(...) === false`):
|
|
56
|
+
- викликається `reporter.pass('npm/skills/ відсутній — немає скілів для валідації')`;
|
|
57
|
+
- повертається `Promise.resolve(reporter.getExitCode())` (рання гілка).
|
|
58
|
+
4. Інакше — ітерація `readdirSync(skillsDir, { withFileTypes: true })`:
|
|
59
|
+
- пропускаються не-директорії та назви, що починаються з крапки (`.` префікс);
|
|
60
|
+
- для кожного скіла `id = entry.name` будується `skillDir = join(skillsDir, id)` і прапор `skillOk = true`.
|
|
61
|
+
5. Для кожного скіла послідовно перевіряється:
|
|
62
|
+
- **Залишковий `auto.md`**: якщо `existsSync(join(skillDir, 'auto.md'))`, фіксується
|
|
63
|
+
`fail("skills/<id>: залишковий auto.md — видали (метадані тепер у meta.json)")` і `skillOk = false`.
|
|
64
|
+
- **`meta.json` присутній/валідний**: викликається `raw = readSkillMetaRaw(skillDir)`.
|
|
65
|
+
Якщо `raw` falsy — фіксується `fail("skills/<id>: відсутній або невалідний meta.json ...")` і виконується `continue` (решта перевірок для цього скіла пропускається).
|
|
66
|
+
- **Тип `worktree`**: якщо `typeof raw.worktree !== 'boolean'` — `fail("skills/<id>: meta.json.worktree має бути boolean")` і `skillOk = false`.
|
|
67
|
+
- **Розпізнаваність `auto`**: якщо `raw.auto !== undefined` і `parseSkillAutoSpec(raw.auto) === null` — `fail("skills/<id>: meta.json.auto нерозпізнане — очікується \"завжди\" або непорожній масив правил")` і `skillOk = false`.
|
|
68
|
+
6. Якщо після всіх перевірок цього скіла `skillOk === true`, фіксується `pass("skills/<id>: meta.json валідний")`.
|
|
69
|
+
7. Після проходу всіх скілів повертається `Promise.resolve(reporter.getExitCode())`.
|
|
70
|
+
|
|
71
|
+
**Side effects:**
|
|
72
|
+
|
|
73
|
+
- читання файлової системи (`existsSync`, `readdirSync`) — синхронне;
|
|
74
|
+
- мутація стану внутрішнього звітника (`createCheckReporter()`) через `pass(...)` / `fail(...)`;
|
|
75
|
+
- запис у `stdout`/`stderr` залежить від реалізації `check-reporter` (зазвичай агрегує повідомлення для подальшого виводу викликачем);
|
|
76
|
+
- **не змінює** жодних файлів і **не** створює директорій.
|
|
77
|
+
|
|
78
|
+
**Поведінкові інваріанти:**
|
|
79
|
+
|
|
80
|
+
- Відсутність `npm/skills/` — не помилка, а явний `pass` (модуль працює і в репо-споживачі).
|
|
81
|
+
- Невалідний `meta.json` зупиняє подальші перевірки конкретного скіла (`continue`), але **не зупиняє** обхід інших скілів.
|
|
82
|
+
- Прихований запис (назва починається з `.`) ігнорується, тож `.DS_Store` чи `.tmp` не плутають перевірку.
|
|
83
|
+
- Перевірка детермінована: результат залежить тільки від вмісту `npm/skills/` у `cwd`.
|
|
84
|
+
|
|
85
|
+
## Залежності
|
|
86
|
+
|
|
87
|
+
### Стандартна бібліотека Node.js
|
|
88
|
+
|
|
89
|
+
| Модуль | Імпортовані символи | Використання |
|
|
90
|
+
| ----------- | --------------------------- | -------------------------------------------------------------------- |
|
|
91
|
+
| `node:fs` | `existsSync`, `readdirSync` | перевірка наявності `npm/skills/`, `auto.md`; читання списку скілів. |
|
|
92
|
+
| `node:path` | `join` | побудова крос-платформних шляхів до каталогів і файлів. |
|
|
93
|
+
|
|
94
|
+
### Внутрішні модулі проєкту
|
|
95
|
+
|
|
96
|
+
| Модуль | Імпортовані символи | Використання |
|
|
97
|
+
| ----------------------------------------- | ---------------------------------------- | --------------------------------------------------------------------------- |
|
|
98
|
+
| `../../../scripts/lib/check-reporter.mjs` | `createCheckReporter` | створення звітника з API `pass(msg)` / `fail(msg)` / `getExitCode()`. |
|
|
99
|
+
| `../../../scripts/lib/skill-meta.mjs` | `readSkillMetaRaw`, `parseSkillAutoSpec` | читання сирого `meta.json` зі скіла; парсинг/валідація специфікації `auto`. |
|
|
100
|
+
|
|
101
|
+
### Контракти зовнішніх модулів (як використовуються в коді)
|
|
102
|
+
|
|
103
|
+
- `createCheckReporter()` → об'єкт із методами `pass(message: string)`, `fail(message: string)`, `getExitCode(): number` (`0` якщо не було `fail`, `1` якщо хоч раз був).
|
|
104
|
+
- `readSkillMetaRaw(skillDir: string)` → розпарсений об'єкт `meta.json` або falsy (наприклад, `null`) якщо файл відсутній/невалідний.
|
|
105
|
+
- `parseSkillAutoSpec(value)` → нормалізована специфікація `auto` або `null`, якщо значення нерозпізнане. Прийнятні форми: рядок `"завжди"` або непорожній масив рядків.
|
|
106
|
+
|
|
107
|
+
## Потік виконання / Використання
|
|
108
|
+
|
|
109
|
+
### Контекст
|
|
110
|
+
|
|
111
|
+
Модуль викликається з раннера правил концерну `npm-module` (точка входу `npm/rules/npm-module/js/index.mjs` або аналог). У типовому пайплайні `bun run lint` / `n-cursor check` всі `check`-функції правил зібрані в реєстр і виконуються послідовно; результат об'єднується в загальний код виходу процесу.
|
|
112
|
+
|
|
113
|
+
### Типовий виклик
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
import { check } from './skill_meta.mjs'
|
|
117
|
+
|
|
118
|
+
const exitCode = await check() // використає process.cwd()
|
|
119
|
+
process.exit(exitCode)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Або з явним коренем (тести/інтеграція):
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
const exitCode = await check('/abs/path/to/repo-root')
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Сценарій 1: репо самого пакета `@nitra/cursor`
|
|
129
|
+
|
|
130
|
+
- `npm/skills/` існує, містить кілька скілів (наприклад, `n-docgen/`, `n-fix/`, `mdc-check/`).
|
|
131
|
+
- Для кожного скіла читається `meta.json`, перевіряються `worktree` (boolean) і `auto` (опціонально).
|
|
132
|
+
- Якщо хоч один скіл має `auto.md`, або відсутній/невалідний `meta.json`, або `worktree` не boolean, або `auto` нерозпізнане — функція повертає `1`.
|
|
133
|
+
- Інакше — `0`.
|
|
134
|
+
|
|
135
|
+
### Сценарій 2: проєкт-споживач `@nitra/cursor`
|
|
136
|
+
|
|
137
|
+
- `npm/skills/` відсутній (інша файлова структура).
|
|
138
|
+
- Функція фіксує `pass(...)`, повертає `0` і не виконує жодної ітерації.
|
|
139
|
+
- Це дозволяє правилам пакета транзитивно застосовуватися в чужих проєктах без false-negative.
|
|
140
|
+
|
|
141
|
+
### Сценарій 3: переходова міграція на `meta.json`
|
|
142
|
+
|
|
143
|
+
- У скілі лишилася стара пам'ятка-метадані `auto.md` поряд з новим `meta.json`.
|
|
144
|
+
- Перевірка явно фейлить такий скіл повідомленням `залишковий auto.md — видали (метадані тепер у meta.json)`.
|
|
145
|
+
- Це примушує закрити технічний борг міграції.
|
|
146
|
+
|
|
147
|
+
### Очікуваний формат `meta.json`
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"worktree": true,
|
|
152
|
+
"auto": "завжди"
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
або
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"worktree": false,
|
|
161
|
+
"auto": ["n-bun", "n-flow"]
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
або (мінімально):
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"worktree": false
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Невалідні приклади (будуть зафейлені)
|
|
174
|
+
|
|
175
|
+
- відсутній файл `meta.json` → `відсутній або невалідний meta.json`;
|
|
176
|
+
- `{"worktree": "yes"}` → `meta.json.worktree має бути boolean`;
|
|
177
|
+
- `{"worktree": true, "auto": []}` або `{"worktree": true, "auto": "always"}` → `meta.json.auto нерозпізнане ...`;
|
|
178
|
+
- наявний `auto.md` поряд → `залишковий auto.md — видали`.
|
|
179
|
+
|
|
180
|
+
### Rebuild Test
|
|
181
|
+
|
|
182
|
+
Перевірка інваріантів модуля (можна виконати вручну для регресії):
|
|
183
|
+
|
|
184
|
+
1. У тимчасовому корені створити порожню директорію без `npm/skills/` → `check(tmp)` має повернути `0` і зафіксувати `pass` про відсутність каталогу.
|
|
185
|
+
2. Створити `npm/skills/foo/meta.json` з валідним вмістом `{"worktree": true}` → `check(tmp)` має повернути `0`.
|
|
186
|
+
3. Замінити `worktree` на рядок (`"true"`) → `check(tmp)` має повернути `1` з повідомленням про boolean.
|
|
187
|
+
4. Додати `npm/skills/foo/auto.md` → `check(tmp)` має повернути `1` з повідомленням про залишковий `auto.md`.
|
|
188
|
+
5. Видалити `meta.json` → `check(tmp)` має повернути `1` з повідомленням про відсутній/невалідний `meta.json`.
|
|
189
|
+
6. Створити `npm/skills/.hidden/` — перевірка має ігнорувати її (повернути `0`, якщо інших скілів немає).
|
|
190
|
+
7. Додати `npm/skills/bar/meta.json` з `{"worktree": false, "auto": "завжди"}` → `pass`; замінити `auto` на `"always"` → `fail` про нерозпізнане `auto`.
|