@nitra/cursor 12.6.1 → 12.8.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 (372) hide show
  1. package/.claude-template/settings.template.json +1 -1
  2. package/.pi-template/extensions/n-cursor-adr/docs/index.md +2 -2
  3. package/CHANGELOG.md +25 -5
  4. package/bin/docs/n-cursor.md +4 -20
  5. package/bin/n-cursor.js +8 -54
  6. package/docs/index.md +3 -3
  7. package/docs/stryker.config.md +20 -28
  8. package/lib/docs/index.md +5 -5
  9. package/lib/docs/llm.md +4 -4
  10. package/package.json +2 -2
  11. package/rules/abie/docs/fix.md +8 -8
  12. package/rules/abie/docs/index.md +4 -3
  13. package/rules/abie/docs/main.md +29 -0
  14. package/rules/abie/js/docs/index.md +6 -6
  15. package/rules/abie/lib/docs/index.md +9 -9
  16. package/rules/abie/{fix.mjs → main.mjs} +5 -3
  17. package/rules/adr/docs/index.md +1 -0
  18. package/rules/adr/docs/main.md +29 -0
  19. package/rules/adr/{fix.mjs → main.mjs} +5 -3
  20. package/rules/bun/docs/fix.md +5 -5
  21. package/rules/bun/docs/index.md +4 -3
  22. package/rules/bun/docs/main.md +30 -0
  23. package/rules/bun/js/docs/index.md +2 -2
  24. package/rules/bun/js/docs/layout.md +11 -36
  25. package/rules/bun/{fix.mjs → main.mjs} +5 -3
  26. package/rules/capacitor/docs/fix.md +10 -10
  27. package/rules/capacitor/docs/index.md +4 -3
  28. package/rules/capacitor/docs/main.md +29 -0
  29. package/rules/capacitor/js/docs/index.md +2 -2
  30. package/rules/capacitor/{fix.mjs → main.mjs} +5 -3
  31. package/rules/changelog/docs/fix.md +11 -11
  32. package/rules/changelog/docs/index.md +4 -3
  33. package/rules/changelog/docs/main.md +27 -0
  34. package/rules/changelog/js/docs/consistency.md +12 -12
  35. package/rules/changelog/js/docs/index.md +2 -2
  36. package/rules/changelog/lib/docs/index.md +2 -2
  37. package/rules/changelog/main.mjs +20 -0
  38. package/rules/ci4/docs/fix.md +4 -4
  39. package/rules/ci4/docs/index.md +4 -3
  40. package/rules/ci4/docs/main.md +30 -0
  41. package/rules/ci4/js/docs/index.md +2 -2
  42. package/rules/ci4/main.mjs +20 -0
  43. package/rules/doc-files/docs/index.md +4 -3
  44. package/rules/doc-files/docs/main.md +31 -0
  45. package/rules/doc-files/js/docgen-crc.mjs +2 -8
  46. package/rules/doc-files/js/docgen-extract.mjs +5 -3
  47. package/rules/doc-files/js/docgen-files-batch.mjs +63 -4
  48. package/rules/doc-files/js/docgen-gen.mjs +11 -3
  49. package/rules/doc-files/js/docgen-judge-measure.mjs +67 -18
  50. package/rules/doc-files/js/docgen-judge.mjs +8 -1
  51. package/rules/doc-files/js/docgen-scan.mjs +99 -11
  52. package/rules/doc-files/js/docs/docgen-crc.md +25 -14
  53. package/rules/doc-files/js/docs/docgen-extract.md +15 -13
  54. package/rules/doc-files/js/docs/docgen-files-batch.md +15 -15
  55. package/rules/doc-files/js/docs/docgen-gen.md +15 -26
  56. package/rules/doc-files/js/docs/docgen-judge-measure.md +14 -12
  57. package/rules/doc-files/js/docs/docgen-scan.md +34 -34
  58. package/rules/doc-files/js/docs/index.md +16 -15
  59. package/rules/doc-files/js/docs/run-lint.md +27 -0
  60. package/rules/doc-files/{lint/lint.mjs → js/run-lint.mjs} +23 -9
  61. package/rules/doc-files/{js/lint.mjs → main.mjs} +60 -10
  62. package/rules/docker/docs/fix.md +6 -6
  63. package/rules/docker/docs/index.md +4 -3
  64. package/rules/docker/docs/main.md +28 -0
  65. package/rules/docker/js/docs/index.md +2 -2
  66. package/rules/docker/js/docs/lint.md +26 -54
  67. package/rules/docker/js/lint.mjs +11 -0
  68. package/rules/docker/lib/docker-hadolint.mjs +1 -1
  69. package/rules/docker/lib/docs/docker-hadolint.md +16 -173
  70. package/rules/docker/lib/docs/index.md +5 -5
  71. package/rules/docker/main.mjs +20 -0
  72. package/rules/efes/docs/fix.md +8 -8
  73. package/rules/efes/docs/index.md +4 -3
  74. package/rules/efes/docs/main.md +29 -0
  75. package/rules/efes/main.mjs +20 -0
  76. package/rules/feedback/docs/fix.md +5 -5
  77. package/rules/feedback/docs/index.md +4 -3
  78. package/rules/feedback/docs/main.md +30 -0
  79. package/rules/feedback/main.mjs +20 -0
  80. package/rules/ga/docs/fix.md +5 -5
  81. package/rules/ga/docs/index.md +4 -3
  82. package/rules/ga/docs/main.md +29 -0
  83. package/rules/ga/js/docs/index.md +3 -3
  84. package/rules/ga/{lint/lint.mjs → main.mjs} +36 -10
  85. package/rules/graphql/docs/fix.md +8 -8
  86. package/rules/graphql/docs/index.md +4 -3
  87. package/rules/graphql/docs/main.md +36 -0
  88. package/rules/graphql/js/docs/index.md +2 -2
  89. package/rules/graphql/lib/docs/index.md +2 -2
  90. package/rules/graphql/main.mjs +20 -0
  91. package/rules/hasura/docs/fix.md +11 -11
  92. package/rules/hasura/docs/index.md +4 -3
  93. package/rules/hasura/docs/main.md +30 -0
  94. package/rules/hasura/js/docs/index.md +2 -2
  95. package/rules/hasura/main.mjs +20 -0
  96. package/rules/image-avif/docs/fix.md +3 -3
  97. package/rules/image-avif/docs/index.md +4 -3
  98. package/rules/image-avif/docs/main.md +30 -0
  99. package/rules/image-avif/js/docs/avif_generation.md +20 -233
  100. package/rules/image-avif/js/docs/index.md +2 -2
  101. package/rules/image-avif/main.mjs +20 -0
  102. package/rules/image-compress/docs/fix.md +2 -2
  103. package/rules/image-compress/docs/index.md +4 -3
  104. package/rules/image-compress/docs/main.md +29 -0
  105. package/rules/image-compress/js/docs/index.md +3 -3
  106. package/rules/image-compress/js/docs/package_setup.md +12 -11
  107. package/rules/image-compress/{js/lint.mjs → main.mjs} +21 -5
  108. package/rules/js-bun-db/docs/fix.md +5 -5
  109. package/rules/js-bun-db/docs/index.md +4 -3
  110. package/rules/js-bun-db/docs/main.md +30 -0
  111. package/rules/js-bun-db/js/docs/index.md +2 -2
  112. package/rules/js-bun-db/lib/docs/index.md +2 -2
  113. package/rules/js-bun-db/main.mjs +20 -0
  114. package/rules/js-bun-redis/docs/fix.md +6 -6
  115. package/rules/js-bun-redis/docs/index.md +4 -3
  116. package/rules/js-bun-redis/docs/main.md +29 -0
  117. package/rules/js-bun-redis/js/docs/index.md +2 -2
  118. package/rules/js-bun-redis/lib/docs/index.md +2 -2
  119. package/rules/js-bun-redis/main.mjs +20 -0
  120. package/rules/js-lint/docs/fix.md +9 -9
  121. package/rules/js-lint/docs/index.md +4 -3
  122. package/rules/js-lint/docs/main.md +29 -0
  123. package/rules/js-lint/js/check.mjs +268 -0
  124. package/rules/js-lint/js/docs/check.md +39 -0
  125. package/rules/js-lint/js/docs/index.md +4 -4
  126. package/rules/js-lint/js/docs/tooling.md +12 -32
  127. package/rules/js-lint/js/tooling.mjs +1 -265
  128. package/rules/js-lint/{js/lint.mjs → main.mjs} +19 -2
  129. package/rules/js-lint-ci/docs/fix.md +3 -3
  130. package/rules/js-lint-ci/docs/index.md +4 -3
  131. package/rules/js-lint-ci/docs/main.md +27 -0
  132. package/rules/js-lint-ci/js/docs/index.md +2 -2
  133. package/rules/js-lint-ci/main.mjs +33 -0
  134. package/rules/js-mssql/docs/fix.md +5 -5
  135. package/rules/js-mssql/docs/index.md +4 -3
  136. package/rules/js-mssql/docs/main.md +30 -0
  137. package/rules/js-mssql/js/docs/index.md +2 -2
  138. package/rules/js-mssql/lib/docs/index.md +2 -2
  139. package/rules/js-mssql/main.mjs +20 -0
  140. package/rules/js-run/docs/fix.md +8 -8
  141. package/rules/js-run/docs/index.md +4 -3
  142. package/rules/js-run/docs/main.md +30 -0
  143. package/rules/js-run/js/docs/index.md +2 -2
  144. package/rules/js-run/lib/docs/index.md +7 -7
  145. package/rules/js-run/main.mjs +20 -0
  146. package/rules/k8s/docs/fix.md +4 -4
  147. package/rules/k8s/docs/index.md +4 -3
  148. package/rules/k8s/docs/main.md +40 -0
  149. package/rules/k8s/js/docs/index.md +12 -0
  150. package/rules/k8s/{lint/lint.mjs → main.mjs} +32 -10
  151. package/rules/nginx-default-tpl/docs/fix.md +7 -7
  152. package/rules/nginx-default-tpl/docs/index.md +4 -3
  153. package/rules/nginx-default-tpl/docs/main.md +30 -0
  154. package/rules/nginx-default-tpl/js/docs/index.md +2 -2
  155. package/rules/nginx-default-tpl/js/docs/template.md +2 -2
  156. package/rules/nginx-default-tpl/main.mjs +20 -0
  157. package/rules/npm-module/docs/fix.md +8 -8
  158. package/rules/npm-module/docs/index.md +4 -3
  159. package/rules/npm-module/docs/main.md +29 -0
  160. package/rules/npm-module/js/docs/index.md +5 -5
  161. package/rules/npm-module/js/docs/rule_meta.md +17 -16
  162. package/rules/npm-module/js/header_doc_pointer.mjs +1 -3
  163. package/rules/npm-module/js/rule_meta.mjs +13 -3
  164. package/rules/npm-module/main.mjs +20 -0
  165. package/rules/php/docs/fix.md +6 -6
  166. package/rules/php/docs/index.md +4 -3
  167. package/rules/php/docs/main.md +33 -0
  168. package/rules/php/js/docs/index.md +3 -3
  169. package/rules/php/js/docs/tooling.md +10 -10
  170. package/rules/php/{lint/lint.mjs → main.mjs} +32 -6
  171. package/rules/python/docs/fix.md +11 -11
  172. package/rules/python/docs/index.md +4 -3
  173. package/rules/python/docs/main.md +31 -0
  174. package/rules/python/js/docs/index.md +3 -3
  175. package/rules/python/js/docs/tooling.md +17 -17
  176. package/rules/python/{lint/lint.mjs → main.mjs} +31 -6
  177. package/rules/rego/docs/fix.md +5 -5
  178. package/rules/rego/docs/index.md +4 -3
  179. package/rules/rego/docs/main.md +37 -0
  180. package/rules/rego/js/docs/index.md +3 -3
  181. package/rules/rego/{lint/lint.mjs → main.mjs} +27 -5
  182. package/rules/release/docs/index.md +5 -4
  183. package/rules/release/docs/main.md +29 -0
  184. package/rules/release/docs/release.md +0 -3
  185. package/rules/release/lib/docs/index.md +4 -4
  186. package/rules/release/release.mdc +10 -0
  187. package/rules/rust/docs/fix.md +4 -4
  188. package/rules/rust/docs/index.md +4 -3
  189. package/rules/rust/docs/main.md +27 -0
  190. package/rules/rust/js/docs/index.md +3 -3
  191. package/rules/rust/lib/docs/index.md +2 -2
  192. package/rules/rust/{js/lint.mjs → main.mjs} +27 -4
  193. package/rules/security/docs/fix.md +6 -6
  194. package/rules/security/docs/index.md +4 -3
  195. package/rules/security/docs/main.md +28 -0
  196. package/rules/security/js/docs/index.md +4 -4
  197. package/rules/security/main.mjs +45 -0
  198. package/rules/style-lint/docs/fix.md +3 -3
  199. package/rules/style-lint/docs/index.md +4 -3
  200. package/rules/style-lint/docs/main.md +29 -0
  201. package/rules/style-lint/js/docs/index.md +3 -3
  202. package/rules/style-lint/{js/lint.mjs → main.mjs} +19 -1
  203. package/rules/tauri/docs/fix.md +11 -11
  204. package/rules/tauri/docs/index.md +4 -3
  205. package/rules/tauri/docs/main.md +29 -0
  206. package/rules/tauri/js/docs/index.md +3 -3
  207. package/rules/tauri/main.mjs +20 -0
  208. package/rules/test/docs/fix.md +5 -5
  209. package/rules/test/docs/index.md +4 -3
  210. package/rules/test/docs/main.md +30 -0
  211. package/rules/test/js/data/stryker_config/docs/index.md +4 -4
  212. package/rules/test/js/data/vitest_config/docs/index.md +2 -2
  213. package/rules/test/js/docs/index.md +7 -7
  214. package/rules/test/main.mjs +20 -0
  215. package/rules/text/docs/fix.md +11 -11
  216. package/rules/text/docs/index.md +4 -3
  217. package/rules/text/docs/main.md +29 -0
  218. package/rules/text/{lint → js}/cspell-fix.mjs +7 -2
  219. package/rules/text/js/docs/cspell-fix.md +30 -0
  220. package/rules/text/js/docs/formatting.md +12 -45
  221. package/rules/text/js/docs/index.md +8 -4
  222. package/rules/text/js/docs/run-dotenv-linter.md +31 -0
  223. package/rules/text/js/docs/run-shellcheck.md +28 -0
  224. package/rules/text/js/docs/run-v8r.md +29 -0
  225. package/rules/text/{lint/lint.mjs → main.mjs} +41 -10
  226. package/rules/tool-surface/docs/index.md +4 -3
  227. package/rules/tool-surface/docs/main.md +29 -0
  228. package/rules/tool-surface/main.mjs +20 -0
  229. package/rules/tool-surface/meta.json +6 -1
  230. package/rules/vue/docs/fix.md +6 -6
  231. package/rules/vue/docs/index.md +4 -3
  232. package/rules/vue/docs/main.md +29 -0
  233. package/rules/vue/js/docs/index.md +2 -2
  234. package/rules/vue/lib/docs/index.md +2 -2
  235. package/rules/vue/main.mjs +20 -0
  236. package/rules/worktree/docs/fix.md +11 -11
  237. package/rules/worktree/docs/index.md +4 -3
  238. package/rules/worktree/docs/main.md +28 -0
  239. package/rules/worktree/main.mjs +20 -0
  240. package/scripts/coverage-classify/docs/index.md +6 -6
  241. package/scripts/dispatcher/docs/index.md +2 -2
  242. package/scripts/docs/index.md +16 -15
  243. package/scripts/docs/post-tool-use-check.md +29 -0
  244. package/scripts/docs/sync-claude-config.md +64 -92
  245. package/scripts/lib/adr/docs/normalize-cli.md +0 -3
  246. package/scripts/lib/adr/docs/normalize-pipeline.md +0 -3
  247. package/scripts/lib/docs/gha-workflow.md +25 -317
  248. package/scripts/lib/docs/index.md +36 -35
  249. package/scripts/lib/docs/list-project-rules-mdc.md +5 -4
  250. package/scripts/lib/docs/list-rule-ids.md +15 -148
  251. package/scripts/lib/docs/read-n-cursor-config-lite.md +12 -16
  252. package/scripts/lib/docs/run-lint-step.md +13 -13
  253. package/scripts/lib/docs/run-lint.md +30 -0
  254. package/scripts/lib/docs/run-rule-cli.md +14 -10
  255. package/scripts/lib/docs/run-standard-lint.md +29 -10
  256. package/scripts/lib/docs/run-standard-rule.md +12 -11
  257. package/scripts/lib/docs/timing-summary.md +11 -12
  258. package/scripts/lib/docs/worktree-notice.md +0 -3
  259. package/scripts/lib/fix/analyze-escalation.mjs +4 -1
  260. package/scripts/lib/fix/docs/index.md +11 -10
  261. package/scripts/lib/fix/docs/orchestrator.md +23 -18
  262. package/scripts/lib/fix/docs/run-conformance-check.md +33 -0
  263. package/scripts/lib/fix/docs/run-fix-check.md +3 -3
  264. package/scripts/lib/fix/docs/t0.md +10 -9
  265. package/scripts/lib/fix/orchestrator.mjs +31 -8
  266. package/scripts/lib/fix/{run-fix-check.mjs → run-conformance-check.mjs} +13 -13
  267. package/scripts/lib/fix/t0.mjs +6 -3
  268. package/scripts/lib/list-project-rules-mdc.mjs +1 -1
  269. package/scripts/lib/list-rule-ids.mjs +12 -3
  270. package/scripts/lib/read-n-cursor-config-lite.mjs +2 -2
  271. package/{rules/lint/js/orchestrate.mjs → scripts/lib/run-lint.mjs} +42 -22
  272. package/scripts/lib/run-rule-cli.mjs +4 -4
  273. package/scripts/lib/run-standard-lint.mjs +19 -6
  274. package/scripts/lib/run-standard-rule.mjs +4 -4
  275. package/scripts/lib/timing-summary.mjs +1 -1
  276. package/scripts/{post-tool-use-fix.mjs → post-tool-use-check.mjs} +9 -9
  277. package/scripts/sync-claude-config.mjs +2 -2
  278. package/scripts/utils/docs/index.md +14 -14
  279. package/skills/doc-aggregate/js/docs/index.md +3 -3
  280. package/skills/doc-files/.changes/260612-0002.md +1 -0
  281. package/skills/doc-files/.changes/260612-0006.md +1 -0
  282. package/skills/doc-files/.changes/260612-0008.md +1 -0
  283. package/skills/doc-files/.changes/260612-0012.md +1 -0
  284. package/skills/doc-files/.changes/260612-0031.md +1 -0
  285. package/skills/doc-files/.changes/260612-0036.md +1 -0
  286. package/skills/doc-files/.changes/260612-0114.md +1 -0
  287. package/skills/start-check/js/docs/index.md +2 -2
  288. package/skills/taze/js/docs/index.md +2 -2
  289. package/types/bin/n-cursor.d.ts +1 -1
  290. package/rules/changelog/fix.mjs +0 -18
  291. package/rules/ci4/fix.mjs +0 -18
  292. package/rules/doc-files/fix.mjs +0 -19
  293. package/rules/doc-files/js/docs/lint.md +0 -34
  294. package/rules/doc-files/lint/docs/index.md +0 -11
  295. package/rules/doc-files/lint/docs/lint.md +0 -35
  296. package/rules/docker/fix.mjs +0 -18
  297. package/rules/docker/lint/docs/index.md +0 -11
  298. package/rules/docker/lint/docs/lint.md +0 -200
  299. package/rules/docker/lint/lint.mjs +0 -95
  300. package/rules/efes/fix.mjs +0 -18
  301. package/rules/feedback/fix.mjs +0 -18
  302. package/rules/ga/fix.mjs +0 -18
  303. package/rules/ga/js/docs/lint.md +0 -20
  304. package/rules/ga/js/lint.mjs +0 -12
  305. package/rules/ga/lint/docs/index.md +0 -11
  306. package/rules/ga/lint/docs/lint.md +0 -31
  307. package/rules/graphql/fix.mjs +0 -18
  308. package/rules/hasura/fix.mjs +0 -18
  309. package/rules/image-avif/fix.mjs +0 -18
  310. package/rules/image-compress/fix.mjs +0 -18
  311. package/rules/image-compress/js/docs/lint.md +0 -24
  312. package/rules/js-bun-db/fix.mjs +0 -18
  313. package/rules/js-bun-redis/fix.mjs +0 -18
  314. package/rules/js-lint/fix.mjs +0 -18
  315. package/rules/js-lint/js/docs/lint.md +0 -32
  316. package/rules/js-lint-ci/fix.mjs +0 -18
  317. package/rules/js-lint-ci/js/docs/lint.md +0 -22
  318. package/rules/js-lint-ci/js/lint.mjs +0 -15
  319. package/rules/js-mssql/fix.mjs +0 -18
  320. package/rules/js-run/fix.mjs +0 -18
  321. package/rules/k8s/fix.mjs +0 -18
  322. package/rules/k8s/js/lint.mjs +0 -14
  323. package/rules/k8s/lint/docs/index.md +0 -11
  324. package/rules/k8s/lint/docs/lint.md +0 -413
  325. package/rules/lint/docs/fix.md +0 -25
  326. package/rules/lint/docs/index.md +0 -11
  327. package/rules/lint/fix.mjs +0 -18
  328. package/rules/lint/js/docs/index.md +0 -11
  329. package/rules/lint/js/docs/orchestrate.md +0 -31
  330. package/rules/lint/meta.json +0 -1
  331. package/rules/nginx-default-tpl/fix.mjs +0 -18
  332. package/rules/npm-module/fix.mjs +0 -18
  333. package/rules/php/fix.mjs +0 -18
  334. package/rules/php/js/docs/lint.md +0 -20
  335. package/rules/php/js/lint.mjs +0 -15
  336. package/rules/php/lint/docs/index.md +0 -11
  337. package/rules/php/lint/docs/lint.md +0 -219
  338. package/rules/python/fix.mjs +0 -18
  339. package/rules/python/js/docs/lint.md +0 -21
  340. package/rules/python/js/lint.mjs +0 -14
  341. package/rules/python/lint/docs/index.md +0 -11
  342. package/rules/python/lint/docs/lint.md +0 -29
  343. package/rules/rego/fix.mjs +0 -18
  344. package/rules/rego/js/docs/lint.md +0 -21
  345. package/rules/rego/js/lint.mjs +0 -12
  346. package/rules/rego/lint/docs/index.md +0 -11
  347. package/rules/rego/lint/docs/lint.md +0 -208
  348. package/rules/rust/fix.mjs +0 -18
  349. package/rules/rust/js/docs/lint.md +0 -21
  350. package/rules/security/fix.mjs +0 -18
  351. package/rules/security/js/docs/lint.md +0 -175
  352. package/rules/security/js/lint.mjs +0 -26
  353. package/rules/style-lint/fix.mjs +0 -18
  354. package/rules/style-lint/js/docs/lint.md +0 -31
  355. package/rules/tauri/fix.mjs +0 -18
  356. package/rules/test/fix.mjs +0 -18
  357. package/rules/text/fix.mjs +0 -18
  358. package/rules/text/js/docs/lint.md +0 -23
  359. package/rules/text/js/lint.mjs +0 -15
  360. package/rules/text/lint/docs/cspell-fix.md +0 -32
  361. package/rules/text/lint/docs/index.md +0 -15
  362. package/rules/text/lint/docs/lint.md +0 -36
  363. package/rules/text/lint/docs/run-dotenv-linter.md +0 -161
  364. package/rules/text/lint/docs/run-shellcheck.md +0 -216
  365. package/rules/text/lint/docs/run-v8r.md +0 -201
  366. package/rules/tool-surface/fix.mjs +0 -18
  367. package/rules/vue/fix.mjs +0 -18
  368. package/rules/worktree/fix.mjs +0 -18
  369. /package/rules/release/{fix.mjs → main.mjs} +0 -0
  370. /package/rules/text/{lint → js}/run-dotenv-linter.mjs +0 -0
  371. /package/rules/text/{lint → js}/run-shellcheck.mjs +0 -0
  372. /package/rules/text/{lint → js}/run-v8r.mjs +0 -0
