@7n/rules-lang-js 0.3.1 → 0.4.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.
Files changed (258) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +14 -4
  3. package/rules/bun/bunfig/bunfig.mdc +17 -0
  4. package/rules/bun/bunfig/bunfig.rego +29 -0
  5. package/rules/bun/bunfig/concern.json +9 -0
  6. package/rules/bun/bunfig/template/bunfig.toml.snippet.toml +2 -0
  7. package/rules/bun/docs/index.md +11 -0
  8. package/rules/bun/layout/concern.json +16 -0
  9. package/rules/bun/layout/docs/fix-layout.md +29 -0
  10. package/rules/bun/layout/docs/main.md +34 -0
  11. package/rules/bun/layout/fix-layout.mjs +63 -0
  12. package/rules/bun/layout/layout.mdc +60 -0
  13. package/rules/bun/layout/main.mjs +53 -0
  14. package/rules/bun/licensee/concern.json +7 -0
  15. package/rules/bun/licensee/docs/fix-licensee.md +27 -0
  16. package/rules/bun/licensee/docs/index.md +12 -0
  17. package/rules/bun/licensee/docs/main.md +30 -0
  18. package/rules/bun/licensee/fix-licensee.mjs +31 -0
  19. package/rules/bun/licensee/main.mjs +68 -0
  20. package/rules/bun/lint-surface/concern.json +3 -0
  21. package/rules/bun/lint-surface/lint-surface.mdc +13 -0
  22. package/rules/bun/main.json +1 -0
  23. package/rules/bun/main.mdc +11 -0
  24. package/rules/bun/package_json/concern.json +9 -0
  25. package/rules/bun/package_json/docs/fix-package_json.md +28 -0
  26. package/rules/bun/package_json/docs/index.md +9 -0
  27. package/rules/bun/package_json/fix-package_json.mjs +311 -0
  28. package/rules/bun/package_json/package_json.mdc +14 -0
  29. package/rules/bun/package_json/package_json.rego +64 -0
  30. package/rules/bun/package_json/template/package.json.deny.json +4 -0
  31. package/rules/js/check/check.mdc +26 -0
  32. package/rules/js/check/concern.json +19 -0
  33. package/rules/js/check/docs/eslint-config.md +56 -0
  34. package/rules/js/check/docs/fix-check.md +48 -0
  35. package/rules/js/check/docs/index.md +13 -0
  36. package/rules/js/check/docs/main.md +48 -0
  37. package/rules/js/check/eslint-config.mjs +262 -0
  38. package/rules/js/check/fix-check.mjs +82 -0
  39. package/rules/js/check/main.mjs +310 -0
  40. package/rules/js/dep-policy/concern.json +4 -0
  41. package/rules/js/dep-policy/dep-policy.mdc +36 -0
  42. package/rules/js/dep-policy/docs/main.md +35 -0
  43. package/rules/js/dep-policy/main.mjs +99 -0
  44. package/rules/js/docs/index.md +11 -0
  45. package/rules/js/eslint/concern.json +8 -0
  46. package/rules/js/eslint/docs/fix-eslint.md +50 -0
  47. package/rules/js/eslint/docs/fix-worker.md +31 -0
  48. package/rules/js/eslint/docs/index.md +11 -0
  49. package/rules/js/eslint/docs/main.md +35 -0
  50. package/rules/js/eslint/fix-eslint.mjs +160 -0
  51. package/rules/js/eslint/fix-worker.mjs +139 -0
  52. package/rules/js/eslint/main.mjs +115 -0
  53. package/rules/js/file-extensions/concern.json +3 -0
  54. package/rules/js/file-extensions/file-extensions.mdc +12 -0
  55. package/rules/js/jscpd_config/concern.json +11 -0
  56. package/rules/js/jscpd_config/docs/fix-jscpd_config.md +25 -0
  57. package/rules/js/jscpd_config/docs/index.md +9 -0
  58. package/rules/js/jscpd_config/fix-jscpd_config.mjs +3 -0
  59. package/rules/js/jscpd_config/jscpd_config.mdc +42 -0
  60. package/rules/js/jscpd_config/jscpd_config.rego +44 -0
  61. package/rules/js/jscpd_config/template/.jscpd.json.snippet.json +7 -0
  62. package/rules/js/jscpd_duplicates/concern.json +7 -0
  63. package/rules/js/jscpd_duplicates/docs/main.md +29 -0
  64. package/rules/js/jscpd_duplicates/main.mjs +69 -0
  65. package/rules/js/knip/concern.json +7 -0
  66. package/rules/js/knip/docs/main.md +32 -0
  67. package/rules/js/knip/knip.mdc +15 -0
  68. package/rules/js/knip/main.mjs +68 -0
  69. package/rules/js/lint-findings/concern.json +3 -0
  70. package/rules/js/lint-findings/docs/main.md +40 -0
  71. package/rules/js/lint-findings/main.mjs +125 -0
  72. package/rules/js/main.json +1 -0
  73. package/rules/js/main.mdc +18 -0
  74. package/rules/js/package_json/concern.json +9 -0
  75. package/rules/js/package_json/docs/fix-package_json.md +25 -0
  76. package/rules/js/package_json/docs/index.md +9 -0
  77. package/rules/js/package_json/fix-package_json.mjs +3 -0
  78. package/rules/js/package_json/package_json.mdc +15 -0
  79. package/rules/js/package_json/package_json.rego +142 -0
  80. package/rules/js/package_json/template/package.json.snippet.json +6 -0
  81. package/rules/js/tooling/concern.json +3 -0
  82. package/rules/js/tooling/data/tooling/knip-canonical.json +30 -0
  83. package/rules/js/tooling/data/tooling/oxlint-canonical.json +400 -0
  84. package/rules/js/tooling/docs/main.md +53 -0
  85. package/rules/js/tooling/main.mjs +183 -0
  86. package/rules/js/utils_imports/concern.json +4 -0
  87. package/rules/js/utils_imports/docs/main.md +50 -0
  88. package/rules/js/utils_imports/main.mjs +185 -0
  89. package/rules/js/utils_imports/utils_imports.mdc +15 -0
  90. package/rules/js/vscode_extensions/concern.json +11 -0
  91. package/rules/js/vscode_extensions/docs/fix-vscode_extensions.md +24 -0
  92. package/rules/js/vscode_extensions/docs/index.md +11 -0
  93. package/rules/js/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  94. package/rules/js/vscode_extensions/template/extensions.json.snippet.json +6 -0
  95. package/rules/js/vscode_extensions/vscode_extensions.mdc +11 -0
  96. package/rules/js/vscode_extensions/vscode_extensions.rego +12 -0
  97. package/rules/js-bun-db/connection/concern.json +3 -0
  98. package/rules/js-bun-db/connection/connection.mdc +42 -0
  99. package/rules/js-bun-db/docs/index.md +11 -0
  100. package/rules/js-bun-db/lib/bun-sql-scan.mjs +1047 -0
  101. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +63 -0
  102. package/rules/js-bun-db/lib/docs/index.md +11 -0
  103. package/rules/js-bun-db/main.json +1 -0
  104. package/rules/js-bun-db/main.mdc +8 -0
  105. package/rules/js-bun-db/package_json/concern.json +9 -0
  106. package/rules/js-bun-db/package_json/package_json.mdc +31 -0
  107. package/rules/js-bun-db/package_json/package_json.rego +15 -0
  108. package/rules/js-bun-db/package_json/template/package.json.deny.json +6 -0
  109. package/rules/js-bun-db/pg_format_identifiers/concern.json +3 -0
  110. package/rules/js-bun-db/pg_format_identifiers/pg_format_identifiers.mdc +104 -0
  111. package/rules/js-bun-db/safety/concern.json +4 -0
  112. package/rules/js-bun-db/safety/docs/main.md +34 -0
  113. package/rules/js-bun-db/safety/main.mjs +430 -0
  114. package/rules/js-bun-db/safety/safety.mdc +458 -0
  115. package/rules/js-bun-redis/docs/index.md +11 -0
  116. package/rules/js-bun-redis/imports/concern.json +4 -0
  117. package/rules/js-bun-redis/imports/docs/main.md +36 -0
  118. package/rules/js-bun-redis/imports/imports.mdc +47 -0
  119. package/rules/js-bun-redis/imports/main.mjs +88 -0
  120. package/rules/js-bun-redis/lib/docs/index.md +11 -0
  121. package/rules/js-bun-redis/lib/docs/redis-imports.md +227 -0
  122. package/rules/js-bun-redis/lib/redis-imports.mjs +130 -0
  123. package/rules/js-bun-redis/main.json +1 -0
  124. package/rules/js-bun-redis/main.mdc +8 -0
  125. package/rules/js-bun-redis/package_json/concern.json +9 -0
  126. package/rules/js-bun-redis/package_json/package_json.mdc +11 -0
  127. package/rules/js-bun-redis/package_json/package_json.rego +15 -0
  128. package/rules/js-bun-redis/package_json/template/package.json.deny.json +12 -0
  129. package/rules/js-mssql/deps/concern.json +4 -0
  130. package/rules/js-mssql/deps/docs/main.md +33 -0
  131. package/rules/js-mssql/deps/main.mjs +297 -0
  132. package/rules/js-mssql/docs/index.md +11 -0
  133. package/rules/js-mssql/lib/docs/index.md +11 -0
  134. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +380 -0
  135. package/rules/js-mssql/lib/mssql-pool-scan.mjs +610 -0
  136. package/rules/js-mssql/main.json +1 -0
  137. package/rules/js-mssql/main.mdc +144 -0
  138. package/rules/js-mssql/mssql-tvp/concern.json +3 -0
  139. package/rules/js-mssql/mssql-tvp/mssql-tvp.mdc +77 -0
  140. package/rules/js-mssql/package_json/concern.json +9 -0
  141. package/rules/js-mssql/package_json/package_json.mdc +9 -0
  142. package/rules/js-mssql/package_json/package_json.rego +57 -0
  143. package/rules/js-run/configmap/concern.json +9 -0
  144. package/rules/js-run/configmap/configmap.mdc +37 -0
  145. package/rules/js-run/configmap/configmap.rego +21 -0
  146. package/rules/js-run/configmap/template/configmap.yaml.contains.yml +4 -0
  147. package/rules/js-run/docs/index.md +11 -0
  148. package/rules/js-run/jsconfig/concern.json +9 -0
  149. package/rules/js-run/jsconfig/docs/fix-jsconfig.md +28 -0
  150. package/rules/js-run/jsconfig/docs/index.md +9 -0
  151. package/rules/js-run/jsconfig/fix-jsconfig.mjs +119 -0
  152. package/rules/js-run/jsconfig/jsconfig.mdc +48 -0
  153. package/rules/js-run/jsconfig/jsconfig.rego +59 -0
  154. package/rules/js-run/jsconfig/template/jsconfig.json.snippet.json +10 -0
  155. package/rules/js-run/lib/bunyan-imports.mjs +98 -0
  156. package/rules/js-run/lib/check-env-scan.mjs +338 -0
  157. package/rules/js-run/lib/conn-file-rules.mjs +214 -0
  158. package/rules/js-run/lib/conn-imports-scan.mjs +154 -0
  159. package/rules/js-run/lib/docs/bunyan-imports.md +121 -0
  160. package/rules/js-run/lib/docs/check-env-scan.md +438 -0
  161. package/rules/js-run/lib/docs/conn-file-rules.md +304 -0
  162. package/rules/js-run/lib/docs/conn-imports-scan.md +208 -0
  163. package/rules/js-run/lib/docs/index.md +16 -0
  164. package/rules/js-run/lib/docs/promise-settimeout-scan.md +334 -0
  165. package/rules/js-run/lib/docs/temporal-scan.md +29 -0
  166. package/rules/js-run/lib/promise-settimeout-scan.mjs +128 -0
  167. package/rules/js-run/lib/temporal-scan.mjs +52 -0
  168. package/rules/js-run/main.json +1 -0
  169. package/rules/js-run/main.mdc +16 -0
  170. package/rules/js-run/package_json/concern.json +9 -0
  171. package/rules/js-run/package_json/package_json.mdc +44 -0
  172. package/rules/js-run/package_json/package_json.rego +37 -0
  173. package/rules/js-run/package_json/template/package.json.deny.json +22 -0
  174. package/rules/js-run/project-structure/concern.json +3 -0
  175. package/rules/js-run/project-structure/project-structure.mdc +11 -0
  176. package/rules/js-run/runtime/concern.json +12 -0
  177. package/rules/js-run/runtime/docs/fix-runtime.md +27 -0
  178. package/rules/js-run/runtime/docs/main.md +35 -0
  179. package/rules/js-run/runtime/fix-runtime.mjs +46 -0
  180. package/rules/js-run/runtime/main.mjs +496 -0
  181. package/rules/js-run/runtime/runtime.mdc +184 -0
  182. package/rules/js-run/scope/concern.json +3 -0
  183. package/rules/js-run/scope/scope.mdc +11 -0
  184. package/rules/npm-module/docs/index.md +11 -0
  185. package/rules/npm-module/emit_types_config/concern.json +9 -0
  186. package/rules/npm-module/emit_types_config/docs/fix-emit_types_config.md +25 -0
  187. package/rules/npm-module/emit_types_config/docs/index.md +9 -0
  188. package/rules/npm-module/emit_types_config/emit_types_config.mdc +43 -0
  189. package/rules/npm-module/emit_types_config/emit_types_config.rego +28 -0
  190. package/rules/npm-module/emit_types_config/fix-emit_types_config.mjs +5 -0
  191. package/rules/npm-module/emit_types_config/template/tsconfig.emit-types.json.snippet.json +9 -0
  192. package/rules/npm-module/header_doc_pointer/concern.json +5 -0
  193. package/rules/npm-module/header_doc_pointer/docs/main.md +40 -0
  194. package/rules/npm-module/header_doc_pointer/header_doc_pointer.mdc +18 -0
  195. package/rules/npm-module/header_doc_pointer/main.mjs +131 -0
  196. package/rules/npm-module/main.json +1 -0
  197. package/rules/npm-module/main.mdc +36 -0
  198. package/rules/npm-module/npm_package_json/concern.json +9 -0
  199. package/rules/npm-module/npm_package_json/docs/fix-npm_package_json.md +26 -0
  200. package/rules/npm-module/npm_package_json/docs/index.md +9 -0
  201. package/rules/npm-module/npm_package_json/fix-npm_package_json.mjs +5 -0
  202. package/rules/npm-module/npm_package_json/npm_package_json.mdc +57 -0
  203. package/rules/npm-module/npm_package_json/npm_package_json.rego +73 -0
  204. package/rules/npm-module/npm_package_json/template/package.json.snippet.json +1 -0
  205. package/rules/npm-module/package_structure/concern.json +5 -0
  206. package/rules/npm-module/package_structure/docs/main.md +37 -0
  207. package/rules/npm-module/package_structure/main.mjs +448 -0
  208. package/rules/npm-module/package_structure/package_structure.mdc +63 -0
  209. package/rules/npm-module/root_package_json/concern.json +9 -0
  210. package/rules/npm-module/root_package_json/docs/fix-root_package_json.md +25 -0
  211. package/rules/npm-module/root_package_json/docs/index.md +9 -0
  212. package/rules/npm-module/root_package_json/fix-root_package_json.mjs +5 -0
  213. package/rules/npm-module/root_package_json/root_package_json.mdc +41 -0
  214. package/rules/npm-module/root_package_json/root_package_json.rego +28 -0
  215. package/rules/npm-module/root_package_json/template/package.json.snippet.json +1 -0
  216. package/rules/npm-module/rule_meta/concern.json +8 -0
  217. package/rules/npm-module/rule_meta/docs/main.md +35 -0
  218. package/rules/npm-module/rule_meta/main.mjs +119 -0
  219. package/rules/npm-module/rule_meta/rule_meta.mdc +11 -0
  220. package/rules/npm-module/skill_meta/concern.json +5 -0
  221. package/rules/npm-module/skill_meta/docs/main.md +149 -0
  222. package/rules/npm-module/skill_meta/main.mjs +91 -0
  223. package/rules/npm-module/skill_meta/skill_meta.mdc +11 -0
  224. package/rules/tool-surface/docs/index.md +11 -0
  225. package/rules/tool-surface/main.json +6 -0
  226. package/rules/tool-surface/main.mdc +72 -0
  227. package/rules/vue/composition-api/composition-api.mdc +82 -0
  228. package/rules/vue/composition-api/concern.json +3 -0
  229. package/rules/vue/docs/index.md +11 -0
  230. package/rules/vue/lib/docs/index.md +11 -0
  231. package/rules/vue/lib/docs/vue-forbidden-imports.md +265 -0
  232. package/rules/vue/lib/vue-forbidden-imports.mjs +240 -0
  233. package/rules/vue/main.json +1 -0
  234. package/rules/vue/main.mdc +18 -0
  235. package/rules/vue/nheader-layout/concern.json +3 -0
  236. package/rules/vue/nheader-layout/nheader-layout.mdc +171 -0
  237. package/rules/vue/package_json/concern.json +9 -0
  238. package/rules/vue/package_json/package_json.mdc +30 -0
  239. package/rules/vue/package_json/package_json.rego +140 -0
  240. package/rules/vue/packages/concern.json +6 -0
  241. package/rules/vue/packages/docs/index.md +11 -0
  242. package/rules/vue/packages/docs/main.md +35 -0
  243. package/rules/vue/packages/main.mjs +575 -0
  244. package/rules/vue/packages/packages.mdc +56 -0
  245. package/rules/vue/quasar-ui/concern.json +3 -0
  246. package/rules/vue/quasar-ui/quasar-ui.mdc +32 -0
  247. package/rules/vue/structure/concern.json +3 -0
  248. package/rules/vue/structure/structure.mdc +101 -0
  249. package/rules/vue/testing/concern.json +3 -0
  250. package/rules/vue/testing/testing.mdc +40 -0
  251. package/rules/vue/tfm-translations/concern.json +7 -0
  252. package/rules/vue/tfm-translations/docs/main.md +29 -0
  253. package/rules/vue/tfm-translations/main.mjs +55 -0
  254. package/rules/vue/tfm-translations/tfm-translations.mdc +32 -0
  255. package/rules/vue/vite-config/concern.json +3 -0
  256. package/rules/vue/vite-config/vite-config.mdc +153 -0
  257. package/rules/vue/vite-env/concern.json +3 -0
  258. package/rules/vue/vite-env/vite-env.mdc +61 -0