@@ -0,0 +1,268 @@
1
+ /** @see ./docs/tooling.md */
2
+ import { existsSync } from 'node:fs'
3
+ import { copyFile, readFile } from 'node:fs/promises'
4
+ import { join } from 'node:path'
5
+
6
+ import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
7
+
8
+ import { KNIP_CANONICAL_JSON_PATH, OXLINT_CANONICAL_JSON_PATH, verifyOxlintRcAgainstCanonical } from './tooling.mjs'
9
+
10
+ const NON_DIGITS_RE = /\D+/u
11
+
12
+ /**
13
+ * Перевіряє ESLint flat config файл.
14
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
15
+ * @param {(msg: string) => void} failFn callback при помилці
16
+ * @param {string} cwd корінь репозиторію
17
+ */
18
+ async function checkEslintConfig(passFn, failFn, cwd) {
19
+ let eslintPath
20
+ if (existsSync(join(cwd, 'eslint.config.js'))) {
21
+ eslintPath = 'eslint.config.js'
22
+ passFn('eslint.config.js існує')
23
+ } else if (existsSync(join(cwd, 'eslint.config.mjs'))) {
24
+ eslintPath = 'eslint.config.mjs'
25
+ passFn('eslint.config.mjs існує')
26
+ } else {
27
+ failFn('Відсутній eslint.config.js або eslint.config.mjs — flat config з getConfig (js-lint.mdc)')
28
+ return
29
+ }
30
+ const eslintRaw = await readFile(join(cwd, eslintPath), 'utf8')
31
+ const checks = [
32
+ {
33
+ needle: 'getConfig',
34
+ ok: `${eslintPath}: містить getConfig`,
35
+ err: `${eslintPath}: потрібен виклик getConfig (js-lint.mdc)`
36
+ },
37
+ {
38
+ needle: '@nitra/eslint-config',
39
+ ok: `${eslintPath}: імпорт @nitra/eslint-config`,
40
+ err: `${eslintPath}: імпортуй getConfig з @nitra/eslint-config`
41
+ },
42
+ {
43
+ needle: '**/auto-imports.d.ts',
44
+ ok: `${eslintPath}: ignores містить **/auto-imports.d.ts`,
45
+ err: `${eslintPath}: додай у ignores запис **/auto-imports.d.ts (js-lint.mdc)`
46
+ }
47
+ ]
48
+ for (const { needle, ok, err } of checks) {
49
+ if (eslintRaw.includes(needle)) {
50
+ passFn(ok)
51
+ } else {
52
+ failFn(err)
53
+ }
54
+ }
55
+ }
56
+
57
+ // Перевірки `prettier` / `@nitra/prettier-config` у залежностях (text.mdc) і
58
+ // `@nitra/eslint-config ≥ 3.10.0` тепер у Rego: відповідно
59
+ // `npm/policy/text/package_json/` і `npm/policy/js_lint/package_json/`. Тут
60
+ // лишилася лише workspace-ітерація для `type: "module"` і engines, бо js_lint
61
+ // Rego запускається лише на кореневому `package.json`.
62
+
63
+ /**
64
+ * Перевіряє, що package.json має `"type": "module"`.
65
+ * @param {string} label шлях або назва пакета для повідомлень
66
+ * @param {{ type?: string }} pkg parsed package.json
67
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
68
+ * @param {(msg: string) => void} failFn callback при помилці
69
+ */
70
+ function checkPackageJsonTypeModule(label, pkg, passFn, failFn) {
71
+ if (pkg.type === 'module') {
72
+ passFn(`${label}: "type": "module"`)
73
+ } else {
74
+ failFn(`${label}: має містити "type": "module" (js-lint.mdc)`)
75
+ }
76
+ }
77
+
78
+ /**
79
+ * `"type": "module"`, `engines.node >= 24` і `engines.bun >= 1.3` у кожному workspace `package.json`.
80
+ * @param {unknown[]} workspaces поле workspaces з package.json
81
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
82
+ * @param {(msg: string) => void} failFn callback при помилці
83
+ * @param {string} cwd корінь репозиторію
84
+ */
85
+ async function checkWorkspacePackages(workspaces, passFn, failFn, cwd) {
86
+ for (const ws of workspaces) {
87
+ const wsPkgRel = `${ws}/package.json`
88
+ const wsPkgAbs = join(cwd, wsPkgRel)
89
+ if (existsSync(wsPkgAbs)) {
90
+ const wsPkg = JSON.parse(await readFile(wsPkgAbs, 'utf8'))
91
+ checkPackageJsonTypeModule(wsPkgRel, wsPkg, passFn, failFn)
92
+ checkEnginesNode(wsPkgRel, wsPkg, passFn, failFn)
93
+ checkEnginesBun(wsPkgRel, wsPkg, passFn, failFn)
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * engines.node >= 24.
100
+ * @param {string} label шлях або назва пакета для повідомлень
101
+ * @param {{ engines?: { node?: string } }} pkg розпарсений package.json
102
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
103
+ * @param {(msg: string) => void} failFn callback при помилці
104
+ */
105
+ function checkEnginesNode(label, pkg, passFn, failFn) {
106
+ const nodeEngine = pkg.engines?.node
107
+ if (nodeEngine) {
108
+ const firstNumeric = String(nodeEngine).split(NON_DIGITS_RE).find(Boolean)
109
+ if (firstNumeric && Number(firstNumeric) >= 24) {
110
+ passFn(`${label}: engines.node "${nodeEngine}"`)
111
+ } else {
112
+ failFn(`${label}: engines.node "${nodeEngine}" — має бути >=24`)
113
+ }
114
+ } else {
115
+ failFn(`${label} не містить engines.node — додай: "engines": { "node": ">=24" }`)
116
+ }
117
+ }
118
+
119
+ /**
120
+ * engines.bun >= 1.3.
121
+ * @param {string} label шлях або назва пакета для повідомлень
122
+ * @param {{ engines?: { bun?: string } }} pkg розпарсений package.json
123
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
124
+ * @param {(msg: string) => void} failFn callback при помилці
125
+ */
126
+ function checkEnginesBun(label, pkg, passFn, failFn) {
127
+ const bunEngine = pkg.engines?.bun
128
+ if (bunEngine) {
129
+ const [major, minor] = String(bunEngine).split(NON_DIGITS_RE).filter(Boolean).map(Number)
130
+ if (Number.isFinite(major) && Number.isFinite(minor) && (major > 1 || (major === 1 && minor >= 3))) {
131
+ passFn(`${label}: engines.bun "${bunEngine}"`)
132
+ } else {
133
+ failFn(`${label}: engines.bun "${bunEngine}" — має бути >=1.3`)
134
+ }
135
+ } else {
136
+ failFn(`${label} не містить engines.bun — додай: "engines": { "bun": ">=1.3" }`)
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Workspace-ітерація: для кожного workspace `package.json` перевіряємо
142
+ * `type: "module"` і `engines.{node,bun}`. Кореневий `package.json` ці поля
143
+ * валідує `npm/policy/js_lint/package_json/`; lint-js скрипт і `@nitra/eslint-config`
144
+ * — теж у Rego.
145
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
146
+ * @param {(msg: string) => void} failFn callback при помилці
147
+ * @param {string} cwd корінь репозиторію
148
+ */
149
+ async function checkPackageJsonJsLint(passFn, failFn, cwd) {
150
+ const pkgPath = join(cwd, 'package.json')
151
+ if (!existsSync(pkgPath)) return
152
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
153
+ const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : []
154
+ await checkWorkspacePackages(workspaces, passFn, failFn, cwd)
155
+ }
156
+
157
+ /**
158
+ * Перевіряє .oxlintrc.json.
159
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
160
+ * @param {(msg: string) => void} failFn callback при помилці
161
+ * @param {string} cwd корінь репозиторію
162
+ */
163
+ async function checkOxlintRc(passFn, failFn, cwd) {
164
+ const oxPath = join(cwd, '.oxlintrc.json')
165
+ if (!existsSync(oxPath)) {
166
+ failFn('.oxlintrc.json не існує — додай конфіг oxlint (js-lint.mdc)')
167
+ return
168
+ }
169
+ let oxCfg
170
+ try {
171
+ oxCfg = JSON.parse(await readFile(oxPath, 'utf8'))
172
+ } catch {
173
+ failFn('.oxlintrc.json не є валідним JSON')
174
+ return
175
+ }
176
+ passFn('.oxlintrc.json існує')
177
+ let canonical
178
+ try {
179
+ canonical = JSON.parse(await readFile(OXLINT_CANONICAL_JSON_PATH, 'utf8'))
180
+ } catch {
181
+ failFn('внутрішня помилка: не вдалося прочитати канон oxlint з пакета @nitra/cursor')
182
+ return
183
+ }
184
+ const oxV = verifyOxlintRcAgainstCanonical(oxCfg, canonical)
185
+ if (oxV.ok) {
186
+ passFn('.oxlintrc.json збігається з каноном oxlint (@nitra/cursor)')
187
+ } else {
188
+ for (const msg of oxV.failures) {
189
+ failFn(msg)
190
+ }
191
+ }
192
+ }
193
+
194
+ /**
195
+ * FS-existence для `lint-js.yml` + cross-file перевірка, що `lint.yml` (якщо існує)
196
+ * не дублює лінт JS-кроки. Структуру `lint-js.yml` (`actions/checkout@v6`,
197
+ * `persist-credentials: false`, `setup-bun-deps`, `bunx oxlint/eslint/jscpd .`,
198
+ * заборона `--fix` у CI) валідує `npm/policy/js_lint/lint_js_yml/`.
199
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
200
+ * @param {(msg: string) => void} failFn callback при помилці
201
+ * @param {string} cwd корінь репозиторію
202
+ */
203
+ async function checkLintJsWorkflows(passFn, failFn, cwd) {
204
+ if (existsSync(join(cwd, '.github/workflows/lint-js.yml'))) {
205
+ passFn('.github/workflows/lint-js.yml є (структуру перевіряє npx @nitra/cursor fix → js_lint.lint_js_yml)')
206
+ } else {
207
+ failFn('.github/workflows/lint-js.yml не існує — створи його (js-lint.mdc)')
208
+ }
209
+
210
+ const lintYmlPath = join(cwd, '.github/workflows/lint.yml')
211
+ if (existsSync(lintYmlPath)) {
212
+ const lintYml = await readFile(lintYmlPath, 'utf8')
213
+ if (lintYml.includes('bunx oxlint') && lintYml.includes('bunx eslint') && lintYml.includes('jscpd')) {
214
+ failFn('.github/workflows/lint.yml дублює кроки lint-js.yml — залиш один workflow на лінт JS (js-lint.mdc)')
215
+ } else {
216
+ passFn('.github/workflows/lint.yml не дублює oxlint/eslint/jscpd з lint-js.yml')
217
+ }
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Перевіряє наявність `knip.json` у корені проєкту. Якщо файл відсутній —
223
+ * копіює канонічний `knip-canonical.json` з пакета `@nitra/cursor` як стартовий
224
+ * baseline; зміст подальших модифікацій локально не валідується (`entry` /
225
+ * `project` / `ignore` / `ignoreDependencies` / `ignoreBinaries` дозволені
226
+ * будь-які; це side effect — описано у js-lint.mdc).
227
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
228
+ * @param {(msg: string) => void} failFn callback при помилці
229
+ * @param {string} cwd корінь репозиторію
230
+ */
231
+ async function checkKnipConfig(passFn, failFn, cwd) {
232
+ const knipPath = join(cwd, 'knip.json')
233
+ if (existsSync(knipPath)) {
234
+ passFn('knip.json існує')
235
+ return
236
+ }
237
+ if (!existsSync(KNIP_CANONICAL_JSON_PATH)) {
238
+ failFn(
239
+ `knip.json відсутній, і канонічний шаблон у пакеті не знайдено (${KNIP_CANONICAL_JSON_PATH}) — ` +
240
+ 'перевстанови @nitra/cursor'
241
+ )
242
+ return
243
+ }
244
+ await copyFile(KNIP_CANONICAL_JSON_PATH, knipPath)
245
+ passFn('knip.json створено з канонічного npm/rules/js-lint/js/data/tooling/knip-canonical.json (js-lint.mdc)')
246
+ }
247
+
248
+ /**
249
+ * Перевіряє відповідність проєкту правилам js-lint.mdc
250
+ * @param {string} [cwd] корінь репозиторію
251
+ * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
252
+ */
253
+ export async function check(cwd = process.cwd()) {
254
+ const reporter = createCheckReporter()
255
+ const { pass, fail } = reporter
256
+
257
+ await checkEslintConfig(pass, fail, cwd)
258
+ await checkPackageJsonJsLint(pass, fail, cwd)
259
+ await checkOxlintRc(pass, fail, cwd)
260
+ await checkLintJsWorkflows(pass, fail, cwd)
261
+ await checkKnipConfig(pass, fail, cwd)
262
+
263
+ for (const dup of ['.eslintrc', '.eslintrc.js', '.eslintrc.json', '.eslintrc.yml']) {
264
+ if (existsSync(join(cwd, dup))) fail(`Знайдено застарілий конфіг ESLint: ${dup} — видали, використовуй flat config`)
265
+ }
266
+
267
+ return reporter.getExitCode()
268
+ }
@@ -0,0 +1,39 @@
1
+ ---
2
+ type: JS Module
3
+ title: check.mjs
4
+ resource: npm/rules/js-lint/js/check.mjs
5
+ docgen:
6
+ crc: f61768f2
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
9
+ ---
10
+
11
+ ## Огляд
12
+
13
+ Модуль виконує перевірку конфігураційних файлів проєкту, забезпечуючи їхню відповідність встановленим стандартам. Він читає та аналізує конфігураційні файли, включаючи `package.json`, `.oxlintrc.json`, `knip.json`, `knip-canonical.json` та `.eslintrc.json`. Перевірка ґрунтується на логіці, визначеній у (js-lint.mdc) та (text.mdc). Функціонал реалізований у режимі лише читання, що означає відсутність змін у файловій системі чи базах даних. При виявленні проблем, система перехоплює помилки, працюючи у режимі fail-safe. Свідомо ігноруються шляхи `.github` та `.git`.
14
+
15
+ ## Поведінка
16
+
17
+ 1. Викликає `check` для початку перевірки.
18
+ 2. Перевіряє наявність та відповідність конфігурації ESLint.
19
+ 3. Перевіряє конфігурацію `package.json` для всіх робочих просторів:
20
+ а. Перевіряє, чи має `package.json` `"type": "module"`.
21
+ б. Перевіряє, чи містить `package.json` `engines.node` з версією не менше 24.
22
+ в. Перевіряє, чи містить `package.json` `engines.bun` з версією не менше 1.3.
23
+ 4. Перевіряє конфігураційний файл `.oxlintrc.json` на відповідність канонічному шаблону.
24
+ 5. Перевіряє файли робочих процесів у `.github/workflows/`:
25
+ а. Перевіряє наявність `lint-js.yml`.
26
+ б. Перевіряє, чи не дублює `lint.yml` кроки лінтінгу JS.
27
+ 6. Перевіряє наявність файлу `knip.json` у корені проєкту. Якщо він відсутній, копіює канонічний шаблон.
28
+ 7. Перевіряє наявність застарілих конфігурацій ESLint (`.eslintrc`, `.eslintrc.js`, `.eslintrc.json`, `.eslintrc.yml`) та повідомляє про їхнє існування.
29
+ 8. Повертає код виходу, що відображає загальний статус перевірки.
30
+
31
+ ## Публічний API
32
+
33
+ check — забезпечує відповідність проєкту вимогам, описаним у js-lint.mdc.
34
+
35
+ ## Гарантії поведінки
36
+
37
+ - Read-only: не виконує операцій запису (ФС/БД).
38
+ - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
39
+ - Свідомо пропускає шляхи: `.github`, `.git`.
@@ -6,9 +6,9 @@ resource: npm/rules/js-lint/js/
6
6
 
7
7
  # npm/rules/js-lint/js
8
8
 
9
- | Файл | Тип |
10
- |---|---|
9
+ | Файл | Тип |
10
+ | ------------------------------------- | --------- |
11
+ | [check.mjs](check.md) | JS Module |
11
12
  | [lint-findings.mjs](lint-findings.md) | JS Module |
12
- | [lint.mjs](lint.md) | JS Module |
13
- | [tooling.mjs](tooling.md) | JS Module |
13
+ | [tooling.mjs](tooling.md) | JS Module |
14
14
  | [utils_imports.mjs](utils_imports.md) | JS Module |
@@ -3,47 +3,27 @@ type: JS Module
3
3
  title: tooling.mjs
4
4
  resource: npm/rules/js-lint/js/tooling.mjs
5
5
  docgen:
6
- crc: e847996e
7
- score: 80
6
+ crc: 7ead48ee
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 95
8
9
  ---
9
10
 
10
- OXLINT_CANONICAL_JSON_PATH
11
- Повертає шлях до канонічного JSON-файлу oxlint у пакету
11
+ ## Огляд
12
12
 
13
- KNIP_CANONICAL_JSON_PATH
14
- Повертає шлях до канонічного JSON-файлу knip у пакету
15
-
16
- verifyOxlintRcAgainstCanonical
17
- Звіряє блок rules з `.oxlintrc.json` проти канону пакета @nitra/cursor
18
-
19
- check
20
- Перевіряє конфігурацію проєкту відповідно до правил js-lint.mdc
13
+ Визначає шляхи до канонічних конфігураційних файлів для oxlint та knip за допомогою OXLINT_CANONICAL_JSON_PATH та KNIP_CANONICAL_JSON_PATH. Дозволяє перевіряти відповідність конфігураційного файлу .oxlintrc.json до канонічного файлу oxlint-canonical.json за допомогою verifyOxlintRcAgainstCanonical.
21
14
 
22
15
  ## Поведінка
23
16
 
24
- OXLINT_CANONICAL_JSON_PATH
25
- Шлях до канонічного oxlint JSON у цьому пакеті
26
-
27
- KNIP_CANONICAL_JSON_PATH
28
- Шлях до канонічного knip JSON у цьому пакеті
29
-
30
- verifyOxlintRcAgainstCanonical
31
- Звіряє блок rules з `.oxlintrc.json` проти канону пакета @nitra/cursor
32
-
33
- check
34
- Перевіряє конфігурацію проєкту відповідно до правил js-lint.mdc
17
+ OXLINT_CANONICAL_JSON_PATH — Вказує шлях до канонічного JSON-файлу oxlint у цьому пакеті.
18
+ KNIP_CANONICAL_JSON_PATH — Вказує шлях до канонічного JSON-файлу knip у цьому пакеті.
19
+ verifyOxlintRcAgainstCanonical — Перевіряє конфігураційний файл `.oxlintrc.json` на відповідність канонічному файлу oxlint-canonical.json, виявляючи відхилення у правилах та інших полях.
35
20
 
36
21
  ## Публічний API
37
22
 
38
- OXLINT_CANONICAL_JSON_PATH — Шлях до канонічного oxlint JSON у пакету (для перевірки та тестів)
39
- KNIP_CANONICAL_JSON_PATH — Шлях до канонічного knip JSON у пакету (копіюється у корінь проєкту-споживача, якщо відсутній)
40
- verifyOxlintRcAgainstCanonical — Порівнює `.oxlintrc.json` з каноном пакета `@nitra/cursor` (включає всі правила з канону та інші поля з `oxlint-canonical.json`). Дозволено додати лише ключі в `rules`; інші поля мають збігатися з каноном.
41
- check — Перевіряє відповідність проєкту правилам js-lint.mdc
23
+ OXLINT_CANONICAL_JSON_PATH — Вказує розташування стандартного конфігураційного файлу oxlint для валідації.
24
+ KNIP_CANONICAL_JSON_PATH — Вказує розташування стандартного конфігураційного файлу knip, який копіюється у кореневий каталог проєкту, якщо його там немає.
25
+ verifyOxlintRcAgainstCanonical — Порівнює конфігураційний файл `.oxlintrc.json` з канонічним файлом пакета, вимагаючи збігу всіх полів, крім додаткових ключів у секції `rules`.
42
26
 
43
27
  ## Гарантії поведінки
44
28
 
45
- - Read-only: файл не виконує операцій запису у файлову систему.
46
- - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
47
- - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
48
- - Свідомо пропускає шляхи: `.github`, `.git`.
49
- - Не звертається до мережі.
29
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -1,11 +1,7 @@
1
1
  /** @see ./docs/tooling.md */
2
- import { existsSync } from 'node:fs'
3
- import { copyFile, readFile } from 'node:fs/promises'
4
2
  import { dirname, join } from 'node:path'
5
3
  import { fileURLToPath } from 'node:url'
6
4
 
7
- import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
8
-
9
5
  /** Шлях до канонічного oxlint JSON у цьому пакеті (для перевірки та тестів). */
10
6
  export const OXLINT_CANONICAL_JSON_PATH = join(
11
7
  dirname(fileURLToPath(import.meta.url)),
@@ -22,15 +18,13 @@ export const KNIP_CANONICAL_JSON_PATH = join(
22
18
  'knip-canonical.json'
23
19
  )
24
20
 
25
- const NON_DIGITS_RE = /\D+/u
26
-
27
21
  // Канонічний рядок `lint-js`-скрипта і мінімальна версія `@nitra/eslint-config` —
28
22
  // у rego (`npm/policy/js_lint/package_json/`). JS-копії (`CANONICAL_LINT_JS`,
29
23
  // `isCanonicalLintJs`, `nitraEslintConfigMeetsMinVersion`) видалено, щоб не
30
24
  // було двох джерел істини й ризику дрифту.
31
25
 
32
26
  /**
33
- * Рекурсивне порівняння фрагментів канону oxlint (масиви — порядок як у каноні; об’єкти — той самий набір ключів і вкладеність).
27
+ * Рекурсивне порівняння фрагментів канону oxlint (масиви — порядок як у каноні; об'єкти — той самий набір ключів і вкладеність).
34
28
  * @param {unknown} actual значення з `.oxlintrc.json`
35
29
  * @param {unknown} expected значення з канону
36
30
  * @returns {boolean} true, якщо значення збігаються за правилами канону
@@ -154,261 +148,3 @@ export function verifyOxlintRcAgainstCanonical(cfg, canonical) {
154
148
 
155
149
  return { ok: failures.length === 0, failures }
156
150
  }
157
-
158
- /**
159
- * Перевіряє ESLint flat config файл.
160
- * @param {(msg: string) => void} passFn callback при успішній перевірці
161
- * @param {(msg: string) => void} failFn callback при помилці
162
- * @param {string} cwd корінь репозиторію
163
- */
164
- async function checkEslintConfig(passFn, failFn, cwd) {
165
- let eslintPath
166
- if (existsSync(join(cwd, 'eslint.config.js'))) {
167
- eslintPath = 'eslint.config.js'
168
- passFn('eslint.config.js існує')
169
- } else if (existsSync(join(cwd, 'eslint.config.mjs'))) {
170
- eslintPath = 'eslint.config.mjs'
171
- passFn('eslint.config.mjs існує')
172
- } else {
173
- failFn('Відсутній eslint.config.js або eslint.config.mjs — flat config з getConfig (js-lint.mdc)')
174
- return
175
- }
176
- const eslintRaw = await readFile(join(cwd, eslintPath), 'utf8')
177
- const checks = [
178
- {
179
- needle: 'getConfig',
180
- ok: `${eslintPath}: містить getConfig`,
181
- err: `${eslintPath}: потрібен виклик getConfig (js-lint.mdc)`
182
- },
183
- {
184
- needle: '@nitra/eslint-config',
185
- ok: `${eslintPath}: імпорт @nitra/eslint-config`,
186
- err: `${eslintPath}: імпортуй getConfig з @nitra/eslint-config`
187
- },
188
- {
189
- needle: '**/auto-imports.d.ts',
190
- ok: `${eslintPath}: ignores містить **/auto-imports.d.ts`,
191
- err: `${eslintPath}: додай у ignores запис **/auto-imports.d.ts (js-lint.mdc)`
192
- }
193
- ]
194
- for (const { needle, ok, err } of checks) {
195
- if (eslintRaw.includes(needle)) {
196
- passFn(ok)
197
- } else {
198
- failFn(err)
199
- }
200
- }
201
- }
202
-
203
- // Перевірки `prettier` / `@nitra/prettier-config` у залежностях (text.mdc) і
204
- // `@nitra/eslint-config ≥ 3.10.0` тепер у Rego: відповідно
205
- // `npm/policy/text/package_json/` і `npm/policy/js_lint/package_json/`. Тут
206
- // лишилася лише workspace-ітерація для `type: "module"` і engines, бо js_lint
207
- // Rego запускається лише на кореневому `package.json`.
208
-
209
- /**
210
- * Перевіряє, що package.json має `"type": "module"`.
211
- * @param {string} label шлях або назва пакета для повідомлень
212
- * @param {{ type?: string }} pkg parsed package.json
213
- * @param {(msg: string) => void} passFn callback при успішній перевірці
214
- * @param {(msg: string) => void} failFn callback при помилці
215
- */
216
- function checkPackageJsonTypeModule(label, pkg, passFn, failFn) {
217
- if (pkg.type === 'module') {
218
- passFn(`${label}: "type": "module"`)
219
- } else {
220
- failFn(`${label}: має містити "type": "module" (js-lint.mdc)`)
221
- }
222
- }
223
-
224
- /**
225
- * `"type": "module"`, `engines.node >= 24` і `engines.bun >= 1.3` у кожному workspace `package.json`.
226
- * @param {unknown[]} workspaces поле workspaces з package.json
227
- * @param {(msg: string) => void} passFn callback при успішній перевірці
228
- * @param {(msg: string) => void} failFn callback при помилці
229
- * @param {string} cwd корінь репозиторію
230
- */
231
- async function checkWorkspacePackages(workspaces, passFn, failFn, cwd) {
232
- for (const ws of workspaces) {
233
- const wsPkgRel = `${ws}/package.json`
234
- const wsPkgAbs = join(cwd, wsPkgRel)
235
- if (existsSync(wsPkgAbs)) {
236
- const wsPkg = JSON.parse(await readFile(wsPkgAbs, 'utf8'))
237
- checkPackageJsonTypeModule(wsPkgRel, wsPkg, passFn, failFn)
238
- checkEnginesNode(wsPkgRel, wsPkg, passFn, failFn)
239
- checkEnginesBun(wsPkgRel, wsPkg, passFn, failFn)
240
- }
241
- }
242
- }
243
-
244
- /**
245
- * engines.node >= 24.
246
- * @param {string} label шлях або назва пакета для повідомлень
247
- * @param {{ engines?: { node?: string } }} pkg розпарсений package.json
248
- * @param {(msg: string) => void} passFn callback при успішній перевірці
249
- * @param {(msg: string) => void} failFn callback при помилці
250
- */
251
- function checkEnginesNode(label, pkg, passFn, failFn) {
252
- const nodeEngine = pkg.engines?.node
253
- if (nodeEngine) {
254
- const firstNumeric = String(nodeEngine).split(NON_DIGITS_RE).find(Boolean)
255
- if (firstNumeric && Number(firstNumeric) >= 24) {
256
- passFn(`${label}: engines.node "${nodeEngine}"`)
257
- } else {
258
- failFn(`${label}: engines.node "${nodeEngine}" — має бути >=24`)
259
- }
260
- } else {
261
- failFn(`${label} не містить engines.node — додай: "engines": { "node": ">=24" }`)
262
- }
263
- }
264
-
265
- /**
266
- * engines.bun >= 1.3.
267
- * @param {string} label шлях або назва пакета для повідомлень
268
- * @param {{ engines?: { bun?: string } }} pkg розпарсений package.json
269
- * @param {(msg: string) => void} passFn callback при успішній перевірці
270
- * @param {(msg: string) => void} failFn callback при помилці
271
- */
272
- function checkEnginesBun(label, pkg, passFn, failFn) {
273
- const bunEngine = pkg.engines?.bun
274
- if (bunEngine) {
275
- const [major, minor] = String(bunEngine).split(NON_DIGITS_RE).filter(Boolean).map(Number)
276
- if (Number.isFinite(major) && Number.isFinite(minor) && (major > 1 || (major === 1 && minor >= 3))) {
277
- passFn(`${label}: engines.bun "${bunEngine}"`)
278
- } else {
279
- failFn(`${label}: engines.bun "${bunEngine}" — має бути >=1.3`)
280
- }
281
- } else {
282
- failFn(`${label} не містить engines.bun — додай: "engines": { "bun": ">=1.3" }`)
283
- }
284
- }
285
-
286
- /**
287
- * Workspace-ітерація: для кожного workspace `package.json` перевіряємо
288
- * `type: "module"` і `engines.{node,bun}`. Кореневий `package.json` ці поля
289
- * валідує `npm/policy/js_lint/package_json/`; lint-js скрипт і `@nitra/eslint-config`
290
- * — теж у Rego.
291
- * @param {(msg: string) => void} passFn callback при успішній перевірці
292
- * @param {(msg: string) => void} failFn callback при помилці
293
- * @param {string} cwd корінь репозиторію
294
- */
295
- async function checkPackageJsonJsLint(passFn, failFn, cwd) {
296
- const pkgPath = join(cwd, 'package.json')
297
- if (!existsSync(pkgPath)) return
298
- const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
299
- const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : []
300
- await checkWorkspacePackages(workspaces, passFn, failFn, cwd)
301
- }
302
-
303
- /**
304
- * Перевіряє .oxlintrc.json.
305
- * @param {(msg: string) => void} passFn callback при успішній перевірці
306
- * @param {(msg: string) => void} failFn callback при помилці
307
- * @param {string} cwd корінь репозиторію
308
- */
309
- async function checkOxlintRc(passFn, failFn, cwd) {
310
- const oxPath = join(cwd, '.oxlintrc.json')
311
- if (!existsSync(oxPath)) {
312
- failFn('.oxlintrc.json не існує — додай конфіг oxlint (js-lint.mdc)')
313
- return
314
- }
315
- let oxCfg
316
- try {
317
- oxCfg = JSON.parse(await readFile(oxPath, 'utf8'))
318
- } catch {
319
- failFn('.oxlintrc.json не є валідним JSON')
320
- return
321
- }
322
- passFn('.oxlintrc.json існує')
323
- let canonical
324
- try {
325
- canonical = JSON.parse(await readFile(OXLINT_CANONICAL_JSON_PATH, 'utf8'))
326
- } catch {
327
- failFn('внутрішня помилка: не вдалося прочитати канон oxlint з пакета @nitra/cursor')
328
- return
329
- }
330
- const oxV = verifyOxlintRcAgainstCanonical(oxCfg, canonical)
331
- if (oxV.ok) {
332
- passFn('.oxlintrc.json збігається з каноном oxlint (@nitra/cursor)')
333
- } else {
334
- for (const msg of oxV.failures) {
335
- failFn(msg)
336
- }
337
- }
338
- }
339
-
340
- /**
341
- * FS-existence для `lint-js.yml` + cross-file перевірка, що `lint.yml` (якщо існує)
342
- * не дублює лінт JS-кроки. Структуру `lint-js.yml` (`actions/checkout@v6`,
343
- * `persist-credentials: false`, `setup-bun-deps`, `bunx oxlint/eslint/jscpd .`,
344
- * заборона `--fix` у CI) валідує `npm/policy/js_lint/lint_js_yml/`.
345
- * @param {(msg: string) => void} passFn callback при успішній перевірці
346
- * @param {(msg: string) => void} failFn callback при помилці
347
- * @param {string} cwd корінь репозиторію
348
- */
349
- async function checkLintJsWorkflows(passFn, failFn, cwd) {
350
- if (existsSync(join(cwd, '.github/workflows/lint-js.yml'))) {
351
- passFn('.github/workflows/lint-js.yml є (структуру перевіряє npx @nitra/cursor fix → js_lint.lint_js_yml)')
352
- } else {
353
- failFn('.github/workflows/lint-js.yml не існує — створи його (див. rules/js-lint/fix.mjs / js-lint.mdc)')
354
- }
355
-
356
- const lintYmlPath = join(cwd, '.github/workflows/lint.yml')
357
- if (existsSync(lintYmlPath)) {
358
- const lintYml = await readFile(lintYmlPath, 'utf8')
359
- if (lintYml.includes('bunx oxlint') && lintYml.includes('bunx eslint') && lintYml.includes('jscpd')) {
360
- failFn('.github/workflows/lint.yml дублює кроки lint-js.yml — залиш один workflow на лінт JS (js-lint.mdc)')
361
- } else {
362
- passFn('.github/workflows/lint.yml не дублює oxlint/eslint/jscpd з lint-js.yml')
363
- }
364
- }
365
- }
366
-
367
- /**
368
- * Перевіряє наявність `knip.json` у корені проєкту. Якщо файл відсутній —
369
- * копіює канонічний `knip-canonical.json` з пакета `@nitra/cursor` як стартовий
370
- * baseline; зміст подальших модифікацій локально не валідується (`entry` /
371
- * `project` / `ignore` / `ignoreDependencies` / `ignoreBinaries` дозволені
372
- * будь-які; це side effect — описано у js-lint.mdc).
373
- * @param {(msg: string) => void} passFn callback при успішній перевірці
374
- * @param {(msg: string) => void} failFn callback при помилці
375
- * @param {string} cwd корінь репозиторію
376
- */
377
- async function checkKnipConfig(passFn, failFn, cwd) {
378
- const knipPath = join(cwd, 'knip.json')
379
- if (existsSync(knipPath)) {
380
- passFn('knip.json існує')
381
- return
382
- }
383
- if (!existsSync(KNIP_CANONICAL_JSON_PATH)) {
384
- failFn(
385
- `knip.json відсутній, і канонічний шаблон у пакеті не знайдено (${KNIP_CANONICAL_JSON_PATH}) — ` +
386
- 'перевстанови @nitra/cursor'
387
- )
388
- return
389
- }
390
- await copyFile(KNIP_CANONICAL_JSON_PATH, knipPath)
391
- passFn('knip.json створено з канонічного npm/rules/js-lint/js/data/tooling/knip-canonical.json (js-lint.mdc)')
392
- }
393
-
394
- /**
395
- * Перевіряє відповідність проєкту правилам js-lint.mdc
396
- * @param {string} [cwd] корінь репозиторію
397
- * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
398
- */
399
- export async function check(cwd = process.cwd()) {
400
- const reporter = createCheckReporter()
401
- const { pass, fail } = reporter
402
-
403
- await checkEslintConfig(pass, fail, cwd)
404
- await checkPackageJsonJsLint(pass, fail, cwd)
405
- await checkOxlintRc(pass, fail, cwd)
406
- await checkLintJsWorkflows(pass, fail, cwd)
407
- await checkKnipConfig(pass, fail, cwd)
408
-
409
- for (const dup of ['.eslintrc', '.eslintrc.js', '.eslintrc.json', '.eslintrc.yml']) {
410
- if (existsSync(join(cwd, dup))) fail(`Знайдено застарілий конфіг ESLint: ${dup} — видали, використовуй flat config`)
411
- }
412
-
413
- return reporter.getExitCode()
414
- }