@@ -0,0 +1,57 @@
1
+ ## Rego-gate: валідація `npm/package.json`
2
+
3
+ Rego-пакет: `npm-module.npm_package_json`
4
+
5
+ Цільовий файл: `npm/package.json`
6
+
7
+ ### Що перевіряється
8
+
9
+ **Поле `types`** (логіка в rego, не template-driven):
10
+
11
+ - Має відповідати патерну `./types/index.d.ts` або `./types/<назва>.d.ts|.d.mts`.
12
+ - Шляхи поза директорією `types/` або з іншими розширеннями (`.ts` без `.d`) — deny.
13
+
14
+ **Поле `files`** (частково template-driven):
15
+
16
+ - Обовʼязкове, має бути непорожнім масивом.
17
+ - Subset-of перевірка: кожне значення з канонічного сніпету має бути присутнє у `files`. За замовчуванням — `"types"` обовʼязковий.
18
+
19
+ **Поле `devDependencies`** (inverse-pattern, логіка в rego):
20
+
21
+ - Не публікуються користувачам пакета — має бути відсутнє або порожнє `{}`.
22
+ - Наявність будь-яких devDeps → deny з переліком залежностей. Dev-інструментарій переноситься у кореневий `package.json`; CLI-тули, які пакет спавнить через `bunx` у репозиторіях-споживачах (пінінг версій), — у `dependencies` (кореневе bun-правило `package_json` такі пакети в root devDeps не пускає).
23
+
24
+ Канонічний сніпет `files`: [package.json.snippet.json](./template/package.json.snippet.json)
25
+
26
+ FS-перевірки (наявність файлу зі шляху `types`, скан tarball на тест-патерни) — у JS-перевірці, не тут.
27
+
28
+ ### Приклади
29
+
30
+ ✓ Правильно:
31
+
32
+ ```json
33
+ {
34
+ "name": "@7n/rules",
35
+ "types": "./types/bin/n-rules.d.ts",
36
+ "files": ["types", "mdc", "bin", "CHANGELOG.md"],
37
+ "dependencies": { "oxc-parser": "^0.128.0" }
38
+ }
39
+ ```
40
+
41
+ ✗ Неправильно — `types` поза директорією `types/`:
42
+
43
+ ```json
44
+ { "types": "./dist/index.d.ts" }
45
+ ```
46
+
47
+ ✗ Неправильно — `files` без `"types"`:
48
+
49
+ ```json
50
+ { "files": ["bin", "mdc"] }
51
+ ```
52
+
53
+ ✗ Неправильно — наявні `devDependencies`:
54
+
55
+ ```json
56
+ { "devDependencies": { "@7n/rules": "^1.0.0" } }
57
+ ```
@@ -0,0 +1,73 @@
1
+ # Порт перевірок `npm/package.json` (npm-module.mdc).
2
+ #
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/package.json.snippet.json
5
+ # (snippet-array subset-of для whitelist `files`).
6
+ #
7
+ # Логіка, що ЛИШАЄТЬСЯ у rego (inverse-patterns, не виносяться у template):
8
+ # - форма поля `types` (regex pattern: `./types/index.d.ts` або `./types/<…>.d.ts|.d.mts`);
9
+ # - `devDependencies` мають бути відсутні або порожні (inverse-pattern — заборона будь-яких).
10
+ #
11
+ # FS-перевірки (наявність файлу зі шляху `types`, скан tarball на тест-патерни) — у JS.
12
+ package npm_module.npm_package_json
13
+
14
+ import rego.v1
15
+
16
+ types_field_template := concat(" ", [
17
+ "npm/package.json: поле \"types\" має бути \"./types/index.d.ts\"",
18
+ "або \"./types/<…>.d.ts|.d.mts\" (зараз: %v) (npm-module.mdc)",
19
+ ])
20
+
21
+ dev_deps_template := concat(" ", [
22
+ "npm/package.json: \"devDependencies\" не публікуються користувачам пакета —",
23
+ "dev-інструментарій перенеси у кореневий package.json, а CLI-тули, які пакет",
24
+ "спавнить через bunx у споживачів, — у \"dependencies\": %v (npm-module.mdc)",
25
+ ])
26
+
27
+ # ── deny: types (regex — лишається в rego) ───────────────────────────────
28
+
29
+ deny contains msg if {
30
+ types_field := object.get(input, "types", "")
31
+ not valid_types_field(types_field)
32
+ msg := sprintf(types_field_template, [types_field])
33
+ }
34
+
35
+ # ── deny: files має існувати та бути масивом ─────────────────────────────
36
+
37
+ deny contains msg if {
38
+ not is_array(object.get(input, "files", null))
39
+ msg := "npm/package.json: обовʼязковий whitelist \"files\" (без нього npm пакує майже все) (npm-module.mdc)"
40
+ }
41
+
42
+ deny contains msg if {
43
+ is_array(input.files)
44
+ count(input.files) == 0
45
+ msg := "npm/package.json: масив \"files\" не повинен бути порожнім (npm-module.mdc: компактний пакет)"
46
+ }
47
+
48
+ # ── deny: files subset-of з template (template-driven) ──────────────────
49
+
50
+ deny contains msg if {
51
+ some field, expected_values in data.template.snippet
52
+ is_array(object.get(input, field, null))
53
+ count(input[field]) > 0
54
+ actual_set := {v | some v in input[field]}
55
+ some required in expected_values
56
+ not required in actual_set
57
+ msg := sprintf("npm/package.json: масив \"%s\" має містити %q (npm-module.mdc)", [field, required])
58
+ }
59
+
60
+ # ── deny: devDependencies (inverse-pattern, лишається в rego) ────────────
61
+
62
+ deny contains msg if {
63
+ dev := object.get(input, "devDependencies", {})
64
+ count(dev) > 0
65
+ names := concat(", ", sort([n | some n, _ in dev]))
66
+ msg := sprintf(dev_deps_template, [names])
67
+ }
68
+
69
+ # ── helpers ────────────────────────────────────────────────────────────────
70
+
71
+ valid_types_field("./types/index.d.ts")
72
+
73
+ valid_types_field(t) if regex.match(`^\./types/.+\.d\.(ts|mts)$`, t)
@@ -0,0 +1 @@
1
+ { "files": ["types"] }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "fixability": "structural",
4
+ "lint": { "scope": "full", "glob": ["**/*"] }
5
+ }
@@ -0,0 +1,37 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: plugins/lang-js/rules/npm-module/package_structure/main.mjs
5
+ docgen:
6
+ crc: b6884b16
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 85
9
+ issues: anchor-miss:(npm-module.mdc),surzhik,judge:inaccurate:0.99
10
+ judgeModel: openai-codex/gpt-5.4-mini
11
+ ---
12
+
13
+ ## Огляд
14
+
15
+ Компонент валідує відповідність проєкту стандартам, визначеним у `npm-module.mdc`, аналізуючи конфігурації (`tsconfig.emit-types.json`, `package.json`, `meta.json`). Він виконує логіку, зокрема через функції `globToRegex`, `findTestFrameworkImport`, `classifyPublishedFileAsTest` та `main`, обробляючи помилки за принципом fail-safe, ніколи не кидаючи винятків назовні. Процес роботи є read-only, і він свідомо ігнорує каталоги `.github` та `.git` під час аналізу.
16
+
17
+ ## Поведінка
18
+
19
+ Поведінка:
20
+ globToRegex перетворює glob-патерн у регулярний вираз з якорями початку та кінця, підтримуючи globstar, `*`, `?` та brace-альтернативи; ігнорує шляхи з `.github` та `.git`.
21
+ findTestFrameworkImport шукає у вмісті файлу імпорти або виклики, які вказують на використання тестових фреймворків.
22
+ classifyPublishedFileAsTest визначає, чи вважається певний опублікований файл тестовим або фікстурою на основі назви, каталогу або імпортів.
23
+ main виконує повну перевірку відповідності проєкту конвенціям npm-module.mdc, аналізуючи структуру, конфігурації та файли, і виводить статус проходження перевірок.
24
+
25
+ ## Публічний API
26
+
27
+ globToRegex — перетворює шаблон пошуку файлів у регулярний вираз, забезпечуючи коректне зіставлення шляхів. Реалізація живе в ядрі (`@7n/rules/scripts/lib/glob-to-regex.mjs`, спільна з auto-rules) — тут ре-експорт.
28
+ findTestFrameworkImport — виявляє, чи використовується у файлі механізм імпорту тест-фреймворку.
29
+ classifyPublishedFileAsTest — визначає, чи повинен вважатися опублікований файл тестом або фікстурою, ґрунтуючись на його шляху та вмісті.
30
+ main — забезпечує дотримання проєкту всіх вимог, визначених у конвенціях, описаних у npm-module.mdc.
31
+
32
+ ## Гарантії поведінки
33
+
34
+ - Read-only: не виконує операцій запису (ФС/БД).
35
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
36
+ - За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
37
+ - Свідомо пропускає шляхи: `.github`, `.git`.
@@ -0,0 +1,448 @@
1
+ /** @see ./docs/package_structure.md */
2
+ import { existsSync } from 'node:fs'
3
+ import { readFile, stat } from 'node:fs/promises'
4
+ import { join, sep } from 'node:path'
5
+
6
+ // glob → RegExp живе в ядрі (спільний з auto-rules рушієм); ре-експорт зберігає публічний API концерну
7
+ export { globToRegex } from '@7n/rules/scripts/lib/glob-to-regex.mjs'
8
+ import { globToRegex } from '@7n/rules/scripts/lib/glob-to-regex.mjs'
9
+
10
+ import { parseSync } from 'oxc-parser'
11
+
12
+ import {
13
+ dynamicImportModule,
14
+ langFromPath,
15
+ requireCallModule,
16
+ walkAstWithAncestors
17
+ } from '@7n/rules/scripts/utils/ast-scan-utils.mjs'
18
+ import { createViolationReporter } from '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
19
+ import { loadCursorIgnorePaths } from '@7n/rules/scripts/lib/load-cursor-config.mjs'
20
+ import { walkDir } from '@7n/rules/scripts/utils/walkDir.mjs'
21
+
22
+ /** Файл проєкту TypeScript для emit без каталогу `src` (див. npm-module.mdc) */
23
+ const EMIT_TYPES_CONFIG = 'npm/tsconfig.emit-types.json'
24
+
25
+ /** Каталоги, які за конвенцією тримають тести / фікстури і не повинні публікуватися. */
26
+ const TEST_DIR_NAMES = new Set(['tests', '__tests__', 'fixtures', '__fixtures__', 'spec', 'test'])
27
+
28
+ /**
29
+ * Імена файлів за патернами test/spec (тільки basename, без path). Rego
30
+ * (`*_test.rego`) свідомо не входить: за конвенцією conftest юніт-тест лежить
31
+ * поруч з полісі у тому самому `package` — і це дозволений виняток усередині
32
+ * опублікованого `policy/`-каталогу (npm-module.mdc).
33
+ * @param {string} cwd корінь репозиторію
34
+ */
35
+ const TEST_FILE_PATTERNS = [/^.+\.(test|spec)\.[cm]?[jt]sx?$/iu]
36
+
37
+ /** Розширення, у яких ловимо імпорти test-фреймворків. */
38
+ const JS_LIKE_EXT_RE = /\.[cm]?[jt]sx?$/iu
39
+
40
+ /** Імпорти/require/dynamic-import, які видають test-файл. */
41
+ const TEST_FRAMEWORK_MODULES = new Set([
42
+ 'bun:test',
43
+ 'node:test',
44
+ 'vitest',
45
+ '@jest/globals',
46
+ 'jest',
47
+ 'mocha',
48
+ 'ava',
49
+ 'tap',
50
+ 'tape'
51
+ ])
52
+
53
+ /**
54
+ * Чи є під `npm/src` хоча б один `.js` (рекурсивно).
55
+ * @param {string} cwd корінь репозиторію
56
+ * @param {string[]} [ignorePaths] абсолютні шляхи каталогів, повністю виключених з обходу
57
+ * @returns {Promise<boolean>} `true`, якщо знайдено хоча б один `.js`
58
+ */
59
+ async function npmSrcTreeHasJsFile(cwd, ignorePaths = []) {
60
+ const root = join(cwd, 'npm/src')
61
+ if (!existsSync(root)) {
62
+ return false
63
+ }
64
+ let found = false
65
+ await walkDir(
66
+ root,
67
+ p => {
68
+ if (p.endsWith('.js')) {
69
+ found = true
70
+ }
71
+ },
72
+ ignorePaths
73
+ )
74
+ return found
75
+ }
76
+
77
+ /**
78
+ * Знаходить текстовий вміст конфігурації hk для перевірки npm-module.
79
+ * @returns {Promise<{ path: string, text: string } | null>} знайдений файл або `null`
80
+ * @param {string} cwd корінь репозиторію
81
+ */
82
+ async function readHkConfig(cwd) {
83
+ const candidates = ['hk.pkl', '.config/hk.pkl']
84
+ for (const p of candidates) {
85
+ const abs = join(cwd, p)
86
+ if (existsSync(abs)) {
87
+ const text = await readFile(abs, 'utf8')
88
+ return { path: p, text }
89
+ }
90
+ }
91
+ return null
92
+ }
93
+
94
+ /**
95
+ * Підрядки для hk при layout з каталогом `npm/src` і glob `src` + `.js` у команді (див. npm-module.mdc).
96
+ * @param {string} hkText текст конфігурації hk
97
+ * @returns {string[]} відсутні фрагменти
98
+ */
99
+ function missingHkSrcLayoutFragments(hkText) {
100
+ const need = [
101
+ '["pre-commit"]',
102
+ 'bunx -p typescript tsc',
103
+ 'src/**/*.js',
104
+ '--declaration',
105
+ '--allowJs',
106
+ '--emitDeclarationOnly',
107
+ '--outDir types',
108
+ '--skipLibCheck'
109
+ ]
110
+ return need.filter(s => !hkText.includes(s))
111
+ }
112
+
113
+ /**
114
+ * Підрядки для hk при layout з `tsconfig.emit-types.json` (див. npm-module.mdc).
115
+ * @param {string} hkText текст конфігурації hk
116
+ * @returns {string[]} відсутні фрагменти
117
+ */
118
+ function missingHkEmitTypesConfigFragments(hkText) {
119
+ const need = ['["pre-commit"]', 'bunx -p typescript tsc', 'tsconfig.emit-types.json']
120
+ return need.filter(s => !hkText.includes(s))
121
+ }
122
+
123
+ /**
124
+ * Застаріла інвокація команди `check`, прибраної в v14 (уніфікована поверхня `lint`).
125
+ * Матч на підрядок "check changelog" незалежно від того, яким бінарем/шляхом викликано.
126
+ */
127
+ const DEPRECATED_CHECK_CHANGELOG_RE = /\bcheck\s+changelog\b/u
128
+
129
+ /**
130
+ * Підрядки канонічного кроку `npm-changelog` у hk.pkl (fix-only, autofix через env
131
+ * `N_RULES_CHANGELOG_AUTOFIX=1`; сам крок ставить change-файл у git-індекс, `stage`
132
+ * не потрібен). Див. npm-module.mdc.
133
+ * @param {string} hkText текст конфігурації hk
134
+ * @returns {string[]} відсутні фрагменти
135
+ */
136
+ function missingHkNpmChangelogFragments(hkText) {
137
+ const need = ['["npm-changelog"]', 'N_RULES_CHANGELOG_AUTOFIX=1', 'lint changelog']
138
+ return need.filter(s => !hkText.includes(s))
139
+ }
140
+
141
+ /**
142
+ * Шлях на дискі до файлу з поля `types` у `npm/package.json` (значення на кшталт `./types/bin/x.d.ts`).
143
+ * @param {string} typesField значення поля `types` з `package.json`
144
+ * @returns {string | null} абсолютний шлях або `null`
145
+ */
146
+ function npmTypesFileFromPackageField(typesField) {
147
+ if (typeof typesField !== 'string' || !typesField.startsWith('./types/')) {
148
+ return null
149
+ }
150
+ const rel = typesField.slice(2)
151
+ return join('npm', rel)
152
+ }
153
+
154
+ /**
155
+ * Перевіряє наявність на диску файлу зі значення `types` у `npm/package.json`
156
+ * (cross-file: JSON-поле + FS). Структуру самого поля валідує
157
+ * `npm/policy/npm_module/npm_package_json/`; тут — лише чи файл реально існує.
158
+ * @param {boolean} useSrcJsLayout чи використовується layout з npm/src
159
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
160
+ * @param {(msg: string) => void} failFn callback при помилці
161
+ * @param {string} cwd корінь репозиторію
162
+ */
163
+ async function checkNpmPackageJson(useSrcJsLayout, passFn, failFn, cwd) {
164
+ const npmPkgPath = join(cwd, 'npm/package.json')
165
+ if (!existsSync(npmPkgPath)) return
166
+ const npmPkg = JSON.parse(await readFile(npmPkgPath, 'utf8'))
167
+ const typesField = npmPkg.types
168
+
169
+ const typesRel = useSrcJsLayout ? join('npm', 'types', 'index.d.ts') : npmTypesFileFromPackageField(typesField)
170
+ const missingTypesMsg = useSrcJsLayout
171
+ ? `Відсутній ${join('npm', 'types', 'index.d.ts')} (згенеруй tsc з npm-module.mdc)`
172
+ : `Файл для поля types не знайдено або шлях не під ./types/ — ${String(typesField)}`
173
+ if (typesRel && existsSync(join(cwd, typesRel))) {
174
+ passFn(`${typesRel} існує`)
175
+ } else {
176
+ failFn(missingTypesMsg)
177
+ }
178
+ }
179
+
180
+ /**
181
+ * FS-existence для `npm/tsconfig.emit-types.json` (структуру `compilerOptions`
182
+ * валідує `npm/policy/npm_module/emit_types_config/`).
183
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
184
+ * @param {(msg: string) => void} failFn callback при помилці
185
+ * @param {string} cwd корінь репозиторію
186
+ */
187
+ function checkEmitTypesConfig(passFn, failFn, cwd) {
188
+ if (!existsSync(join(cwd, EMIT_TYPES_CONFIG))) {
189
+ failFn(
190
+ `Без .js під npm/src потрібен ${EMIT_TYPES_CONFIG} (див. npm-module.mdc: emit через tsconfig, без штучного src/index.js)`
191
+ )
192
+ return
193
+ }
194
+ passFn(`${EMIT_TYPES_CONFIG} є (структуру перевіряє npx @7n/rules fix → npm_module.emit_types_config)`)
195
+ }
196
+
197
+ /**
198
+ * FS-existence для `npm-publish.yml` workflow. Поля workflow (`on.push.paths`,
199
+ * `branches`, `id-token: write`, JS-DevTools/npm-publish step) валідує
200
+ * `npm/policy/npm_module/npm_publish_yml/`.
201
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
202
+ * @param {(msg: string) => void} failFn callback при виявленому порушенні
203
+ * @param {string} cwd корінь репозиторію
204
+ */
205
+ function checkPublishWorkflow(passFn, failFn, cwd) {
206
+ const publishWf = '.github/workflows/npm-publish.yml'
207
+ if (existsSync(join(cwd, publishWf))) {
208
+ passFn(`${publishWf} є (структуру перевіряє npx @7n/rules fix → npm_module.npm_publish_yml)`)
209
+ } else {
210
+ failFn(`Відсутній ${publishWf} (npm-module.mdc: npm publish)`)
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Збирає список файлів, що потраплять у tarball, виходячи з `files` у
216
+ * `npm/package.json`. Підтримує позитивні patterns (директорії або файли) і
217
+ * негативні (`!…`). Шляхи повертаються у posix-формі, відносно `npm/`.
218
+ * Не пробує дублювати всю логіку `npm pack` (license/readme/тощо) — тут лише
219
+ * простір імен `files`, бо саме його сканує check.
220
+ * @param {string[]} filesField значення поля `files`
221
+ * @returns {Promise<string[]>} відсортовані posix-шляхи без `npm/` префікса
222
+ * @param {string} cwd корінь репозиторію
223
+ */
224
+ async function collectPublishedFiles(filesField, cwd) {
225
+ const positives = filesField.filter(p => typeof p === 'string' && !p.startsWith('!'))
226
+ const negatives = filesField.filter(p => typeof p === 'string' && p.startsWith('!')).map(p => globToRegex(p.slice(1)))
227
+ /** @type {Set<string>} */
228
+ const collected = new Set()
229
+ const npmRoot = join(cwd, 'npm')
230
+ for (const entry of positives) {
231
+ const fullPath = join(npmRoot, entry)
232
+ if (!existsSync(fullPath)) continue
233
+ const s = await stat(fullPath)
234
+ if (s.isFile()) {
235
+ collected.add(entry)
236
+ continue
237
+ }
238
+ if (!s.isDirectory()) continue
239
+ await walkDir(fullPath, p => {
240
+ const rel = p
241
+ .slice(npmRoot.length + 1)
242
+ .split(sep)
243
+ .join('/')
244
+ collected.add(rel)
245
+ })
246
+ }
247
+ const filtered = [...collected].filter(rel => negatives.every(re => !re.test(rel)))
248
+ filtered.sort()
249
+ return filtered
250
+ }
251
+
252
+ /**
253
+ * Чи є у файлі імпорт/require/dynamic-import з модуля тест-фреймворку.
254
+ * Парсимо через oxc-parser (як `bunyan-imports`/`redis-imports`). При помилці
255
+ * парсингу повертаємо `null` — це не наш checker для синтаксису.
256
+ * @param {string} content повний текст файлу
257
+ * @param {string} virtualPath шлях для вибору `lang`
258
+ * @returns {string | null} модуль, через який видно тест, або `null`
259
+ */
260
+ export function findTestFrameworkImport(content, virtualPath) {
261
+ const lang = langFromPath(virtualPath)
262
+ let result
263
+ try {
264
+ result = parseSync(virtualPath, content, { lang, sourceType: 'module' })
265
+ } catch {
266
+ return null
267
+ }
268
+ if (result.errors?.length) return null
269
+ for (const imp of result.module?.staticImports ?? []) {
270
+ const mod = imp.moduleRequest?.value
271
+ if (typeof mod === 'string' && TEST_FRAMEWORK_MODULES.has(mod)) return mod
272
+ }
273
+ /** @type {string | null} */
274
+ let found = null
275
+ walkAstWithAncestors(result.program, [], node => {
276
+ if (found) return
277
+ const reqMod = requireCallModule(node)
278
+ if (reqMod && TEST_FRAMEWORK_MODULES.has(reqMod)) {
279
+ found = reqMod
280
+ return
281
+ }
282
+ const dynMod = dynamicImportModule(node)
283
+ if (dynMod && TEST_FRAMEWORK_MODULES.has(dynMod)) {
284
+ found = dynMod
285
+ }
286
+ })
287
+ return found
288
+ }
289
+
290
+ /**
291
+ * Класифікує опублікований файл як test/fixture, якщо хоча б одна з ознак:
292
+ * (1) у шляху є каталог із `TEST_DIR_NAMES`; (2) basename відповідає
293
+ * `TEST_FILE_PATTERNS`; (3) для JS/TS-розширень — імпорт test-фреймворку.
294
+ *
295
+ * Carve-out: для шляху `rules/<rule-name>/...` сегмент `<rule-name>` (індекс 1)
296
+ * — це ім'я правила, а не каталог. Зокрема, правило з id `test` (або `tests`)
297
+ * описує конвенцію розміщення тестів і саме по собі не є test-fixture'ом.
298
+ * Подальші сегменти (наприклад, `rules/<r>/js/<c>/tests/`) продовжують перевірятись.
299
+ * @param {string} relPath posix-шлях відносно `npm/`
300
+ * @returns {Promise<string | null>} причина порушення або `null`
301
+ * @param {string} [cwd] корінь репозиторію
302
+ */
303
+ export async function classifyPublishedFileAsTest(relPath, cwd = process.cwd()) {
304
+ const segments = relPath.split('/')
305
+ const base = segments.at(-1)
306
+ const dirs = segments.slice(0, -1)
307
+ const testDir = dirs.find((seg, idx) => {
308
+ if (idx === 1 && dirs[0] === 'rules') {
309
+ return false
310
+ }
311
+ return TEST_DIR_NAMES.has(seg.toLowerCase())
312
+ })
313
+ if (testDir) return `test-style каталог "${testDir}/"`
314
+ if (TEST_FILE_PATTERNS.some(re => re.test(base))) return `test-style ім'я файлу`
315
+ if (JS_LIKE_EXT_RE.test(base)) {
316
+ const content = await readFile(join(cwd, 'npm', relPath), 'utf8')
317
+ const mod = findTestFrameworkImport(content, relPath)
318
+ if (mod) return `імпорт test-фреймворку "${mod}"`
319
+ }
320
+ return null
321
+ }
322
+
323
+ /**
324
+ * Для всіх файлів, що потрапили б у tarball (positive `files` мінус negative
325
+ * patterns), забороняє test-style каталоги/імена/імпорти. Так пакет лишається
326
+ * компактним і не везе користувачам тести й фікстури.
327
+ * @param {(msg: string) => void} pass callback при успіху
328
+ * @param {(msg: string) => void} fail callback при порушенні
329
+ * @returns {Promise<void>}
330
+ * @param {string} cwd корінь репозиторію
331
+ */
332
+ async function checkNoTestsInPublishedFiles(pass, fail, cwd) {
333
+ if (!existsSync(join(cwd, 'npm/package.json'))) return
334
+ const pkg = JSON.parse(await readFile(join(cwd, 'npm/package.json'), 'utf8'))
335
+ if (!Array.isArray(pkg.files)) return
336
+ const files = await collectPublishedFiles(pkg.files, cwd)
337
+ /** @type {{ file: string, reason: string }[]} */
338
+ const violations = []
339
+ for (const rel of files) {
340
+ const reason = await classifyPublishedFileAsTest(rel, cwd)
341
+ if (reason) violations.push({ file: rel, reason })
342
+ }
343
+ if (violations.length === 0) {
344
+ pass(`npm/: усі ${files.length} опублікованих файли без тестів і fixtures`)
345
+ return
346
+ }
347
+ for (const v of violations) {
348
+ fail(
349
+ `npm/${v.file}: ${v.reason} — додай у "files" у npm/package.json негативний glob, ` +
350
+ 'що виключає цей файл з tarball (наприклад "!**/*.test.mjs", "!**/fixtures/**", "!**/*_test.rego") (npm-module.mdc)'
351
+ )
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Перевіряє базову структуру монорепо: наявність каталогу `npm/` і
357
+ * `npm/package.json`. Поле `workspaces ∋ "npm"` у кореневому `package.json`
358
+ * валідує `npm/policy/npm_module/root_package_json/`.
359
+ * @param {(msg: string) => void} pass callback при успішній перевірці
360
+ * @param {(msg: string) => void} fail callback при помилці
361
+ * @param {string} cwd корінь репозиторію
362
+ */
363
+ async function checkNpmModuleBasicStructure(pass, fail, cwd) {
364
+ if (existsSync(join(cwd, 'package.json'))) {
365
+ pass('package.json існує')
366
+ } else {
367
+ fail('package.json не існує')
368
+ }
369
+
370
+ const npmDir = join(cwd, 'npm')
371
+ if (existsSync(npmDir)) {
372
+ const s = await stat(npmDir)
373
+ if (s.isDirectory()) {
374
+ pass('npm/ директорія існує')
375
+ } else {
376
+ fail('npm має бути директорією')
377
+ }
378
+ } else {
379
+ fail('npm/ директорія не існує')
380
+ }
381
+
382
+ if (existsSync(join(cwd, 'npm/package.json'))) {
383
+ pass('npm/package.json існує')
384
+ } else {
385
+ fail('npm/package.json не існує — створи package.json для npm модуля')
386
+ }
387
+ }
388
+
389
+ /**
390
+ * Перевіряє відповідність проєкту правилам npm-module.mdc
391
+ * @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту
392
+ * @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} результат лінту
393
+ */
394
+ export async function lint(ctx) {
395
+ const cwd = ctx.cwd
396
+ const reporter = createViolationReporter(ctx)
397
+ const { pass, fail } = reporter
398
+
399
+ await checkNpmModuleBasicStructure(pass, fail, cwd)
400
+ await checkNoTestsInPublishedFiles(pass, fail, cwd)
401
+
402
+ const ignorePaths = await loadCursorIgnorePaths(cwd)
403
+ const useSrcJsLayout = await npmSrcTreeHasJsFile(cwd, ignorePaths)
404
+
405
+ await checkNpmPackageJson(useSrcJsLayout, pass, fail, cwd)
406
+
407
+ if (!useSrcJsLayout) {
408
+ await checkEmitTypesConfig(pass, fail, cwd)
409
+ }
410
+
411
+ const layoutLabel = useSrcJsLayout ? 'layout src' : 'tsconfig emit-types'
412
+ const hk = await readHkConfig(cwd)
413
+ if (hk) {
414
+ pass(`${hk.path} існує`)
415
+ const missing = useSrcJsLayout ? missingHkSrcLayoutFragments(hk.text) : missingHkEmitTypesConfigFragments(hk.text)
416
+ if (missing.length === 0) {
417
+ pass(`${hk.path}: pre-commit містить очікуваний виклик tsc (${layoutLabel})`)
418
+ } else {
419
+ fail(`${hk.path}: онови pre-commit крок (npm-module.mdc); не знайдено: ${missing.join(', ')}`)
420
+ }
421
+
422
+ if (DEPRECATED_CHECK_CHANGELOG_RE.test(hk.text)) {
423
+ fail(
424
+ `${hk.path}: крок містить застарілий виклик "check changelog" — команду \`check\` прибрано в v14 ` +
425
+ '(уніфікована поверхня `lint`). Заміни на "npx @7n/rules lint changelog" (npm-module.mdc)'
426
+ )
427
+ } else {
428
+ const missingChangelogStep = missingHkNpmChangelogFragments(hk.text)
429
+ if (missingChangelogStep.length === 0) {
430
+ pass(`${hk.path}: pre-commit містить крок npm-changelog (npm-module.mdc)`)
431
+ } else {
432
+ fail(`${hk.path}: онови крок npm-changelog (npm-module.mdc); не знайдено: ${missingChangelogStep.join(', ')}`)
433
+ }
434
+ }
435
+ } else {
436
+ fail('Очікується hk.pkl або .config/hk.pkl з pre-commit і tsc (npm-module.mdc)')
437
+ }
438
+
439
+ if (existsSync(join(cwd, '.github/workflows'))) {
440
+ pass('.github/workflows/ існує')
441
+ } else {
442
+ fail('.github/workflows/ не існує')
443
+ }
444
+
445
+ await checkPublishWorkflow(pass, fail, cwd)
446
+
447
+ return reporter.result()
448
+ }