@7n/rules 1.0.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 (1219) hide show
  1. package/.claude-template/hooks/capture-decisions.sh +355 -0
  2. package/.claude-template/hooks/lib/tooling-only.sh +77 -0
  3. package/.claude-template/hooks/normalize-decisions.sh +574 -0
  4. package/.claude-template/settings.template.json +26 -0
  5. package/.pi-template/extensions/n-rules-adr/docs/index.md +28 -0
  6. package/.pi-template/extensions/n-rules-adr/index.ts +105 -0
  7. package/.pi-template/extensions/n-rules-adr/tsconfig.json +17 -0
  8. package/AGENTS.template.md +53 -0
  9. package/CHANGELOG.md +4277 -0
  10. package/README.md +189 -0
  11. package/bin/docs/n-rules.md +612 -0
  12. package/bin/docs/rename-yaml-extensions.md +207 -0
  13. package/bin/n-rules.js +1709 -0
  14. package/bin/rename-yaml-extensions.mjs +42 -0
  15. package/docs/index.md +12 -0
  16. package/docs/stryker.config.md +31 -0
  17. package/docs/vitest.config.md +36 -0
  18. package/github-actions/release/action.yml +9 -0
  19. package/github-actions/setup-bun-deps/action.yml +27 -0
  20. package/package.json +87 -0
  21. package/rules/abie/base_deployment_preem/base_deployment_preem.mdc +22 -0
  22. package/rules/abie/base_deployment_preem/base_deployment_preem.rego +54 -0
  23. package/rules/abie/base_deployment_preem/concern.json +8 -0
  24. package/rules/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.mdc +19 -0
  25. package/rules/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +81 -0
  26. package/rules/abie/clean_merged_ignore_branches/concern.json +8 -0
  27. package/rules/abie/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml +9 -0
  28. package/rules/abie/docs/index.md +11 -0
  29. package/rules/abie/env_dns/concern.json +4 -0
  30. package/rules/abie/env_dns/docs/main.md +32 -0
  31. package/rules/abie/env_dns/env_dns.mdc +33 -0
  32. package/rules/abie/env_dns/main.mjs +47 -0
  33. package/rules/abie/firebase_hosting/concern.json +4 -0
  34. package/rules/abie/firebase_hosting/docs/main.md +41 -0
  35. package/rules/abie/firebase_hosting/firebase_hosting.mdc +3 -0
  36. package/rules/abie/firebase_hosting/main.mjs +46 -0
  37. package/rules/abie/hc_pairing/concern.json +4 -0
  38. package/rules/abie/hc_pairing/docs/main.md +34 -0
  39. package/rules/abie/hc_pairing/hc_pairing.mdc +23 -0
  40. package/rules/abie/hc_pairing/main.mjs +52 -0
  41. package/rules/abie/health_check_policy/concern.json +8 -0
  42. package/rules/abie/health_check_policy/health_check_policy.mdc +19 -0
  43. package/rules/abie/health_check_policy/health_check_policy.rego +172 -0
  44. package/rules/abie/http_route_base/concern.json +8 -0
  45. package/rules/abie/http_route_base/http_route_base.mdc +9 -0
  46. package/rules/abie/http_route_base/http_route_base.rego +45 -0
  47. package/rules/abie/lib/docs/enabled.md +38 -0
  48. package/rules/abie/lib/docs/env-dns.md +31 -0
  49. package/rules/abie/lib/docs/hc-yaml.md +33 -0
  50. package/rules/abie/lib/docs/http-route.md +31 -0
  51. package/rules/abie/lib/docs/index.md +18 -0
  52. package/rules/abie/lib/docs/k8s-tree.md +44 -0
  53. package/rules/abie/lib/docs/kustomization-patches.md +37 -0
  54. package/rules/abie/lib/docs/overlay-paths.md +42 -0
  55. package/rules/abie/lib/docs/yaml.md +33 -0
  56. package/rules/abie/lib/enabled.mjs +37 -0
  57. package/rules/abie/lib/env-dns.mjs +81 -0
  58. package/rules/abie/lib/hc-yaml.mjs +27 -0
  59. package/rules/abie/lib/http-route.mjs +100 -0
  60. package/rules/abie/lib/k8s-tree.mjs +110 -0
  61. package/rules/abie/lib/kustomization-patches.mjs +211 -0
  62. package/rules/abie/lib/overlay-paths.mjs +97 -0
  63. package/rules/abie/lib/yaml.mjs +72 -0
  64. package/rules/abie/main.json +1 -0
  65. package/rules/abie/main.mdc +47 -0
  66. package/rules/abie/package_json_shared/concern.json +11 -0
  67. package/rules/abie/package_json_shared/package_json_shared.mdc +17 -0
  68. package/rules/abie/package_json_shared/package_json_shared.rego +16 -0
  69. package/rules/abie/ua_http_route/concern.json +4 -0
  70. package/rules/abie/ua_http_route/docs/main.md +44 -0
  71. package/rules/abie/ua_http_route/main.mjs +80 -0
  72. package/rules/abie/ua_http_route/ua_http_route.mdc +47 -0
  73. package/rules/abie/ua_node_selector/concern.json +4 -0
  74. package/rules/abie/ua_node_selector/docs/main.md +34 -0
  75. package/rules/abie/ua_node_selector/main.mjs +62 -0
  76. package/rules/abie/ua_node_selector/ua_node_selector.mdc +27 -0
  77. package/rules/adr/docs/index.md +11 -0
  78. package/rules/adr/hooks/concern.json +4 -0
  79. package/rules/adr/hooks/docs/main.md +38 -0
  80. package/rules/adr/hooks/hooks.mdc +32 -0
  81. package/rules/adr/hooks/main.mjs +367 -0
  82. package/rules/adr/main.json +1 -0
  83. package/rules/adr/main.mdc +93 -0
  84. package/rules/adr/settings_json/concern.json +9 -0
  85. package/rules/adr/settings_json/settings_json.mdc +7 -0
  86. package/rules/adr/settings_json/settings_json.rego +37 -0
  87. package/rules/adr/settings_local_json/concern.json +9 -0
  88. package/rules/adr/settings_local_json/settings_local_json.mdc +7 -0
  89. package/rules/adr/settings_local_json/settings_local_json.rego +40 -0
  90. package/rules/bun/bunfig/bunfig.mdc +12 -0
  91. package/rules/bun/bunfig/bunfig.rego +29 -0
  92. package/rules/bun/bunfig/concern.json +9 -0
  93. package/rules/bun/bunfig/template/bunfig.toml.snippet.toml +2 -0
  94. package/rules/bun/docs/index.md +11 -0
  95. package/rules/bun/layout/concern.json +16 -0
  96. package/rules/bun/layout/docs/fix-layout.md +29 -0
  97. package/rules/bun/layout/docs/main.md +34 -0
  98. package/rules/bun/layout/fix-layout.mjs +63 -0
  99. package/rules/bun/layout/layout.mdc +60 -0
  100. package/rules/bun/layout/main.mjs +53 -0
  101. package/rules/bun/licensee/concern.json +7 -0
  102. package/rules/bun/licensee/docs/fix-licensee.md +28 -0
  103. package/rules/bun/licensee/docs/index.md +12 -0
  104. package/rules/bun/licensee/docs/main.md +37 -0
  105. package/rules/bun/licensee/fix-licensee.mjs +32 -0
  106. package/rules/bun/licensee/main.mjs +44 -0
  107. package/rules/bun/main.json +1 -0
  108. package/rules/bun/main.mdc +11 -0
  109. package/rules/bun/package_json/concern.json +9 -0
  110. package/rules/bun/package_json/docs/fix-package_json.md +28 -0
  111. package/rules/bun/package_json/docs/index.md +9 -0
  112. package/rules/bun/package_json/fix-package_json.mjs +311 -0
  113. package/rules/bun/package_json/package_json.mdc +14 -0
  114. package/rules/bun/package_json/package_json.rego +64 -0
  115. package/rules/bun/package_json/template/package.json.deny.json +4 -0
  116. package/rules/capacitor/docs/index.md +11 -0
  117. package/rules/capacitor/main.json +1 -0
  118. package/rules/capacitor/main.mdc +12 -0
  119. package/rules/capacitor/package_json/concern.json +9 -0
  120. package/rules/capacitor/package_json/package_json.mdc +9 -0
  121. package/rules/capacitor/package_json/package_json.rego +45 -0
  122. package/rules/capacitor/platforms/concern.json +4 -0
  123. package/rules/capacitor/platforms/docs/main.md +41 -0
  124. package/rules/capacitor/platforms/main.mjs +486 -0
  125. package/rules/changelog/consistency/concern.json +4 -0
  126. package/rules/changelog/consistency/docs/fix-consistency.md +31 -0
  127. package/rules/changelog/consistency/docs/main.md +41 -0
  128. package/rules/changelog/consistency/fix-consistency.mjs +71 -0
  129. package/rules/changelog/consistency/main.mjs +820 -0
  130. package/rules/changelog/docs/index.md +11 -0
  131. package/rules/changelog/lib/docs/index.md +11 -0
  132. package/rules/changelog/lib/docs/package-manifest.md +214 -0
  133. package/rules/changelog/lib/package-manifest.mjs +158 -0
  134. package/rules/changelog/main.json +1 -0
  135. package/rules/changelog/main.mdc +7 -0
  136. package/rules/changelog/presence/concern.json +7 -0
  137. package/rules/changelog/presence/docs/main.md +44 -0
  138. package/rules/changelog/presence/main.mjs +87 -0
  139. package/rules/ci4/docs/index.md +11 -0
  140. package/rules/ci4/main.json +1 -0
  141. package/rules/ci4/main.mdc +375 -0
  142. package/rules/ci4/marksman_config/concern.json +5 -0
  143. package/rules/ci4/marksman_config/data/marksman_config/marksman.baseline.toml +27 -0
  144. package/rules/ci4/marksman_config/docs/fix-marksman_config.md +27 -0
  145. package/rules/ci4/marksman_config/docs/main.md +30 -0
  146. package/rules/ci4/marksman_config/fix-marksman_config.mjs +24 -0
  147. package/rules/ci4/marksman_config/main.mjs +38 -0
  148. package/rules/ci4/marksman_config/marksman_config.mdc +31 -0
  149. package/rules/ci4/vscode_extensions/concern.json +9 -0
  150. package/rules/ci4/vscode_extensions/docs/fix-vscode_extensions.md +25 -0
  151. package/rules/ci4/vscode_extensions/docs/index.md +11 -0
  152. package/rules/ci4/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  153. package/rules/ci4/vscode_extensions/template/extensions.json.snippet.json +3 -0
  154. package/rules/ci4/vscode_extensions/vscode_extensions.mdc +9 -0
  155. package/rules/ci4/vscode_extensions/vscode_extensions.rego +12 -0
  156. package/rules/doc-files/check/concern.json +7 -0
  157. package/rules/doc-files/check/docs/fix-check.md +28 -0
  158. package/rules/doc-files/check/docs/fix-worker.md +28 -0
  159. package/rules/doc-files/check/docs/main.md +32 -0
  160. package/rules/doc-files/check/fix-check.mjs +45 -0
  161. package/rules/doc-files/check/fix-worker.mjs +56 -0
  162. package/rules/doc-files/check/main.mjs +99 -0
  163. package/rules/doc-files/docgen-crc/concern.json +3 -0
  164. package/rules/doc-files/docgen-crc/docs/main.md +46 -0
  165. package/rules/doc-files/docgen-crc/main.mjs +207 -0
  166. package/rules/doc-files/docgen-extract/concern.json +3 -0
  167. package/rules/doc-files/docgen-extract/docs/main.md +38 -0
  168. package/rules/doc-files/docgen-extract/main.mjs +471 -0
  169. package/rules/doc-files/docgen-extract-anchors/concern.json +3 -0
  170. package/rules/doc-files/docgen-extract-anchors/docs/main.md +49 -0
  171. package/rules/doc-files/docgen-extract-anchors/main.mjs +115 -0
  172. package/rules/doc-files/docgen-files-batch/concern.json +3 -0
  173. package/rules/doc-files/docgen-files-batch/docs/main.md +38 -0
  174. package/rules/doc-files/docgen-files-batch/main.mjs +517 -0
  175. package/rules/doc-files/docgen-fix-worker/concern.json +3 -0
  176. package/rules/doc-files/docgen-gen/concern.json +3 -0
  177. package/rules/doc-files/docgen-gen/docs/main.md +40 -0
  178. package/rules/doc-files/docgen-gen/main.mjs +582 -0
  179. package/rules/doc-files/docgen-ignore/concern.json +3 -0
  180. package/rules/doc-files/docgen-ignore/docs/main.md +33 -0
  181. package/rules/doc-files/docgen-ignore/main.mjs +54 -0
  182. package/rules/doc-files/docgen-judge/concern.json +3 -0
  183. package/rules/doc-files/docgen-judge/docs/main.md +43 -0
  184. package/rules/doc-files/docgen-judge/main.mjs +116 -0
  185. package/rules/doc-files/docgen-prompts/concern.json +3 -0
  186. package/rules/doc-files/docgen-prompts/docs/main.md +48 -0
  187. package/rules/doc-files/docgen-prompts/main.mjs +222 -0
  188. package/rules/doc-files/docgen-scan/concern.json +3 -0
  189. package/rules/doc-files/docgen-scan/docs/main.md +52 -0
  190. package/rules/doc-files/docgen-scan/main.mjs +231 -0
  191. package/rules/doc-files/docs/index.md +11 -0
  192. package/rules/doc-files/main.json +1 -0
  193. package/rules/doc-files/main.mdc +49 -0
  194. package/rules/doc-files/units/concern.json +3 -0
  195. package/rules/doc-files/units/docs/main.md +32 -0
  196. package/rules/doc-files/units/main.mjs +20 -0
  197. package/rules/doc-files/units-js/concern.json +3 -0
  198. package/rules/doc-files/units-js/docs/main.md +32 -0
  199. package/rules/doc-files/units-js/main.mjs +141 -0
  200. package/rules/doc-files/units-rs/concern.json +3 -0
  201. package/rules/doc-files/units-rs/docs/main.md +36 -0
  202. package/rules/doc-files/units-rs/main.mjs +311 -0
  203. package/rules/docker/docs/index.md +11 -0
  204. package/rules/docker/lib/docker-hadolint.mjs +60 -0
  205. package/rules/docker/lib/docker-mirror.mjs +148 -0
  206. package/rules/docker/lib/docker-native-addon.mjs +93 -0
  207. package/rules/docker/lib/docker-nginx-user.mjs +122 -0
  208. package/rules/docker/lib/docs/docker-hadolint.md +29 -0
  209. package/rules/docker/lib/docs/docker-mirror.md +251 -0
  210. package/rules/docker/lib/docs/docker-native-addon.md +174 -0
  211. package/rules/docker/lib/docs/docker-nginx-user.md +223 -0
  212. package/rules/docker/lib/docs/index.md +14 -0
  213. package/rules/docker/lint/concern.json +7 -0
  214. package/rules/docker/lint/docs/main.md +47 -0
  215. package/rules/docker/lint/main.mjs +360 -0
  216. package/rules/docker/lint_docker_yml/concern.json +9 -0
  217. package/rules/docker/lint_docker_yml/lint_docker_yml.mdc +14 -0
  218. package/rules/docker/lint_docker_yml/lint_docker_yml.rego +79 -0
  219. package/rules/docker/lint_docker_yml/template/lint-docker.yml.snippet.yml +41 -0
  220. package/rules/docker/main.json +1 -0
  221. package/rules/docker/main.mdc +14 -0
  222. package/rules/efes/docs/index.md +11 -0
  223. package/rules/efes/main.json +1 -0
  224. package/rules/efes/main.mdc +21 -0
  225. package/rules/efes/package_json_shared/concern.json +11 -0
  226. package/rules/efes/package_json_shared/package_json_shared.mdc +33 -0
  227. package/rules/efes/package_json_shared/package_json_shared.rego +16 -0
  228. package/rules/feedback/docs/index.md +11 -0
  229. package/rules/feedback/main.json +1 -0
  230. package/rules/feedback/main.mdc +63 -0
  231. package/rules/ga/clean_ga_workflows/clean_ga_workflows.mdc +20 -0
  232. package/rules/ga/clean_ga_workflows/clean_ga_workflows.rego +112 -0
  233. package/rules/ga/clean_ga_workflows/concern.json +10 -0
  234. package/rules/ga/clean_ga_workflows/template/clean-ga-workflows.yml.snippet.yml +26 -0
  235. package/rules/ga/clean_merged_branch/clean_merged_branch.mdc +24 -0
  236. package/rules/ga/clean_merged_branch/clean_merged_branch.rego +163 -0
  237. package/rules/ga/clean_merged_branch/concern.json +10 -0
  238. package/rules/ga/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +37 -0
  239. package/rules/ga/docs/index.md +11 -0
  240. package/rules/ga/git_ai/concern.json +11 -0
  241. package/rules/ga/git_ai/git_ai.mdc +19 -0
  242. package/rules/ga/git_ai/git_ai.rego +76 -0
  243. package/rules/ga/git_ai/template/git-ai.yml.snippet.yml +30 -0
  244. package/rules/ga/lint_ga/concern.json +10 -0
  245. package/rules/ga/lint_ga/lint_ga.mdc +21 -0
  246. package/rules/ga/lint_ga/lint_ga.rego +132 -0
  247. package/rules/ga/lint_ga/template/lint-ga.yml.snippet.yml +41 -0
  248. package/rules/ga/main.json +1 -0
  249. package/rules/ga/main.mdc +17 -0
  250. package/rules/ga/vscode_extensions/concern.json +11 -0
  251. package/rules/ga/vscode_extensions/docs/fix-vscode_extensions.md +25 -0
  252. package/rules/ga/vscode_extensions/docs/index.md +11 -0
  253. package/rules/ga/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  254. package/rules/ga/vscode_extensions/template/extensions.json.snippet.json +1 -0
  255. package/rules/ga/vscode_extensions/vscode_extensions.mdc +9 -0
  256. package/rules/ga/vscode_extensions/vscode_extensions.rego +18 -0
  257. package/rules/ga/vscode_settings/concern.json +11 -0
  258. package/rules/ga/vscode_settings/template/settings.json.snippet.json +1 -0
  259. package/rules/ga/vscode_settings/vscode_settings.mdc +9 -0
  260. package/rules/ga/vscode_settings/vscode_settings.rego +22 -0
  261. package/rules/ga/workflow_common/concern.json +9 -0
  262. package/rules/ga/workflow_common/template/uses-min-versions.snippet.json +4 -0
  263. package/rules/ga/workflow_common/workflow_common.mdc +18 -0
  264. package/rules/ga/workflow_common/workflow_common.rego +288 -0
  265. package/rules/ga/workflows/concern.json +8 -0
  266. package/rules/ga/workflows/docs/fix-workflows.md +37 -0
  267. package/rules/ga/workflows/docs/main.md +38 -0
  268. package/rules/ga/workflows/fix-workflows.mjs +212 -0
  269. package/rules/ga/workflows/main.mjs +433 -0
  270. package/rules/ga/workflows/workflows.mdc +33 -0
  271. package/rules/ga/zizmor_yml/concern.json +11 -0
  272. package/rules/ga/zizmor_yml/template/zizmor.yml.snippet.yml +5 -0
  273. package/rules/ga/zizmor_yml/zizmor_yml.mdc +9 -0
  274. package/rules/ga/zizmor_yml/zizmor_yml.rego +27 -0
  275. package/rules/graphql/docs/index.md +11 -0
  276. package/rules/graphql/lib/docs/graphql-gql-scan.md +226 -0
  277. package/rules/graphql/lib/docs/index.md +11 -0
  278. package/rules/graphql/lib/graphql-gql-scan.mjs +146 -0
  279. package/rules/graphql/main.json +1 -0
  280. package/rules/graphql/main.mdc +8 -0
  281. package/rules/graphql/tooling/concern.json +8 -0
  282. package/rules/graphql/tooling/docs/main.md +36 -0
  283. package/rules/graphql/tooling/main.mjs +129 -0
  284. package/rules/graphql/tooling/tooling.mdc +13 -0
  285. package/rules/graphql/vscode_extensions/concern.json +9 -0
  286. package/rules/graphql/vscode_extensions/docs/fix-vscode_extensions.md +24 -0
  287. package/rules/graphql/vscode_extensions/docs/index.md +11 -0
  288. package/rules/graphql/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  289. package/rules/graphql/vscode_extensions/vscode_extensions.mdc +9 -0
  290. package/rules/graphql/vscode_extensions/vscode_extensions.rego +20 -0
  291. package/rules/hasura/docs/index.md +11 -0
  292. package/rules/hasura/internal_urls/concern.json +5 -0
  293. package/rules/hasura/internal_urls/docs/fix-internal_urls.md +26 -0
  294. package/rules/hasura/internal_urls/docs/index.md +12 -0
  295. package/rules/hasura/internal_urls/docs/main.md +36 -0
  296. package/rules/hasura/internal_urls/fix-internal_urls.mjs +66 -0
  297. package/rules/hasura/internal_urls/internal_urls.mdc +27 -0
  298. package/rules/hasura/internal_urls/main.mjs +233 -0
  299. package/rules/hasura/main.json +1 -0
  300. package/rules/hasura/main.mdc +8 -0
  301. package/rules/hasura/migrations/concern.json +4 -0
  302. package/rules/hasura/migrations/docs/fix-migrations.md +25 -0
  303. package/rules/hasura/migrations/docs/index.md +12 -0
  304. package/rules/hasura/migrations/docs/main.md +35 -0
  305. package/rules/hasura/migrations/fix-migrations.mjs +35 -0
  306. package/rules/hasura/migrations/main.mjs +53 -0
  307. package/rules/hasura/migrations/migrations.mdc +13 -0
  308. package/rules/hasura/svc_hl/concern.json +8 -0
  309. package/rules/hasura/svc_hl/svc_hl.mdc +15 -0
  310. package/rules/hasura/svc_hl/svc_hl.rego +50 -0
  311. package/rules/image-avif/avif_generation/avif_generation.mdc +26 -0
  312. package/rules/image-avif/avif_generation/concern.json +4 -0
  313. package/rules/image-avif/avif_generation/docs/fix-avif_generation.md +27 -0
  314. package/rules/image-avif/avif_generation/docs/main.md +36 -0
  315. package/rules/image-avif/avif_generation/fix-avif_generation.mjs +80 -0
  316. package/rules/image-avif/avif_generation/main.mjs +411 -0
  317. package/rules/image-avif/docs/index.md +11 -0
  318. package/rules/image-avif/main.json +1 -0
  319. package/rules/image-avif/main.mdc +8 -0
  320. package/rules/image-avif/package_json/concern.json +9 -0
  321. package/rules/image-avif/package_json/package_json.mdc +18 -0
  322. package/rules/image-avif/package_json/package_json.rego +47 -0
  323. package/rules/image-avif/package_json/template/package.json.deny.json +5 -0
  324. package/rules/image-compress/check/concern.json +7 -0
  325. package/rules/image-compress/check/docs/fix-check.md +34 -0
  326. package/rules/image-compress/check/docs/index.md +10 -0
  327. package/rules/image-compress/check/docs/main.md +31 -0
  328. package/rules/image-compress/check/fix-check.mjs +74 -0
  329. package/rules/image-compress/check/main.mjs +64 -0
  330. package/rules/image-compress/docs/index.md +11 -0
  331. package/rules/image-compress/main.json +1 -0
  332. package/rules/image-compress/main.mdc +10 -0
  333. package/rules/image-compress/package_json/concern.json +9 -0
  334. package/rules/image-compress/package_json/package_json.mdc +13 -0
  335. package/rules/image-compress/package_json/package_json.rego +21 -0
  336. package/rules/image-compress/package_json/template/package.json.deny.json +8 -0
  337. package/rules/image-compress/package_setup/concern.json +4 -0
  338. package/rules/image-compress/package_setup/docs/main.md +34 -0
  339. package/rules/image-compress/package_setup/main.mjs +98 -0
  340. package/rules/image-compress/package_setup/package_setup.mdc +13 -0
  341. package/rules/js/check/concern.json +19 -0
  342. package/rules/js/check/docs/eslint-config.md +56 -0
  343. package/rules/js/check/docs/fix-check.md +31 -0
  344. package/rules/js/check/docs/index.md +13 -0
  345. package/rules/js/check/docs/main.md +45 -0
  346. package/rules/js/check/eslint-config.mjs +262 -0
  347. package/rules/js/check/fix-check.mjs +39 -0
  348. package/rules/js/check/main.mjs +312 -0
  349. package/rules/js/dep-policy/concern.json +4 -0
  350. package/rules/js/dep-policy/dep-policy.mdc +36 -0
  351. package/rules/js/dep-policy/docs/main.md +35 -0
  352. package/rules/js/dep-policy/main.mjs +99 -0
  353. package/rules/js/docs/index.md +11 -0
  354. package/rules/js/eslint/concern.json +7 -0
  355. package/rules/js/eslint/docs/fix-eslint.md +32 -0
  356. package/rules/js/eslint/docs/main.md +33 -0
  357. package/rules/js/eslint/fix-eslint.mjs +77 -0
  358. package/rules/js/eslint/main.mjs +99 -0
  359. package/rules/js/jscpd_config/concern.json +11 -0
  360. package/rules/js/jscpd_config/jscpd_config.mdc +14 -0
  361. package/rules/js/jscpd_config/jscpd_config.rego +44 -0
  362. package/rules/js/jscpd_config/template/.jscpd.json.snippet.json +7 -0
  363. package/rules/js/jscpd_duplicates/concern.json +7 -0
  364. package/rules/js/jscpd_duplicates/docs/main.md +29 -0
  365. package/rules/js/jscpd_duplicates/main.mjs +70 -0
  366. package/rules/js/knip/concern.json +6 -0
  367. package/rules/js/knip/docs/main.md +32 -0
  368. package/rules/js/knip/main.mjs +68 -0
  369. package/rules/js/lint-findings/concern.json +3 -0
  370. package/rules/js/lint-findings/docs/main.md +41 -0
  371. package/rules/js/lint-findings/main.mjs +125 -0
  372. package/rules/js/lint_js_yml/concern.json +9 -0
  373. package/rules/js/lint_js_yml/lint_js_yml.mdc +14 -0
  374. package/rules/js/lint_js_yml/lint_js_yml.rego +117 -0
  375. package/rules/js/lint_js_yml/template/lint-js.yml.snippet.yml +40 -0
  376. package/rules/js/main.json +1 -0
  377. package/rules/js/main.mdc +19 -0
  378. package/rules/js/package_json/concern.json +9 -0
  379. package/rules/js/package_json/package_json.mdc +15 -0
  380. package/rules/js/package_json/package_json.rego +142 -0
  381. package/rules/js/package_json/template/package.json.snippet.json +6 -0
  382. package/rules/js/tooling/concern.json +3 -0
  383. package/rules/js/tooling/data/tooling/knip-canonical.json +29 -0
  384. package/rules/js/tooling/data/tooling/oxlint-canonical.json +400 -0
  385. package/rules/js/tooling/docs/main.md +31 -0
  386. package/rules/js/tooling/main.mjs +149 -0
  387. package/rules/js/utils_imports/concern.json +4 -0
  388. package/rules/js/utils_imports/docs/main.md +50 -0
  389. package/rules/js/utils_imports/main.mjs +185 -0
  390. package/rules/js/vscode_extensions/concern.json +11 -0
  391. package/rules/js/vscode_extensions/docs/fix-vscode_extensions.md +25 -0
  392. package/rules/js/vscode_extensions/docs/index.md +11 -0
  393. package/rules/js/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  394. package/rules/js/vscode_extensions/template/extensions.json.snippet.json +3 -0
  395. package/rules/js/vscode_extensions/vscode_extensions.mdc +11 -0
  396. package/rules/js/vscode_extensions/vscode_extensions.rego +12 -0
  397. package/rules/js-bun-db/docs/index.md +11 -0
  398. package/rules/js-bun-db/lib/bun-sql-scan.mjs +1055 -0
  399. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +63 -0
  400. package/rules/js-bun-db/lib/docs/index.md +11 -0
  401. package/rules/js-bun-db/main.json +1 -0
  402. package/rules/js-bun-db/main.mdc +8 -0
  403. package/rules/js-bun-db/package_json/concern.json +9 -0
  404. package/rules/js-bun-db/package_json/package_json.mdc +17 -0
  405. package/rules/js-bun-db/package_json/package_json.rego +15 -0
  406. package/rules/js-bun-db/package_json/template/package.json.deny.json +6 -0
  407. package/rules/js-bun-db/safety/concern.json +4 -0
  408. package/rules/js-bun-db/safety/docs/main.md +34 -0
  409. package/rules/js-bun-db/safety/main.mjs +430 -0
  410. package/rules/js-bun-redis/docs/index.md +11 -0
  411. package/rules/js-bun-redis/imports/concern.json +4 -0
  412. package/rules/js-bun-redis/imports/docs/main.md +36 -0
  413. package/rules/js-bun-redis/imports/imports.mdc +47 -0
  414. package/rules/js-bun-redis/imports/main.mjs +88 -0
  415. package/rules/js-bun-redis/lib/docs/index.md +11 -0
  416. package/rules/js-bun-redis/lib/docs/redis-imports.md +227 -0
  417. package/rules/js-bun-redis/lib/redis-imports.mjs +130 -0
  418. package/rules/js-bun-redis/main.json +1 -0
  419. package/rules/js-bun-redis/main.mdc +8 -0
  420. package/rules/js-bun-redis/package_json/concern.json +9 -0
  421. package/rules/js-bun-redis/package_json/package_json.mdc +11 -0
  422. package/rules/js-bun-redis/package_json/package_json.rego +15 -0
  423. package/rules/js-bun-redis/package_json/template/package.json.deny.json +12 -0
  424. package/rules/js-mssql/deps/concern.json +4 -0
  425. package/rules/js-mssql/deps/docs/main.md +33 -0
  426. package/rules/js-mssql/deps/main.mjs +297 -0
  427. package/rules/js-mssql/docs/index.md +11 -0
  428. package/rules/js-mssql/lib/docs/index.md +11 -0
  429. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +380 -0
  430. package/rules/js-mssql/lib/mssql-pool-scan.mjs +610 -0
  431. package/rules/js-mssql/main.json +1 -0
  432. package/rules/js-mssql/main.mdc +14 -0
  433. package/rules/js-mssql/package_json/concern.json +9 -0
  434. package/rules/js-mssql/package_json/package_json.mdc +9 -0
  435. package/rules/js-mssql/package_json/package_json.rego +57 -0
  436. package/rules/js-run/configmap/concern.json +9 -0
  437. package/rules/js-run/configmap/configmap.mdc +31 -0
  438. package/rules/js-run/configmap/configmap.rego +21 -0
  439. package/rules/js-run/configmap/template/configmap.yaml.contains.yml +4 -0
  440. package/rules/js-run/docs/index.md +11 -0
  441. package/rules/js-run/jsconfig/concern.json +9 -0
  442. package/rules/js-run/jsconfig/docs/fix-jsconfig.md +29 -0
  443. package/rules/js-run/jsconfig/docs/index.md +9 -0
  444. package/rules/js-run/jsconfig/fix-jsconfig.mjs +119 -0
  445. package/rules/js-run/jsconfig/jsconfig.mdc +25 -0
  446. package/rules/js-run/jsconfig/jsconfig.rego +59 -0
  447. package/rules/js-run/jsconfig/template/jsconfig.json.snippet.json +10 -0
  448. package/rules/js-run/lib/bunyan-imports.mjs +98 -0
  449. package/rules/js-run/lib/check-env-scan.mjs +338 -0
  450. package/rules/js-run/lib/conn-file-rules.mjs +214 -0
  451. package/rules/js-run/lib/conn-imports-scan.mjs +154 -0
  452. package/rules/js-run/lib/docs/bunyan-imports.md +121 -0
  453. package/rules/js-run/lib/docs/check-env-scan.md +438 -0
  454. package/rules/js-run/lib/docs/conn-file-rules.md +304 -0
  455. package/rules/js-run/lib/docs/conn-imports-scan.md +208 -0
  456. package/rules/js-run/lib/docs/index.md +16 -0
  457. package/rules/js-run/lib/docs/promise-settimeout-scan.md +334 -0
  458. package/rules/js-run/lib/docs/temporal-scan.md +29 -0
  459. package/rules/js-run/lib/promise-settimeout-scan.mjs +128 -0
  460. package/rules/js-run/lib/temporal-scan.mjs +52 -0
  461. package/rules/js-run/main.json +1 -0
  462. package/rules/js-run/main.mdc +16 -0
  463. package/rules/js-run/package_json/concern.json +9 -0
  464. package/rules/js-run/package_json/package_json.mdc +38 -0
  465. package/rules/js-run/package_json/package_json.rego +37 -0
  466. package/rules/js-run/package_json/template/package.json.deny.json +22 -0
  467. package/rules/js-run/runtime/concern.json +12 -0
  468. package/rules/js-run/runtime/docs/fix-runtime.md +27 -0
  469. package/rules/js-run/runtime/docs/main.md +35 -0
  470. package/rules/js-run/runtime/fix-runtime.mjs +46 -0
  471. package/rules/js-run/runtime/main.mjs +494 -0
  472. package/rules/js-run/runtime/runtime.mdc +14 -0
  473. package/rules/k8s/base_kustomization/base_kustomization.mdc +12 -0
  474. package/rules/k8s/base_kustomization/base_kustomization.rego +77 -0
  475. package/rules/k8s/base_kustomization/concern.json +8 -0
  476. package/rules/k8s/base_manifest/base_manifest.mdc +14 -0
  477. package/rules/k8s/base_manifest/base_manifest.rego +154 -0
  478. package/rules/k8s/base_manifest/concern.json +8 -0
  479. package/rules/k8s/docs/index.md +11 -0
  480. package/rules/k8s/gateway/concern.json +8 -0
  481. package/rules/k8s/gateway/gateway.mdc +31 -0
  482. package/rules/k8s/gateway/gateway.rego +151 -0
  483. package/rules/k8s/hasura_configmap/concern.json +8 -0
  484. package/rules/k8s/hasura_configmap/hasura_configmap.mdc +20 -0
  485. package/rules/k8s/hasura_configmap/hasura_configmap.rego +129 -0
  486. package/rules/k8s/hasura_httproute/concern.json +8 -0
  487. package/rules/k8s/hasura_httproute/hasura_httproute.mdc +16 -0
  488. package/rules/k8s/hasura_httproute/hasura_httproute.rego +298 -0
  489. package/rules/k8s/hpa_pdb/concern.json +8 -0
  490. package/rules/k8s/hpa_pdb/hpa_pdb.mdc +25 -0
  491. package/rules/k8s/hpa_pdb/hpa_pdb.rego +139 -0
  492. package/rules/k8s/kubeconform/concern.json +7 -0
  493. package/rules/k8s/kubeconform/docs/main.md +32 -0
  494. package/rules/k8s/kubeconform/main.mjs +39 -0
  495. package/rules/k8s/kustomization/concern.json +8 -0
  496. package/rules/k8s/kustomization/kustomization.mdc +20 -0
  497. package/rules/k8s/kustomization/kustomization.rego +220 -0
  498. package/rules/k8s/lint_k8s_yml/concern.json +9 -0
  499. package/rules/k8s/lint_k8s_yml/lint_k8s_yml.rego +57 -0
  500. package/rules/k8s/lint_k8s_yml/template/lint-k8s.yml.snippet.yml +43 -0
  501. package/rules/k8s/main.json +1 -0
  502. package/rules/k8s/main.mdc +22 -0
  503. package/rules/k8s/manifest/concern.json +8 -0
  504. package/rules/k8s/manifest/manifest.mdc +17 -0
  505. package/rules/k8s/manifest/manifest.rego +328 -0
  506. package/rules/k8s/manifests/concern.json +6 -0
  507. package/rules/k8s/manifests/docs/fix-manifests.md +47 -0
  508. package/rules/k8s/manifests/fix-manifests.mjs +217 -0
  509. package/rules/k8s/manifests/main.mjs +6838 -0
  510. package/rules/k8s/network_policy/concern.json +8 -0
  511. package/rules/k8s/network_policy/network_policy.mdc +22 -0
  512. package/rules/k8s/network_policy/network_policy.rego +151 -0
  513. package/rules/k8s/network_policy/template/deployment.snippet.yaml +61 -0
  514. package/rules/k8s/network_policy/template/stateful-set.snippet.yaml +69 -0
  515. package/rules/k8s/svc_hl_yaml/concern.json +8 -0
  516. package/rules/k8s/svc_hl_yaml/svc_hl_yaml.mdc +13 -0
  517. package/rules/k8s/svc_hl_yaml/svc_hl_yaml.rego +51 -0
  518. package/rules/k8s/svc_yaml/concern.json +8 -0
  519. package/rules/k8s/svc_yaml/svc_yaml.mdc +12 -0
  520. package/rules/k8s/svc_yaml/svc_yaml.rego +31 -0
  521. package/rules/nginx-default-tpl/docs/index.md +11 -0
  522. package/rules/nginx-default-tpl/main.json +1 -0
  523. package/rules/nginx-default-tpl/main.mdc +8 -0
  524. package/rules/nginx-default-tpl/template/concern.json +17 -0
  525. package/rules/nginx-default-tpl/template/docs/fix-template.md +26 -0
  526. package/rules/nginx-default-tpl/template/docs/main.md +42 -0
  527. package/rules/nginx-default-tpl/template/fix-template.mjs +67 -0
  528. package/rules/nginx-default-tpl/template/main.mjs +505 -0
  529. package/rules/nginx-default-tpl/vscode_extensions/concern.json +9 -0
  530. package/rules/nginx-default-tpl/vscode_extensions/vscode_extensions.mdc +11 -0
  531. package/rules/nginx-default-tpl/vscode_extensions/vscode_extensions.rego +16 -0
  532. package/rules/nginx-default-tpl/vscode_settings/concern.json +9 -0
  533. package/rules/nginx-default-tpl/vscode_settings/vscode_settings.mdc +15 -0
  534. package/rules/nginx-default-tpl/vscode_settings/vscode_settings.rego +36 -0
  535. package/rules/npm-module/docs/index.md +11 -0
  536. package/rules/npm-module/emit_types_config/concern.json +9 -0
  537. package/rules/npm-module/emit_types_config/emit_types_config.mdc +43 -0
  538. package/rules/npm-module/emit_types_config/emit_types_config.rego +28 -0
  539. package/rules/npm-module/emit_types_config/template/tsconfig.emit-types.json.snippet.json +9 -0
  540. package/rules/npm-module/header_doc_pointer/concern.json +5 -0
  541. package/rules/npm-module/header_doc_pointer/docs/main.md +40 -0
  542. package/rules/npm-module/header_doc_pointer/header_doc_pointer.mdc +18 -0
  543. package/rules/npm-module/header_doc_pointer/main.mjs +131 -0
  544. package/rules/npm-module/main.json +1 -0
  545. package/rules/npm-module/main.mdc +38 -0
  546. package/rules/npm-module/npm_package_json/concern.json +9 -0
  547. package/rules/npm-module/npm_package_json/npm_package_json.mdc +57 -0
  548. package/rules/npm-module/npm_package_json/npm_package_json.rego +73 -0
  549. package/rules/npm-module/npm_package_json/template/package.json.snippet.json +1 -0
  550. package/rules/npm-module/npm_publish_yml/concern.json +10 -0
  551. package/rules/npm-module/npm_publish_yml/template/npm-publish.yml.snippet.yml +49 -0
  552. package/rules/npm-module/package_structure/concern.json +5 -0
  553. package/rules/npm-module/package_structure/docs/main.md +37 -0
  554. package/rules/npm-module/package_structure/main.mjs +515 -0
  555. package/rules/npm-module/package_structure/package_structure.mdc +63 -0
  556. package/rules/npm-module/root_package_json/concern.json +9 -0
  557. package/rules/npm-module/root_package_json/root_package_json.mdc +41 -0
  558. package/rules/npm-module/root_package_json/root_package_json.rego +28 -0
  559. package/rules/npm-module/root_package_json/template/package.json.snippet.json +1 -0
  560. package/rules/npm-module/rule_meta/concern.json +8 -0
  561. package/rules/npm-module/rule_meta/docs/main.md +35 -0
  562. package/rules/npm-module/rule_meta/main.mjs +119 -0
  563. package/rules/npm-module/rule_meta/rule_meta.mdc +11 -0
  564. package/rules/npm-module/skill_meta/concern.json +5 -0
  565. package/rules/npm-module/skill_meta/docs/main.md +149 -0
  566. package/rules/npm-module/skill_meta/main.mjs +91 -0
  567. package/rules/npm-module/skill_meta/skill_meta.mdc +11 -0
  568. package/rules/php/cs_fixer/concern.json +7 -0
  569. package/rules/php/cs_fixer/docs/main.md +34 -0
  570. package/rules/php/cs_fixer/main.mjs +52 -0
  571. package/rules/php/docs/index.md +11 -0
  572. package/rules/php/lint_php_yml/concern.json +9 -0
  573. package/rules/php/lint_php_yml/lint_php_yml.mdc +23 -0
  574. package/rules/php/lint_php_yml/lint_php_yml.rego +34 -0
  575. package/rules/php/lint_php_yml/template/lint-php.yml.snippet.yml +47 -0
  576. package/rules/php/main.json +1 -0
  577. package/rules/php/main.mdc +8 -0
  578. package/rules/php/phpcs/concern.json +7 -0
  579. package/rules/php/phpcs/docs/main.md +31 -0
  580. package/rules/php/phpcs/main.mjs +71 -0
  581. package/rules/php/project/concern.json +6 -0
  582. package/rules/php/project/docs/main.md +36 -0
  583. package/rules/php/project/main.mjs +88 -0
  584. package/rules/php/tooling/concern.json +5 -0
  585. package/rules/php/tooling/docs/main.md +33 -0
  586. package/rules/php/tooling/main.mjs +35 -0
  587. package/rules/php/tooling/tooling.mdc +66 -0
  588. package/rules/python/applies/concern.json +4 -0
  589. package/rules/python/applies/docs/main.md +29 -0
  590. package/rules/python/applies/main.mjs +24 -0
  591. package/rules/python/docs/index.md +11 -0
  592. package/rules/python/lib/docs/index.md +9 -0
  593. package/rules/python/lib/docs/uv-run.md +32 -0
  594. package/rules/python/lib/uv-run.mjs +48 -0
  595. package/rules/python/lint_python_yml/concern.json +9 -0
  596. package/rules/python/lint_python_yml/lint_python_yml.mdc +12 -0
  597. package/rules/python/lint_python_yml/lint_python_yml.rego +68 -0
  598. package/rules/python/lint_python_yml/template/lint-python.yml.snippet.yml +41 -0
  599. package/rules/python/main.json +1 -0
  600. package/rules/python/main.mdc +8 -0
  601. package/rules/python/mypy/concern.json +7 -0
  602. package/rules/python/mypy/docs/main.md +32 -0
  603. package/rules/python/mypy/main.mjs +36 -0
  604. package/rules/python/project/concern.json +6 -0
  605. package/rules/python/project/docs/main.md +43 -0
  606. package/rules/python/project/main.mjs +115 -0
  607. package/rules/python/pyproject_toml/concern.json +9 -0
  608. package/rules/python/pyproject_toml/pyproject_toml.mdc +13 -0
  609. package/rules/python/pyproject_toml/pyproject_toml.rego +53 -0
  610. package/rules/python/pyproject_toml/template/pyproject.toml.deny.toml +2 -0
  611. package/rules/python/pyproject_toml/template/pyproject.toml.snippet.toml +12 -0
  612. package/rules/python/ruff/concern.json +7 -0
  613. package/rules/python/ruff/docs/fix-ruff.md +34 -0
  614. package/rules/python/ruff/docs/main.md +35 -0
  615. package/rules/python/ruff/fix-ruff.mjs +63 -0
  616. package/rules/python/ruff/main.mjs +52 -0
  617. package/rules/python/tooling/concern.json +15 -0
  618. package/rules/python/tooling/docs/index.md +11 -0
  619. package/rules/python/tooling/docs/main.md +35 -0
  620. package/rules/python/tooling/main.mjs +50 -0
  621. package/rules/python/tooling/tooling.mdc +23 -0
  622. package/rules/rego/conftest_verify/concern.json +6 -0
  623. package/rules/rego/conftest_verify/docs/main.md +31 -0
  624. package/rules/rego/conftest_verify/main.mjs +55 -0
  625. package/rules/rego/docs/index.md +11 -0
  626. package/rules/rego/lib/docs/index.md +9 -0
  627. package/rules/rego/lib/docs/run-external-tool.md +36 -0
  628. package/rules/rego/lib/run-external-tool.mjs +53 -0
  629. package/rules/rego/main.json +1 -0
  630. package/rules/rego/main.mdc +20 -0
  631. package/rules/rego/opa_check/concern.json +7 -0
  632. package/rules/rego/opa_check/docs/main.md +32 -0
  633. package/rules/rego/opa_check/main.mjs +34 -0
  634. package/rules/rego/package_json/concern.json +10 -0
  635. package/rules/rego/package_json/package_json.mdc +12 -0
  636. package/rules/rego/package_json/package_json.rego +21 -0
  637. package/rules/rego/regal/concern.json +7 -0
  638. package/rules/rego/regal/docs/index.md +11 -0
  639. package/rules/rego/regal/docs/main.md +32 -0
  640. package/rules/rego/regal/main.mjs +34 -0
  641. package/rules/rego/tooling/concern.json +5 -0
  642. package/rules/rego/tooling/docs/index.md +11 -0
  643. package/rules/rego/tooling/docs/main.md +31 -0
  644. package/rules/rego/tooling/main.mjs +25 -0
  645. package/rules/rego/tooling/tooling.mdc +14 -0
  646. package/rules/rego/vscode_extensions/concern.json +11 -0
  647. package/rules/rego/vscode_extensions/docs/fix-vscode_extensions.md +24 -0
  648. package/rules/rego/vscode_extensions/docs/index.md +11 -0
  649. package/rules/rego/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  650. package/rules/rego/vscode_extensions/template/extensions.json.snippet.json +1 -0
  651. package/rules/rego/vscode_extensions/vscode_extensions.mdc +11 -0
  652. package/rules/rego/vscode_extensions/vscode_extensions.rego +15 -0
  653. package/rules/rego/vscode_settings/concern.json +11 -0
  654. package/rules/rego/vscode_settings/template/settings.json.snippet.json +6 -0
  655. package/rules/rego/vscode_settings/vscode_settings.mdc +19 -0
  656. package/rules/rego/vscode_settings/vscode_settings.rego +30 -0
  657. package/rules/release/change.mjs +88 -0
  658. package/rules/release/docs/change.md +189 -0
  659. package/rules/release/docs/index.md +13 -0
  660. package/rules/release/docs/release.md +25 -0
  661. package/rules/release/lib/aggregate.mjs +82 -0
  662. package/rules/release/lib/change-file.mjs +116 -0
  663. package/rules/release/lib/docs/aggregate.md +250 -0
  664. package/rules/release/lib/docs/change-file.md +206 -0
  665. package/rules/release/lib/docs/fallback.md +207 -0
  666. package/rules/release/lib/docs/index.md +13 -0
  667. package/rules/release/lib/fallback.mjs +48 -0
  668. package/rules/release/main.json +1 -0
  669. package/rules/release/main.mdc +10 -0
  670. package/rules/release/release.mjs +220 -0
  671. package/rules/rust/applies/concern.json +4 -0
  672. package/rules/rust/applies/docs/index.md +11 -0
  673. package/rules/rust/applies/docs/main.md +30 -0
  674. package/rules/rust/applies/main.mjs +28 -0
  675. package/rules/rust/check/concern.json +7 -0
  676. package/rules/rust/check/docs/fix-check.md +30 -0
  677. package/rules/rust/check/docs/index.md +11 -0
  678. package/rules/rust/check/docs/main.md +37 -0
  679. package/rules/rust/check/fix-check.mjs +79 -0
  680. package/rules/rust/check/main.mjs +81 -0
  681. package/rules/rust/coverage/coverage.mdc +28 -0
  682. package/rules/rust/docs/index.md +11 -0
  683. package/rules/rust/lib/docs/has-cargo-toml.md +134 -0
  684. package/rules/rust/lib/docs/index.md +11 -0
  685. package/rules/rust/lib/has-cargo-toml.mjs +37 -0
  686. package/rules/rust/lint_rust_yml/concern.json +11 -0
  687. package/rules/rust/lint_rust_yml/lint_rust_yml.mdc +17 -0
  688. package/rules/rust/lint_rust_yml/lint_rust_yml.rego +79 -0
  689. package/rules/rust/lint_rust_yml/template/lint-rust.yml.snippet.yml +44 -0
  690. package/rules/rust/main.json +1 -0
  691. package/rules/rust/main.mdc +8 -0
  692. package/rules/rust/package_json/concern.json +10 -0
  693. package/rules/rust/package_json/package_json.mdc +12 -0
  694. package/rules/rust/package_json/package_json.rego +20 -0
  695. package/rules/rust/toolchain_cache/concern.json +5 -0
  696. package/rules/rust/toolchain_cache/docs/fix-toolchain_cache.md +35 -0
  697. package/rules/rust/toolchain_cache/docs/index.md +12 -0
  698. package/rules/rust/toolchain_cache/docs/main.md +57 -0
  699. package/rules/rust/toolchain_cache/fix-toolchain_cache.mjs +117 -0
  700. package/rules/rust/toolchain_cache/main.mjs +177 -0
  701. package/rules/rust/toolchain_cache/toolchain_cache.mdc +23 -0
  702. package/rules/rust/vscode_extensions/concern.json +11 -0
  703. package/rules/rust/vscode_extensions/docs/fix-vscode_extensions.md +25 -0
  704. package/rules/rust/vscode_extensions/docs/index.md +11 -0
  705. package/rules/rust/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  706. package/rules/rust/vscode_extensions/template/extensions.json.snippet.json +1 -0
  707. package/rules/rust/vscode_extensions/vscode_extensions.mdc +9 -0
  708. package/rules/rust/vscode_extensions/vscode_extensions.rego +15 -0
  709. package/rules/security/docs/index.md +11 -0
  710. package/rules/security/lint_security_yml/concern.json +11 -0
  711. package/rules/security/lint_security_yml/lint_security_yml.mdc +7 -0
  712. package/rules/security/lint_security_yml/lint_security_yml.rego +33 -0
  713. package/rules/security/lint_security_yml/template/lint-security.yml.snippet.yml +31 -0
  714. package/rules/security/main.json +1 -0
  715. package/rules/security/main.mdc +15 -0
  716. package/rules/security/package_json/concern.json +9 -0
  717. package/rules/security/package_json/package_json.mdc +7 -0
  718. package/rules/security/package_json/package_json.rego +19 -0
  719. package/rules/security/package_json/template/package.json.deny.json +4 -0
  720. package/rules/security/sample_secret/concern.json +11 -0
  721. package/rules/security/sample_secret/docs/index.md +11 -0
  722. package/rules/security/sample_secret/docs/main.md +29 -0
  723. package/rules/security/sample_secret/main.mjs +80 -0
  724. package/rules/security/sample_secret/sample_secret.mdc +20 -0
  725. package/rules/security/scan/concern.json +6 -0
  726. package/rules/security/scan/docs/index.md +11 -0
  727. package/rules/security/scan/docs/main.md +32 -0
  728. package/rules/security/scan/main.mjs +48 -0
  729. package/rules/security/todo.MD +27 -0
  730. package/rules/security/trufflehog/concern.json +4 -0
  731. package/rules/security/trufflehog/docs/index.md +11 -0
  732. package/rules/security/trufflehog/docs/main.md +28 -0
  733. package/rules/security/trufflehog/main.mjs +45 -0
  734. package/rules/security/trufflehog/templates/trufflehog/.trufflehog-exclude.snippet.txt +6 -0
  735. package/rules/security/trufflehog/trufflehog.mdc +21 -0
  736. package/rules/style/docs/index.md +11 -0
  737. package/rules/style/lint/concern.json +7 -0
  738. package/rules/style/lint/docs/fix-lint.md +29 -0
  739. package/rules/style/lint/docs/index.md +11 -0
  740. package/rules/style/lint/docs/main.md +29 -0
  741. package/rules/style/lint/fix-lint.mjs +66 -0
  742. package/rules/style/lint/main.mjs +68 -0
  743. package/rules/style/lint_style_yml/concern.json +9 -0
  744. package/rules/style/lint_style_yml/lint_style_yml.mdc +13 -0
  745. package/rules/style/lint_style_yml/lint_style_yml.rego +38 -0
  746. package/rules/style/lint_style_yml/template/lint-style.yml.snippet.yml +39 -0
  747. package/rules/style/main.json +1 -0
  748. package/rules/style/main.mdc +13 -0
  749. package/rules/style/package_json/concern.json +9 -0
  750. package/rules/style/package_json/package_json.mdc +18 -0
  751. package/rules/style/package_json/package_json.rego +31 -0
  752. package/rules/style/package_json/template/package.json.snippet.json +5 -0
  753. package/rules/style/tooling/concern.json +14 -0
  754. package/rules/style/tooling/docs/fix-tooling.md +29 -0
  755. package/rules/style/tooling/docs/index.md +12 -0
  756. package/rules/style/tooling/docs/main.md +34 -0
  757. package/rules/style/tooling/fix-tooling.mjs +58 -0
  758. package/rules/style/tooling/main.mjs +78 -0
  759. package/rules/style/tooling/tooling.mdc +85 -0
  760. package/rules/style/vscode_extensions/concern.json +9 -0
  761. package/rules/style/vscode_extensions/docs/fix-vscode_extensions.md +25 -0
  762. package/rules/style/vscode_extensions/docs/index.md +11 -0
  763. package/rules/style/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  764. package/rules/style/vscode_extensions/template/extensions.json.snippet.json +1 -0
  765. package/rules/style/vscode_extensions/vscode_extensions.mdc +13 -0
  766. package/rules/style/vscode_extensions/vscode_extensions.rego +13 -0
  767. package/rules/style/vscode_settings/concern.json +9 -0
  768. package/rules/style/vscode_settings/template/settings.json.snippet.json +5 -0
  769. package/rules/style/vscode_settings/vscode_settings.mdc +19 -0
  770. package/rules/style/vscode_settings/vscode_settings.rego +15 -0
  771. package/rules/tauri/cargo_mutants_config/cargo_mutants_config.mdc +39 -0
  772. package/rules/tauri/cargo_mutants_config/concern.json +5 -0
  773. package/rules/tauri/cargo_mutants_config/docs/fix-cargo_mutants_config.md +30 -0
  774. package/rules/tauri/cargo_mutants_config/docs/index.md +12 -0
  775. package/rules/tauri/cargo_mutants_config/docs/main.md +30 -0
  776. package/rules/tauri/cargo_mutants_config/fix-cargo_mutants_config.mjs +53 -0
  777. package/rules/tauri/cargo_mutants_config/main.mjs +148 -0
  778. package/rules/tauri/core_test_isolation/concern.json +5 -0
  779. package/rules/tauri/core_test_isolation/core_test_isolation.mdc +43 -0
  780. package/rules/tauri/core_test_isolation/docs/index.md +9 -0
  781. package/rules/tauri/core_test_isolation/docs/main.md +36 -0
  782. package/rules/tauri/core_test_isolation/main.mjs +166 -0
  783. package/rules/tauri/docs/index.md +11 -0
  784. package/rules/tauri/linux_deps/concern.json +8 -0
  785. package/rules/tauri/linux_deps/docs/fix-linux_deps.md +29 -0
  786. package/rules/tauri/linux_deps/docs/index.md +10 -0
  787. package/rules/tauri/linux_deps/docs/main.md +36 -0
  788. package/rules/tauri/linux_deps/fix-linux_deps.mjs +89 -0
  789. package/rules/tauri/linux_deps/linux_deps.mdc +18 -0
  790. package/rules/tauri/linux_deps/main.mjs +100 -0
  791. package/rules/tauri/main.json +1 -0
  792. package/rules/tauri/main.mdc +86 -0
  793. package/rules/tauri/release/concern.json +8 -0
  794. package/rules/tauri/release/docs/fix-release.md +28 -0
  795. package/rules/tauri/release/docs/index.md +10 -0
  796. package/rules/tauri/release/docs/main.md +32 -0
  797. package/rules/tauri/release/fix-release.mjs +353 -0
  798. package/rules/tauri/release/main.mjs +315 -0
  799. package/rules/tauri/release/release.mdc +104 -0
  800. package/rules/tauri/tooling/concern.json +8 -0
  801. package/rules/tauri/tooling/docs/index.md +11 -0
  802. package/rules/tauri/tooling/docs/main.md +34 -0
  803. package/rules/tauri/tooling/main.mjs +94 -0
  804. package/rules/tauri/tooling/tooling.mdc +27 -0
  805. package/rules/tauri/updater/concern.json +14 -0
  806. package/rules/tauri/updater/docs/fix-updater.md +33 -0
  807. package/rules/tauri/updater/docs/index.md +10 -0
  808. package/rules/tauri/updater/docs/main.md +32 -0
  809. package/rules/tauri/updater/fix-updater.mjs +322 -0
  810. package/rules/tauri/updater/main.mjs +403 -0
  811. package/rules/tauri/updater/updater.mdc +90 -0
  812. package/rules/tauri/vscode_extensions/concern.json +9 -0
  813. package/rules/tauri/vscode_extensions/docs/fix-vscode_extensions.md +25 -0
  814. package/rules/tauri/vscode_extensions/docs/index.md +11 -0
  815. package/rules/tauri/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  816. package/rules/tauri/vscode_extensions/vscode_extensions.mdc +23 -0
  817. package/rules/tauri/vscode_extensions/vscode_extensions.rego +26 -0
  818. package/rules/test/cargo_mutants_config/cargo_mutants_config.mdc +18 -0
  819. package/rules/test/cargo_mutants_config/concern.json +8 -0
  820. package/rules/test/cargo_mutants_config/data/cargo_mutants_config/mutants.toml.baseline +7 -0
  821. package/rules/test/cargo_mutants_config/docs/fix-cargo_mutants_config.md +31 -0
  822. package/rules/test/cargo_mutants_config/docs/index.md +12 -0
  823. package/rules/test/cargo_mutants_config/docs/main.md +36 -0
  824. package/rules/test/cargo_mutants_config/fix-cargo_mutants_config.mjs +49 -0
  825. package/rules/test/cargo_mutants_config/main.mjs +62 -0
  826. package/rules/test/docs/index.md +11 -0
  827. package/rules/test/location/concern.json +8 -0
  828. package/rules/test/location/docs/index.md +11 -0
  829. package/rules/test/location/docs/main.md +36 -0
  830. package/rules/test/location/location.mdc +52 -0
  831. package/rules/test/location/main.mjs +70 -0
  832. package/rules/test/main.json +1 -0
  833. package/rules/test/main.mdc +26 -0
  834. package/rules/test/no-bun-test-import/concern.json +7 -0
  835. package/rules/test/no-bun-test-import/docs/fix-no-bun-test-import.md +31 -0
  836. package/rules/test/no-bun-test-import/docs/index.md +10 -0
  837. package/rules/test/no-bun-test-import/docs/main.md +36 -0
  838. package/rules/test/no-bun-test-import/fix-no-bun-test-import.mjs +51 -0
  839. package/rules/test/no-bun-test-import/main.mjs +105 -0
  840. package/rules/test/no-bun-test-import/no-bun-test-import.mdc +9 -0
  841. package/rules/test/no-console-store-restore/concern.json +7 -0
  842. package/rules/test/no-console-store-restore/docs/index.md +11 -0
  843. package/rules/test/no-console-store-restore/docs/main.md +44 -0
  844. package/rules/test/no-console-store-restore/main.mjs +88 -0
  845. package/rules/test/no-console-store-restore/no-console-store-restore.mdc +11 -0
  846. package/rules/test/no-process-chdir/concern.json +7 -0
  847. package/rules/test/no-process-chdir/docs/index.md +11 -0
  848. package/rules/test/no-process-chdir/docs/main.md +32 -0
  849. package/rules/test/no-process-chdir/main.mjs +40 -0
  850. package/rules/test/no-process-chdir/no-process-chdir.mdc +15 -0
  851. package/rules/test/no-relative-fs-path/concern.json +7 -0
  852. package/rules/test/no-relative-fs-path/docs/index.md +11 -0
  853. package/rules/test/no-relative-fs-path/docs/main.md +34 -0
  854. package/rules/test/no-relative-fs-path/main.mjs +239 -0
  855. package/rules/test/no-relative-fs-path/no-relative-fs-path.mdc +22 -0
  856. package/rules/test/package_json/concern.json +11 -0
  857. package/rules/test/package_json/package_json.mdc +18 -0
  858. package/rules/test/package_json/package_json.rego +25 -0
  859. package/rules/test/package_json/template/package.json.contains.json +6 -0
  860. package/rules/test/sandbox-aware-test/concern.json +7 -0
  861. package/rules/test/sandbox-aware-test/docs/index.md +11 -0
  862. package/rules/test/sandbox-aware-test/docs/main.md +55 -0
  863. package/rules/test/sandbox-aware-test/main.mjs +89 -0
  864. package/rules/test/sandbox-aware-test/sandbox-aware-test.mdc +28 -0
  865. package/rules/test/stryker_config/concern.json +8 -0
  866. package/rules/test/stryker_config/data/stryker_config/docs/index.md +13 -0
  867. package/rules/test/stryker_config/data/stryker_config/docs/stryker-vue-macros-ignorer.md +31 -0
  868. package/rules/test/stryker_config/data/stryker_config/docs/stryker.config.baseline.md +31 -0
  869. package/rules/test/stryker_config/data/stryker_config/docs/stryker.config.vue.baseline.md +35 -0
  870. package/rules/test/stryker_config/data/stryker_config/stryker-vue-macros-ignorer.mjs +48 -0
  871. package/rules/test/stryker_config/data/stryker_config/stryker.config.baseline.mjs +18 -0
  872. package/rules/test/stryker_config/data/stryker_config/stryker.config.vue.baseline.mjs +23 -0
  873. package/rules/test/stryker_config/data/vitest_config/docs/index.md +11 -0
  874. package/rules/test/stryker_config/data/vitest_config/docs/vitest.config.baseline.md +35 -0
  875. package/rules/test/stryker_config/data/vitest_config/vitest.config.baseline.js +22 -0
  876. package/rules/test/stryker_config/docs/fix-stryker_config.md +42 -0
  877. package/rules/test/stryker_config/docs/index.md +12 -0
  878. package/rules/test/stryker_config/docs/main.md +38 -0
  879. package/rules/test/stryker_config/fix-stryker_config.mjs +76 -0
  880. package/rules/test/stryker_config/main.mjs +504 -0
  881. package/rules/test/stryker_config/stryker_config.mdc +26 -0
  882. package/rules/test/vitest-config-pool-forks/concern.json +7 -0
  883. package/rules/test/vitest-config-pool-forks/docs/index.md +11 -0
  884. package/rules/test/vitest-config-pool-forks/docs/main.md +42 -0
  885. package/rules/test/vitest-config-pool-forks/main.mjs +41 -0
  886. package/rules/test/vitest-config-pool-forks/vitest-config-pool-forks.mdc +34 -0
  887. package/rules/text/cspell/concern.json +9 -0
  888. package/rules/text/cspell/cspell.mdc +41 -0
  889. package/rules/text/cspell/cspell.rego +71 -0
  890. package/rules/text/cspell/docs/fix-cspell.md +36 -0
  891. package/rules/text/cspell/docs/index.md +11 -0
  892. package/rules/text/cspell/fix-cspell.mjs +115 -0
  893. package/rules/text/cspell/template/.cspell.json.contains.json +3 -0
  894. package/rules/text/cspell/template/.cspell.json.deny.json +5 -0
  895. package/rules/text/cspell/template/.cspell.json.snippet.json +15 -0
  896. package/rules/text/cspell-fix/concern.json +7 -0
  897. package/rules/text/cspell-fix/docs/fix-worker.md +38 -0
  898. package/rules/text/cspell-fix/docs/index.md +12 -0
  899. package/rules/text/cspell-fix/docs/main.md +32 -0
  900. package/rules/text/cspell-fix/fix-worker.mjs +73 -0
  901. package/rules/text/cspell-fix/main.mjs +185 -0
  902. package/rules/text/docs/index.md +11 -0
  903. package/rules/text/forbidden-prettier/concern.json +7 -0
  904. package/rules/text/forbidden-prettier/docs/index.md +11 -0
  905. package/rules/text/forbidden-prettier/docs/main.md +38 -0
  906. package/rules/text/forbidden-prettier/forbidden-prettier.mdc +14 -0
  907. package/rules/text/forbidden-prettier/main.mjs +56 -0
  908. package/rules/text/formatting/concern.json +17 -0
  909. package/rules/text/formatting/docs/index.md +11 -0
  910. package/rules/text/formatting/docs/main.md +39 -0
  911. package/rules/text/formatting/main.mjs +149 -0
  912. package/rules/text/lint_text/concern.json +8 -0
  913. package/rules/text/lint_text/lint_text.mdc +19 -0
  914. package/rules/text/lint_text/lint_text.rego +104 -0
  915. package/rules/text/lint_text/template/lint-text.yml.snippet.yml +61 -0
  916. package/rules/text/main.json +1 -0
  917. package/rules/text/main.mdc +23 -0
  918. package/rules/text/markdownlint/concern.json +12 -0
  919. package/rules/text/markdownlint/docs/fix-markdownlint.md +38 -0
  920. package/rules/text/markdownlint/docs/index.md +12 -0
  921. package/rules/text/markdownlint/docs/main.md +40 -0
  922. package/rules/text/markdownlint/fix-markdownlint.mjs +64 -0
  923. package/rules/text/markdownlint/main.mjs +55 -0
  924. package/rules/text/markdownlint/markdownlint.mdc +40 -0
  925. package/rules/text/markdownlint/markdownlint.rego +57 -0
  926. package/rules/text/markdownlint/template/.markdownlint-cli2.jsonc.snippet.jsonc +11 -0
  927. package/rules/text/oxfmt/concern.json +7 -0
  928. package/rules/text/oxfmt/docs/fix-oxfmt.md +28 -0
  929. package/rules/text/oxfmt/docs/main.md +27 -0
  930. package/rules/text/oxfmt/fix-oxfmt.mjs +52 -0
  931. package/rules/text/oxfmt/main.mjs +48 -0
  932. package/rules/text/oxfmt/oxfmt.mdc +11 -0
  933. package/rules/text/oxfmtrc/concern.json +9 -0
  934. package/rules/text/oxfmtrc/oxfmtrc.mdc +11 -0
  935. package/rules/text/oxfmtrc/oxfmtrc.rego +55 -0
  936. package/rules/text/oxfmtrc/template/.oxfmtrc.json.snippet.json +8 -0
  937. package/rules/text/package_json/concern.json +9 -0
  938. package/rules/text/package_json/package_json.mdc +35 -0
  939. package/rules/text/package_json/package_json.rego +66 -0
  940. package/rules/text/package_json/template/package.json.deny.json +15 -0
  941. package/rules/text/run-dotenv-linter/concern.json +7 -0
  942. package/rules/text/run-dotenv-linter/docs/fix-run-dotenv-linter.md +43 -0
  943. package/rules/text/run-dotenv-linter/docs/index.md +12 -0
  944. package/rules/text/run-dotenv-linter/docs/main.md +35 -0
  945. package/rules/text/run-dotenv-linter/fix-run-dotenv-linter.mjs +64 -0
  946. package/rules/text/run-dotenv-linter/main.mjs +122 -0
  947. package/rules/text/run-shellcheck/concern.json +7 -0
  948. package/rules/text/run-shellcheck/docs/fix-run-shellcheck.md +40 -0
  949. package/rules/text/run-shellcheck/docs/index.md +12 -0
  950. package/rules/text/run-shellcheck/docs/main.md +31 -0
  951. package/rules/text/run-shellcheck/fix-run-shellcheck.mjs +44 -0
  952. package/rules/text/run-shellcheck/main.mjs +245 -0
  953. package/rules/text/run-v8r/concern.json +7 -0
  954. package/rules/text/run-v8r/docs/index.md +11 -0
  955. package/rules/text/run-v8r/docs/main.md +40 -0
  956. package/rules/text/run-v8r/main.mjs +266 -0
  957. package/rules/text/vscode_extensions/concern.json +9 -0
  958. package/rules/text/vscode_extensions/docs/fix-vscode_extensions.md +36 -0
  959. package/rules/text/vscode_extensions/docs/index.md +11 -0
  960. package/rules/text/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  961. package/rules/text/vscode_extensions/template/extensions.json.snippet.json +3 -0
  962. package/rules/text/vscode_extensions/vscode_extensions.mdc +13 -0
  963. package/rules/text/vscode_extensions/vscode_extensions.rego +12 -0
  964. package/rules/text/vscode_settings/concern.json +9 -0
  965. package/rules/text/vscode_settings/template/settings.json.snippet.json +9 -0
  966. package/rules/text/vscode_settings/vscode_settings.mdc +13 -0
  967. package/rules/text/vscode_settings/vscode_settings.rego +34 -0
  968. package/rules/tool-surface/docs/index.md +11 -0
  969. package/rules/tool-surface/main.json +6 -0
  970. package/rules/tool-surface/main.mdc +72 -0
  971. package/rules/vue/docs/index.md +11 -0
  972. package/rules/vue/lib/docs/index.md +11 -0
  973. package/rules/vue/lib/docs/vue-forbidden-imports.md +265 -0
  974. package/rules/vue/lib/vue-forbidden-imports.mjs +264 -0
  975. package/rules/vue/main.json +1 -0
  976. package/rules/vue/main.mdc +18 -0
  977. package/rules/vue/package_json/concern.json +9 -0
  978. package/rules/vue/package_json/package_json.mdc +30 -0
  979. package/rules/vue/package_json/package_json.rego +140 -0
  980. package/rules/vue/packages/concern.json +6 -0
  981. package/rules/vue/packages/docs/index.md +11 -0
  982. package/rules/vue/packages/docs/main.md +35 -0
  983. package/rules/vue/packages/main.mjs +575 -0
  984. package/rules/worktree/docs/index.md +11 -0
  985. package/rules/worktree/main.json +1 -0
  986. package/rules/worktree/main.mdc +100 -0
  987. package/rules/worktree/vscode_settings/concern.json +10 -0
  988. package/rules/worktree/vscode_settings/template/settings.json.snippet.json +8 -0
  989. package/rules/worktree/zed_settings/concern.json +10 -0
  990. package/rules/worktree/zed_settings/template/settings.json.snippet.json +12 -0
  991. package/schemas/any.json +4 -0
  992. package/schemas/concern.json +108 -0
  993. package/schemas/layers-config.json +38 -0
  994. package/schemas/n-rules.json +65 -0
  995. package/schemas/rule-meta.json +39 -0
  996. package/schemas/skill-meta.json +30 -0
  997. package/schemas/target.json +63 -0
  998. package/schemas/v8r-catalog.json +179 -0
  999. package/schemas/vendor/bunfig.json +616 -0
  1000. package/schemas/vendor/cargo-mutants-config.json +158 -0
  1001. package/schemas/vendor/cargo.json +9438 -0
  1002. package/schemas/vendor/claude-code-settings.json +2742 -0
  1003. package/schemas/vendor/codex-hooks.json +148 -0
  1004. package/schemas/vendor/cspell.json +2427 -0
  1005. package/schemas/vendor/github-action.json +695 -0
  1006. package/schemas/vendor/github-workflow.json +2193 -0
  1007. package/schemas/vendor/jsconfig.json +1252 -0
  1008. package/schemas/vendor/jscpd.json +405 -0
  1009. package/schemas/vendor/knip.json +915 -0
  1010. package/schemas/vendor/mcp.json +26 -0
  1011. package/schemas/vendor/mt-config.json +85 -0
  1012. package/schemas/vendor/oxfmtrc.json +810 -0
  1013. package/schemas/vendor/oxlintrc.json +18690 -0
  1014. package/schemas/vendor/package.json +23798 -0
  1015. package/schemas/vendor/prettierrc.json +433 -0
  1016. package/schemas/vendor/quikrun.json +195 -0
  1017. package/schemas/vendor/schema-catalog.json +62 -0
  1018. package/schemas/vendor/tsconfig.json +1535 -0
  1019. package/schemas/vendor/zizmor.json +489 -0
  1020. package/scripts/auto-rules.mjs +444 -0
  1021. package/scripts/auto-skills.mjs +95 -0
  1022. package/scripts/build-agents-commands.mjs +91 -0
  1023. package/scripts/cli-entry.mjs +34 -0
  1024. package/scripts/dispatcher/docs/index.md +11 -0
  1025. package/scripts/docs/auto-rules.md +34 -0
  1026. package/scripts/docs/auto-skills.md +30 -0
  1027. package/scripts/docs/build-agents-commands.md +37 -0
  1028. package/scripts/docs/cli-entry.md +36 -0
  1029. package/scripts/docs/ensure-n-rules-dev-dependencies.md +40 -0
  1030. package/scripts/docs/hook.md +30 -0
  1031. package/scripts/docs/index.md +22 -0
  1032. package/scripts/docs/post-tool-use-check.md +27 -0
  1033. package/scripts/docs/rename-yaml-extensions.md +40 -0
  1034. package/scripts/docs/skills-cli.md +35 -0
  1035. package/scripts/docs/smoke-check-imports.md +31 -0
  1036. package/scripts/docs/sync-claude-config.md +84 -0
  1037. package/scripts/docs/sync-setup-bun-deps-action.md +28 -0
  1038. package/scripts/docs/update-blue-oak.md +28 -0
  1039. package/scripts/docs/upgrade-n-rules-and-install.md +36 -0
  1040. package/scripts/ensure-n-rules-dev-dependencies.mjs +217 -0
  1041. package/scripts/hook.mjs +100 -0
  1042. package/scripts/lib/adr/docs/index.md +12 -0
  1043. package/scripts/lib/adr/docs/normalize-cli.md +36 -0
  1044. package/scripts/lib/adr/docs/normalize-pipeline.md +45 -0
  1045. package/scripts/lib/adr/normalize-cli.mjs +74 -0
  1046. package/scripts/lib/adr/normalize-pipeline.mjs +701 -0
  1047. package/scripts/lib/assert-project-root.mjs +77 -0
  1048. package/scripts/lib/blue-oak.mjs +41 -0
  1049. package/scripts/lib/changed-files.mjs +83 -0
  1050. package/scripts/lib/check-reporter.mjs +27 -0
  1051. package/scripts/lib/collect-test-files.mjs +48 -0
  1052. package/scripts/lib/concern-meta.mjs +140 -0
  1053. package/scripts/lib/diff-added-lines.mjs +86 -0
  1054. package/scripts/lib/discover-check-rules-from-cursor.mjs +40 -0
  1055. package/scripts/lib/discover-checkable-rules.mjs +49 -0
  1056. package/scripts/lib/docs/assert-project-root.md +30 -0
  1057. package/scripts/lib/docs/blue-oak.md +29 -0
  1058. package/scripts/lib/docs/changed-files.md +32 -0
  1059. package/scripts/lib/docs/check-reporter.md +179 -0
  1060. package/scripts/lib/docs/collect-test-files.md +32 -0
  1061. package/scripts/lib/docs/concern-meta.md +34 -0
  1062. package/scripts/lib/docs/diff-added-lines.md +38 -0
  1063. package/scripts/lib/docs/discover-check-rules-from-cursor.md +29 -0
  1064. package/scripts/lib/docs/discover-checkable-rules.md +25 -0
  1065. package/scripts/lib/docs/ensure-tool.md +258 -0
  1066. package/scripts/lib/docs/generated-markdown.md +279 -0
  1067. package/scripts/lib/docs/gha-workflow.md +38 -0
  1068. package/scripts/lib/docs/index.md +40 -0
  1069. package/scripts/lib/docs/inline-template-links.md +25 -0
  1070. package/scripts/lib/docs/list-project-rules-mdc.md +27 -0
  1071. package/scripts/lib/docs/list-rule-ids.md +25 -0
  1072. package/scripts/lib/docs/load-cursor-config.md +154 -0
  1073. package/scripts/lib/docs/mirror-parity.md +32 -0
  1074. package/scripts/lib/docs/read-n-rules-config-lite.md +26 -0
  1075. package/scripts/lib/docs/resolve-target-files.md +38 -0
  1076. package/scripts/lib/docs/root-notice.md +29 -0
  1077. package/scripts/lib/docs/rule-meta-helpers.md +38 -0
  1078. package/scripts/lib/docs/rule-meta.md +29 -0
  1079. package/scripts/lib/docs/rule-predicates.md +31 -0
  1080. package/scripts/lib/docs/run-conftest-batch.md +30 -0
  1081. package/scripts/lib/docs/run-lint-step.md +29 -0
  1082. package/scripts/lib/docs/run-standard-lint.md +43 -0
  1083. package/scripts/lib/docs/skill-meta.md +62 -0
  1084. package/scripts/lib/docs/sync-gitignore-worktree.md +26 -0
  1085. package/scripts/lib/docs/template.md +44 -0
  1086. package/scripts/lib/docs/timing-summary.md +25 -0
  1087. package/scripts/lib/docs/workspaces.md +34 -0
  1088. package/scripts/lib/docs/worktree-notice.md +27 -0
  1089. package/scripts/lib/ensure-tool.mjs +352 -0
  1090. package/scripts/lib/fix/docs/index.md +11 -0
  1091. package/scripts/lib/fix/docs/vscode-ext-add.md +33 -0
  1092. package/scripts/lib/fix/vscode-ext-add.mjs +53 -0
  1093. package/scripts/lib/generated-markdown.mjs +71 -0
  1094. package/scripts/lib/gha-workflow.mjs +220 -0
  1095. package/scripts/lib/inline-template-links.mjs +108 -0
  1096. package/scripts/lib/lint-surface/codegen-opa-wrapper.mjs +30 -0
  1097. package/scripts/lib/lint-surface/collateral-veto.mjs +55 -0
  1098. package/scripts/lib/lint-surface/default-worker.mjs +67 -0
  1099. package/scripts/lib/lint-surface/detect.mjs +169 -0
  1100. package/scripts/lib/lint-surface/docs/codegen-opa-wrapper.md +30 -0
  1101. package/scripts/lib/lint-surface/docs/collateral-veto.md +28 -0
  1102. package/scripts/lib/lint-surface/docs/default-worker.md +32 -0
  1103. package/scripts/lib/lint-surface/docs/detect.md +38 -0
  1104. package/scripts/lib/lint-surface/docs/index.md +27 -0
  1105. package/scripts/lib/lint-surface/docs/ladder.md +33 -0
  1106. package/scripts/lib/lint-surface/docs/lint-lock.md +47 -0
  1107. package/scripts/lib/lint-surface/docs/mt-run-node.md +37 -0
  1108. package/scripts/lib/lint-surface/docs/mt-tail.md +41 -0
  1109. package/scripts/lib/lint-surface/docs/policy-lint-adapter.md +44 -0
  1110. package/scripts/lib/lint-surface/docs/policy-test-step.md +43 -0
  1111. package/scripts/lib/lint-surface/docs/progress.md +28 -0
  1112. package/scripts/lib/lint-surface/docs/render.md +29 -0
  1113. package/scripts/lib/lint-surface/docs/run-detectors.md +32 -0
  1114. package/scripts/lib/lint-surface/docs/run-fix.md +33 -0
  1115. package/scripts/lib/lint-surface/docs/snapshot.md +30 -0
  1116. package/scripts/lib/lint-surface/docs/tier-sampling-bench.md +40 -0
  1117. package/scripts/lib/lint-surface/docs/tier-sampling-experiment.md +35 -0
  1118. package/scripts/lib/lint-surface/docs/types.md +27 -0
  1119. package/scripts/lib/lint-surface/docs/violation-reporter.md +32 -0
  1120. package/scripts/lib/lint-surface/ladder.mjs +92 -0
  1121. package/scripts/lib/lint-surface/lint-lock.mjs +247 -0
  1122. package/scripts/lib/lint-surface/mt-run-node.mjs +206 -0
  1123. package/scripts/lib/lint-surface/mt-tail.mjs +0 -0
  1124. package/scripts/lib/lint-surface/policy-lint-adapter.mjs +101 -0
  1125. package/scripts/lib/lint-surface/policy-test-step.mjs +149 -0
  1126. package/scripts/lib/lint-surface/progress.mjs +198 -0
  1127. package/scripts/lib/lint-surface/render.mjs +50 -0
  1128. package/scripts/lib/lint-surface/run-detectors.mjs +314 -0
  1129. package/scripts/lib/lint-surface/run-fix.mjs +732 -0
  1130. package/scripts/lib/lint-surface/snapshot.mjs +83 -0
  1131. package/scripts/lib/lint-surface/tier-sampling-bench.mjs +457 -0
  1132. package/scripts/lib/lint-surface/tier-sampling-experiment.mjs +289 -0
  1133. package/scripts/lib/lint-surface/types.mjs +150 -0
  1134. package/scripts/lib/lint-surface/violation-reporter.mjs +46 -0
  1135. package/scripts/lib/list-project-rules-mdc.mjs +22 -0
  1136. package/scripts/lib/list-rule-ids.mjs +32 -0
  1137. package/scripts/lib/load-cursor-config.mjs +58 -0
  1138. package/scripts/lib/mirror-parity.mjs +62 -0
  1139. package/scripts/lib/read-n-rules-config-lite.mjs +63 -0
  1140. package/scripts/lib/resolve-target-files.mjs +103 -0
  1141. package/scripts/lib/root-notice.mjs +67 -0
  1142. package/scripts/lib/rule-meta-helpers.mjs +103 -0
  1143. package/scripts/lib/rule-meta.mjs +66 -0
  1144. package/scripts/lib/rule-predicates.mjs +114 -0
  1145. package/scripts/lib/run-conftest-batch.mjs +124 -0
  1146. package/scripts/lib/run-lint-step.mjs +38 -0
  1147. package/scripts/lib/run-standard-lint.mjs +50 -0
  1148. package/scripts/lib/skill-meta.mjs +90 -0
  1149. package/scripts/lib/sync-gitignore-worktree.mjs +27 -0
  1150. package/scripts/lib/template.mjs +315 -0
  1151. package/scripts/lib/timing-summary.mjs +63 -0
  1152. package/scripts/lib/workspaces.mjs +109 -0
  1153. package/scripts/lib/worktree-notice.mjs +183 -0
  1154. package/scripts/post-tool-use-check.mjs +82 -0
  1155. package/scripts/rename-yaml-extensions.mjs +170 -0
  1156. package/scripts/skills-cli.mjs +262 -0
  1157. package/scripts/smoke-check-imports.mjs +66 -0
  1158. package/scripts/sync-claude-config.mjs +692 -0
  1159. package/scripts/sync-setup-bun-deps-action.mjs +36 -0
  1160. package/scripts/update-blue-oak.mjs +50 -0
  1161. package/scripts/upgrade-n-rules-and-install.mjs +264 -0
  1162. package/scripts/utils/apply-to-files.mjs +36 -0
  1163. package/scripts/utils/ast-extract.mjs +149 -0
  1164. package/scripts/utils/ast-scan-utils.mjs +206 -0
  1165. package/scripts/utils/docs/apply-to-files.md +38 -0
  1166. package/scripts/utils/docs/ast-extract.md +30 -0
  1167. package/scripts/utils/docs/ast-scan-utils.md +54 -0
  1168. package/scripts/utils/docs/ensure-gitignore-entries.md +32 -0
  1169. package/scripts/utils/docs/find-package-json-paths.md +30 -0
  1170. package/scripts/utils/docs/index.md +23 -0
  1171. package/scripts/utils/docs/lock-cache-dir.md +29 -0
  1172. package/scripts/utils/docs/pass.md +29 -0
  1173. package/scripts/utils/docs/resolve-cargo-manifest.md +27 -0
  1174. package/scripts/utils/docs/resolve-cmd.md +33 -0
  1175. package/scripts/utils/docs/resolve-js-root.md +26 -0
  1176. package/scripts/utils/docs/test-helpers.md +30 -0
  1177. package/scripts/utils/docs/walk-cache.md +31 -0
  1178. package/scripts/utils/docs/walkDir.md +31 -0
  1179. package/scripts/utils/docs/with-lock.md +30 -0
  1180. package/scripts/utils/docs/worktree-fingerprint.md +31 -0
  1181. package/scripts/utils/ensure-gitignore-entries.mjs +30 -0
  1182. package/scripts/utils/find-package-json-paths.mjs +30 -0
  1183. package/scripts/utils/lock-cache-dir.mjs +37 -0
  1184. package/scripts/utils/pass.mjs +9 -0
  1185. package/scripts/utils/resolve-cargo-manifest.mjs +62 -0
  1186. package/scripts/utils/resolve-cmd.mjs +25 -0
  1187. package/scripts/utils/resolve-js-root.mjs +61 -0
  1188. package/scripts/utils/walk-cache.mjs +24 -0
  1189. package/scripts/utils/walkDir.mjs +55 -0
  1190. package/scripts/utils/with-lock.mjs +190 -0
  1191. package/scripts/utils/worktree-fingerprint.mjs +33 -0
  1192. package/skills/adr-normalize/SKILL.md +73 -0
  1193. package/skills/adr-normalize/main.json +1 -0
  1194. package/skills/brainstorming/SKILL.md +165 -0
  1195. package/skills/brainstorming/main.json +1 -0
  1196. package/skills/doc-aggregate/SKILL.md +121 -0
  1197. package/skills/doc-aggregate/js/docs/index.md +11 -0
  1198. package/skills/doc-aggregate/main.json +1 -0
  1199. package/skills/doc-files/.changes/260612-0002.md +6 -0
  1200. package/skills/doc-files/.changes/260612-0006.md +6 -0
  1201. package/skills/doc-files/.changes/260612-0008.md +6 -0
  1202. package/skills/doc-files/.changes/260612-0012.md +6 -0
  1203. package/skills/doc-files/.changes/260612-0031.md +6 -0
  1204. package/skills/doc-files/.changes/260612-0036.md +6 -0
  1205. package/skills/doc-files/.changes/260612-0114.md +6 -0
  1206. package/skills/doc-files/SKILL.md +86 -0
  1207. package/skills/doc-files/main.json +1 -0
  1208. package/skills/lint/SKILL.md +121 -0
  1209. package/skills/lint/main.json +1 -0
  1210. package/skills/llm-patch/SKILL.md +260 -0
  1211. package/skills/llm-patch/main.json +1 -0
  1212. package/skills/publish-telegram/SKILL.md +93 -0
  1213. package/skills/publish-telegram/main.json +1 -0
  1214. package/skills/taze/SKILL.md +114 -0
  1215. package/skills/taze/js/diff.mjs +140 -0
  1216. package/skills/taze/js/docs/diff.md +55 -0
  1217. package/skills/taze/js/docs/index.md +11 -0
  1218. package/skills/taze/main.json +1 -0
  1219. package/types/bin/n-rules.d.ts +2 -0
package/bin/n-rules.js ADDED
@@ -0,0 +1,1709 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * n-rules — CLI завантаження правил та перевірки проєкту
5
+ *
6
+ * Використання:
7
+ * `npx \@7n/rules` — завантажити cursor-правила (синк); якщо в корені вже є `.n-rules.json`,
8
+ * спочатку зчитується конфіг і за потреби дописується `$schema`
9
+ * `npx \@7n/rules rename-yaml-extensions` — k8s `*.yml` → `*.yaml`, `.github` `*.yaml` → `*.yml` (опції: `--dry-run`, `--root=…`; див. bin/rename-yaml-extensions.mjs)
10
+ * `npx \@7n/rules hook --post-tool-use` — PostToolUse hook: per-file lint правила для зміненого файлу (stdin JSON `tool_input.file_path`). Прописується автоматично в `.claude/settings.json`.
11
+ * `npx \@7n/rules hook --stop` — Stop hook: per-file lint по всіх змінених файлах (git diff HEAD + untracked).
12
+ * `npx \@7n/rules lint` — data-driven оркестратор lint+конформності по `rules/<id>/meta.json` (`lint: per-file|full`):
13
+ * за замовчуванням fix-by-default по дельті vs origin (лише `per-file` правила); `--full` =
14
+ * весь репо (`per-file` ∪ `full`); `--no-fix` = без мутацій/LLM (CI); позиційні
15
+ * (не-флаг) аргументи — фільтр правил конформності (мапить колишній `fix <rule>`).
16
+ * CI = `lint --no-fix --full` (весь репо, нуль мутацій/LLM).
17
+ * `npx \@7n/rules skill list` — скіли пакета без синку в проєкт
18
+ * `npx \@7n/rules skill taze` — промпт на stdout
19
+ * `npx \@7n/rules skill cursor taze ["task"]` — Cursor CLI (`cursor-agent -p`)
20
+ * `npx \@7n/rules skill claude taze ["task"]` — Claude Code CLI (`claude -p`)
21
+ *
22
+ * Agent інтеграція: під час синку, окрім `.cursor/rules` і `.claude/commands` (з skills), CLI ще раз
23
+ * синхронізує `.claude/settings.json` (hooks + permissions; merge — користувацькі поля зберігаються)
24
+ * і `.cursor/hooks.json` (Cursor Agent hooks; merge — користувацькі hooks зберігаються).
25
+ * Опт-аут — поле `claude-config: false` у `.n-rules.json`.
26
+ * Pi.dev інтеграція: для кожного skill у `.cursor/skills/<dir>/` CLI генерує
27
+ * `.pi/skills/<dir>/SKILL.md` із frontmatter `name`+`description` (формат pi.dev). Тіло — делегат
28
+ * на джерельний `.cursor/skills/<dir>/SKILL.md`. Always-on, симетрично до `.claude/commands/`.
29
+ *
30
+ * Якщо у корені репозиторію немає .n-rules.json, спочатку перейменовується за наявності nitra-cursor.json;
31
+ * у `.cursor/rules` файли `nitra-*.mdc` перейменовуються на `n-*.mdc`; інакше конфіг створюється автоматично
32
+ * з усіма правилами з каталогу rules/ пакету (їх можна відредагувати після створення). У файлі завжди має бути
33
+ * поле `$schema` з посиланням на JSON Schema пакету (публічний URL для IDE); при зчитуванні конфігу воно додається або виправляється на диску, якщо відсутнє або некоректне.
34
+ * Масиви `rules`, `skills`, `disable-rules` і `disable-skills` при записі сортуються за алфавітом.
35
+ *
36
+ * Файл AGENTS.md у корені: щоразу повністю перезаписується змістом з AGENTS.template.md
37
+ * пакету; список правил у шаблоні будується з файлів *.mdc у .cursor/rules поточного проєкту.
38
+ * Секція команд — з кореневого package.json (scripts) та фіксовані рядки про CLI синхрону/перевірок.
39
+ *
40
+ * Після завантаження: у .cursor/rules видаляються файли *.mdc з префіксом «n-» (керовані
41
+ * пакетом), яких немає у списку rules у .n-rules.json. Інші .mdc у цій директорії залишаються.
42
+ *
43
+ * Composite GitHub Action `.github/actions/setup-bun-deps/action.yml` копіюється з каталогу
44
+ * `github-actions/` пакету при кожному успішному синку (workflows з правил ga / js-lint / text).
45
+ *
46
+ * Skills копіюються з npm/skills пакету лише для id з масиву «skills» у .n-rules.json
47
+ * (у JSON — без префікса, як імена каталогів у rules/ без n-). У пакеті джерело — каталоги
48
+ * skills/<id>/ (без префікса); у проєкті — .cursor/skills/n-<id>/ (префікс n-, як n-*.mdc).
49
+ * Якщо ключа skills немає, за замовчуванням підтягуються всі підкаталоги skills/ (лише імена без префікса n-).
50
+ * Зайві каталоги n-* у .cursor/skills, яких немає у списку, видаляються.
51
+ * Файл `auto.md` у скілі — джерело правди для auto-skills у CLI (`scripts/auto-skills.mjs`)
52
+ * і у проєкт не копіюється; раніше синхронізовані `auto.md` у `.cursor/skills/n-<id>/` CLI
53
+ * не чіпає — їх потрібно прибрати вручну.
54
+ *
55
+ * Якщо в корені є package.json і в ньому ще немає \@7n/rules у devDependencies (і не оголошено
56
+ * в dependencies), CLI дописує devDependencies з діапазоном ^<version> поточного пакету — зручно після npx.
57
+ *
58
+ * Перед копіюванням правил (режим без підкоманди): оновлення \@7n/rules у package.json до
59
+ * останньої версії з npm (крім workspace:/file:/link: тощо), `bun i`, далі файли беруться з
60
+ * `node_modules/@7n/rules`, якщо пакет з’явився після встановлення.
61
+ */
62
+
63
+ import { spawnSync } from 'node:child_process'
64
+ import { existsSync } from 'node:fs'
65
+ import { mkdir, readdir, readFile, rename, rm, unlink, writeFile } from 'node:fs/promises'
66
+ import { basename, dirname, join, resolve } from 'node:path'
67
+ import { cwd, env } from 'node:process'
68
+ import { fileURLToPath } from 'node:url'
69
+
70
+ import { buildAgentsCommandBulletItems } from '../scripts/build-agents-commands.mjs'
71
+ import { formatGeneratedMarkdownLines, renderAgentsTemplate } from '../scripts/lib/generated-markdown.mjs'
72
+ import { appendDiscoveredMdcFiles, inlineTemplateLinks } from '../scripts/lib/inline-template-links.mjs'
73
+ import {
74
+ detectAutoRules,
75
+ detectLegacyRuleIds,
76
+ mergeConfigWithAutoDetected,
77
+ normalizeIdList,
78
+ RULE_MIGRATIONS
79
+ } from '../scripts/auto-rules.mjs'
80
+ import { detectAutoSkills } from '../scripts/auto-skills.mjs'
81
+ import { readSkillMetaRaw } from '../scripts/lib/skill-meta.mjs'
82
+ import { injectWorktreeNotice } from '../scripts/lib/worktree-notice.mjs'
83
+ import { injectRootNotice } from '../scripts/lib/root-notice.mjs'
84
+ import { listProjectRulesMdcFiles } from '../scripts/lib/list-project-rules-mdc.mjs'
85
+ import { ensureNRulesInRootDevDependencies } from '../scripts/ensure-n-rules-dev-dependencies.mjs'
86
+ import { assertCwdIsProjectRoot } from '../scripts/lib/assert-project-root.mjs'
87
+ import { syncClaudeConfig } from '../scripts/sync-claude-config.mjs'
88
+ import { syncGitignoreWorktree } from '../scripts/lib/sync-gitignore-worktree.mjs'
89
+ import { upgradeNRulesToLatestAndBunInstall } from '../scripts/upgrade-n-rules-and-install.mjs'
90
+ import { runRenameYamlExtensionsCli } from './rename-yaml-extensions.mjs'
91
+ import { runSkillsCli } from '../scripts/skills-cli.mjs'
92
+ import { syncSetupBunDepsAction } from '../scripts/sync-setup-bun-deps-action.mjs'
93
+
94
+ const PACKAGE_NAME = '@7n/rules'
95
+ const CONFIG_FILE = '.n-rules.json'
96
+ /** Публічний URL JSON Schema для поля `$schema` у `.n-rules.json` (IDE); вміст правил CLI читає лише з диска пакету */
97
+ const CONFIG_SCHEMA_URL = 'https://unpkg.com/@7n/rules/schemas/n-rules.json'
98
+ const AGENTS_FILE = 'AGENTS.md'
99
+ const AGENTS_TEMPLATE_FILE = 'AGENTS.template.md'
100
+ const RULES_DIR = '.cursor/rules'
101
+ const SKILLS_DIR = '.cursor/skills'
102
+ const COMMANDS_DIR = '.claude/commands'
103
+ const PI_SKILLS_DIR = '.pi/skills'
104
+ const RULE_PREFIX = 'n-'
105
+
106
+ const binDir = dirname(fileURLToPath(import.meta.url))
107
+ const BUNDLED_RULES_DIR = join(binDir, '..', 'rules')
108
+ const BUNDLED_SKILLS_DIR = join(binDir, '..', 'skills')
109
+ const BUNDLED_AGENTS_TEMPLATE_PATH = join(binDir, '..', AGENTS_TEMPLATE_FILE)
110
+ /** Корінь установленого пакету (каталог з `rules/`, `github-actions/`, …) */
111
+ const BUNDLED_PACKAGE_ROOT = join(binDir, '..')
112
+
113
+ const YAML_FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/
114
+ const NEWLINE_RE = /\r?\n/
115
+ const LEADING_SPACES_RE = /^\s+/
116
+
117
+ /** Ключі `.n-rules.json`, де значення — масиви id; після запуску CLI сортуються за алфавітом */
118
+ const CONFIG_SORTED_ARRAY_KEYS = /** @type {const} */ (['rules', 'skills', 'disable-rules', 'disable-skills'])
119
+
120
+ /**
121
+ * Сортує масиви id у конфігу за алфавітом (`localeCompare`), щоб порядок у файлі був стабільним після синку.
122
+ * @param {Record<string, unknown>} config об'єкт конфігу перед записом на диск
123
+ * @returns {Record<string, unknown>} копія з відсортованими масивами для відомих ключів
124
+ */
125
+ function sortConfigIdArrays(config) {
126
+ const out = { ...config }
127
+ for (const key of CONFIG_SORTED_ARRAY_KEYS) {
128
+ const v = out[key]
129
+ if (key in out && Array.isArray(v)) {
130
+ out[key] = v.map(String).toSorted((a, b) => a.localeCompare(b))
131
+ }
132
+ }
133
+ return out
134
+ }
135
+
136
+ /**
137
+ * Імена правил з каталогу `rules/` поточної інсталяції пакету. Кожне правило — окремий
138
+ * підкаталог `rules/<id>/`, у якому має бути `main.mdc`.
139
+ * @param {string} [bundledRulesDir] каталог `rules/` у корені пакету
140
+ * @returns {Promise<string[]>} відсортовані id правил (імена підкаталогів)
141
+ */
142
+ async function discoverBundledRuleNames(bundledRulesDir = BUNDLED_RULES_DIR) {
143
+ if (!existsSync(bundledRulesDir)) {
144
+ throw new Error(
145
+ `Не знайдено каталог правил пакету.\n` +
146
+ `Очікуваний шлях: ${bundledRulesDir}\n` +
147
+ `Перевстановіть ${PACKAGE_NAME} або створіть ${CONFIG_FILE} вручну.`
148
+ )
149
+ }
150
+ const entries = await readdir(bundledRulesDir, { withFileTypes: true })
151
+ const rules = entries
152
+ .filter(e => e.isDirectory() && !e.name.startsWith('.'))
153
+ .filter(e => existsSync(join(bundledRulesDir, e.name, 'main.mdc')))
154
+ .map(e => e.name)
155
+ .toSorted((a, b) => a.localeCompare(b))
156
+ if (rules.length === 0) {
157
+ throw new Error(`У каталозі rules/ пакету немає підкаталогів з main.mdc. Створіть ${CONFIG_FILE} вручну.`)
158
+ }
159
+ return rules
160
+ }
161
+
162
+ /**
163
+ * Імена skills (id без префікса n-) з каталогу skills пакету — лише підкаталоги `<id>/` без префікса n-
164
+ * @param {string} [bundledSkillsDir] каталог `skills/` у корені пакету
165
+ * @returns {Promise<string[]>} відсортовані id
166
+ */
167
+ async function discoverBundledSkillNames(bundledSkillsDir = BUNDLED_SKILLS_DIR) {
168
+ if (!existsSync(bundledSkillsDir)) {
169
+ return []
170
+ }
171
+ const entries = await readdir(bundledSkillsDir, { withFileTypes: true })
172
+ return entries
173
+ .filter(e => e.isDirectory() && !e.name.startsWith('.') && !e.name.startsWith(RULE_PREFIX))
174
+ .map(e => e.name)
175
+ .toSorted((a, b) => a.localeCompare(b))
176
+ }
177
+
178
+ /**
179
+ * Перейменовує у каталозі правил файли `nitra-*.mdc` → `n-*.mdc`. Якщо `n-*.mdc` уже є, застарілий файл видаляється.
180
+ * @param {string} rulesDir абсолютний шлях до `.cursor/rules`
181
+ * @returns {Promise<void>}
182
+ */
183
+ async function migrateLegacyManagedRuleFilenames(rulesDir) {
184
+ if (!existsSync(rulesDir)) {
185
+ return
186
+ }
187
+ const names = await readdir(rulesDir)
188
+ for (const name of names) {
189
+ if (!(name.endsWith('.mdc') && name.startsWith('nitra-'))) {
190
+ continue
191
+ }
192
+
193
+ const rest = name.slice('nitra-'.length)
194
+ const newName = `${RULE_PREFIX}${rest}`
195
+ const from = join(rulesDir, name)
196
+ const to = join(rulesDir, newName)
197
+ if (existsSync(to)) {
198
+ await unlink(from)
199
+ console.log(`📝 Видалено застарілий ${RULES_DIR}/${name} (вже є ${newName})\n`)
200
+ } else {
201
+ await rename(from, to)
202
+ console.log(`📝 Перейменовано ${RULES_DIR}/${name} → ${RULES_DIR}/${newName}\n`)
203
+ }
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Міграція legacy: `nitra-*.mdc` → `n-*.mdc` у `.cursor/rules`; якщо немає `.n-rules.json`, конфіг
209
+ * береться з legacy-імен (`.n-rules.json`, потім `nitra-cursor.json`) з переписуванням `$schema` на новий URL
210
+ * @returns {Promise<void>}
211
+ */
212
+ async function migrateLegacyConfigIfNeeded() {
213
+ const root = cwd()
214
+ await migrateLegacyManagedRuleFilenames(join(root, RULES_DIR))
215
+
216
+ const target = join(root, CONFIG_FILE)
217
+ if (existsSync(target)) {
218
+ return
219
+ }
220
+ for (const legacyName of ['.n-cursor.json', 'nitra-cursor.json']) {
221
+ const legacyPath = join(root, legacyName)
222
+ if (!existsSync(legacyPath)) {
223
+ continue
224
+ }
225
+ await rename(legacyPath, target)
226
+ console.log(`📝 Перейменовано ${legacyName} → ${CONFIG_FILE}\n`)
227
+ try {
228
+ const parsed = JSON.parse(await readFile(target, 'utf8'))
229
+ if (typeof parsed?.$schema === 'string' && parsed.$schema.includes('@nitra/cursor')) {
230
+ parsed.$schema = CONFIG_SCHEMA_URL
231
+ await writeFile(target, `${JSON.stringify(parsed, null, 2)}\n`)
232
+ }
233
+ } catch {
234
+ /* некоректний JSON — $schema виправить подальший readConfig */
235
+ }
236
+ return
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Повертає розпарсений package.json з кореня або null, якщо файл відсутній/некоректний.
242
+ * @returns {Promise<unknown | null>} обʼєкт package.json або null
243
+ */
244
+ async function readRootPackageJsonSafe() {
245
+ const packageJsonPath = join(cwd(), 'package.json')
246
+ if (!existsSync(packageJsonPath)) {
247
+ return null
248
+ }
249
+ try {
250
+ return JSON.parse(await readFile(packageJsonPath, 'utf8'))
251
+ } catch {
252
+ return null
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Зчитує конфіг .n-rules.json з поточної директорії
258
+ * @param {{ bundledRulesDir?: string, bundledSkillsDir?: string }} [paths] каталоги з пакету-джерела (після `bun i` — зазвичай `node_modules/@7n/rules`)
259
+ * @returns {Promise<{ $schema: string, rules: string[], skills: string[], version?: string } & Record<string, unknown>>} rules, skills (id без префікса n-); поле version у файлі за наявності ігнорується при синхронізації правил
260
+ */
261
+ async function readConfig(paths = {}) {
262
+ const bundledRulesDir = paths.bundledRulesDir ?? BUNDLED_RULES_DIR
263
+ const bundledSkillsDir = paths.bundledSkillsDir ?? BUNDLED_SKILLS_DIR
264
+ await migrateLegacyConfigIfNeeded()
265
+ const configPath = join(cwd(), CONFIG_FILE)
266
+ const availableRules = await discoverBundledRuleNames(bundledRulesDir)
267
+ const availableSkills = await discoverBundledSkillNames(bundledSkillsDir)
268
+
269
+ /**
270
+ * Автодописує правила/skills за `rules/<rule>/auto.md` і синхронізує `$schema`.
271
+ * @param {Record<string, unknown>} parsedConfig сирий обʼєкт конфігу
272
+ * @returns {Promise<Record<string, unknown>>} нормалізований конфіг
273
+ */
274
+ async function normalizeConfigWithAutoRules(parsedConfig) {
275
+ const currentRules = parsedConfig.rules
276
+ if (!Array.isArray(currentRules)) {
277
+ throw new TypeError(`У ${CONFIG_FILE} поле "rules" має бути масивом рядків`)
278
+ }
279
+ if ('skills' in parsedConfig && !Array.isArray(parsedConfig.skills)) {
280
+ throw new Error(`У ${CONFIG_FILE} поле "skills" має бути масивом рядків`)
281
+ }
282
+ const { ignore } = parsedConfig
283
+ if (ignore !== undefined && (!Array.isArray(ignore) || ignore.some(p => typeof p !== 'string'))) {
284
+ throw new Error(`У ${CONFIG_FILE} поле "ignore" має бути масивом рядків (шляхів до директорій)`)
285
+ }
286
+
287
+ const rootPkg = await readRootPackageJsonSafe()
288
+ const disableRules = normalizeIdList(parsedConfig['disable-rules'])
289
+ const disableSkills = normalizeIdList(parsedConfig['disable-skills'])
290
+ const autoDetectedRules = await detectAutoRules({
291
+ root: cwd(),
292
+ availableRules,
293
+ packageJsonParsed: rootPkg,
294
+ disableRules
295
+ })
296
+ // Skills залежать від ефективного списку правил, який буде у конфізі після merge:
297
+ // вже існуючі (опт-ін вручну) + auto-detected, мінус `disable-rules`. Без цього
298
+ // правило, додане вручну (напр. `adr` без auto.md-умови), не активувало б залежні
299
+ // скіли (`adr-normalize`).
300
+ const disableRulesSet = new Set(disableRules)
301
+ const effectiveRulesForSkills = [
302
+ ...new Set([...normalizeIdList(parsedConfig.rules), ...autoDetectedRules.rules]).difference(disableRulesSet)
303
+ ]
304
+ const autoDetectedSkills = detectAutoSkills({
305
+ availableSkills,
306
+ detectedRules: effectiveRulesForSkills,
307
+ disableSkills
308
+ })
309
+
310
+ const merged = mergeConfigWithAutoDetected({
311
+ config: parsedConfig,
312
+ detectedRules: autoDetectedRules.rules,
313
+ detectedSkills: autoDetectedSkills.skills,
314
+ availableRules,
315
+ availableSkills
316
+ })
317
+
318
+ if (merged.pruned) {
319
+ const parts = []
320
+ if (merged.pruned.rules.length > 0) parts.push(`rules: ${merged.pruned.rules.join(', ')}`)
321
+ if (merged.pruned.skills.length > 0) parts.push(`skills: ${merged.pruned.skills.join(', ')}`)
322
+ console.log(`🧹 Прибрано з ${CONFIG_FILE} неактуальні (немає у пакеті) — ${parts.join('; ')}\n`)
323
+ }
324
+
325
+ const rest = Object.fromEntries(Object.entries(parsedConfig).filter(([k]) => k !== '$schema'))
326
+ const normalized = {
327
+ $schema: CONFIG_SCHEMA_URL,
328
+ ...rest,
329
+ rules: merged.rules,
330
+ skills: merged.skills
331
+ }
332
+ if (merged['disable-rules']?.length) {
333
+ normalized['disable-rules'] = merged['disable-rules']
334
+ }
335
+ if (merged['disable-skills']?.length) {
336
+ normalized['disable-skills'] = merged['disable-skills']
337
+ }
338
+ return sortConfigIdArrays(normalized)
339
+ }
340
+
341
+ if (!existsSync(configPath)) {
342
+ const rootPkg = await readRootPackageJsonSafe()
343
+ const autoDetectedRules = await detectAutoRules({
344
+ root: cwd(),
345
+ availableRules,
346
+ packageJsonParsed: rootPkg
347
+ })
348
+ const autoDetectedSkills = detectAutoSkills({
349
+ availableSkills,
350
+ detectedRules: autoDetectedRules.rules
351
+ })
352
+ const defaultConfig = sortConfigIdArrays({
353
+ $schema: CONFIG_SCHEMA_URL,
354
+ rules: autoDetectedRules.rules,
355
+ skills: autoDetectedSkills.skills
356
+ })
357
+ await writeFile(configPath, `${JSON.stringify(defaultConfig, null, 2)}\n`, 'utf8')
358
+ console.log(
359
+ `📝 Створено ${CONFIG_FILE} з автоаналізом правил (${defaultConfig.rules.length}) і skills (${defaultConfig.skills.length}).\n`
360
+ )
361
+ return defaultConfig
362
+ }
363
+ const raw = await readFile(configPath, 'utf8')
364
+ /** @type {Record<string, unknown>} */
365
+ let config
366
+ try {
367
+ config = JSON.parse(raw)
368
+ } catch {
369
+ throw new Error(`Невірний JSON у файлі ${CONFIG_FILE}`)
370
+ }
371
+ logRuleMigrationsIfAny(config)
372
+ const normalized = await normalizeConfigWithAutoRules(config)
373
+ if (JSON.stringify(normalized) !== JSON.stringify(config)) {
374
+ await writeFile(configPath, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8')
375
+ console.log(`📝 Оновлено ${CONFIG_FILE}: синхронізовано $schema та авто-додані rules/skills\n`)
376
+ }
377
+ return normalized
378
+ }
379
+
380
+ /**
381
+ * Якщо у `rules` чи `disable-rules` є застарілі rule-id з `RULE_MIGRATIONS`,
382
+ * виводить пояснювальний лог про автоматичну заміну (саму заміну виконує
383
+ * `migrateRuleIds` у `mergeConfigWithAutoDetected` — тут лише користувацька комунікація).
384
+ * @param {Record<string, unknown>} parsedConfig сирий обʼєкт `.n-rules.json` після `JSON.parse`
385
+ * @returns {void}
386
+ */
387
+ function logRuleMigrationsIfAny(parsedConfig) {
388
+ /** @type {Set<string>} */
389
+ const seen = new Set()
390
+ for (const key of /** @type {const} */ (['rules', 'disable-rules'])) {
391
+ const list = parsedConfig[key]
392
+ if (!Array.isArray(list)) continue
393
+ const legacy = detectLegacyRuleIds(normalizeIdList(list))
394
+ for (const id of legacy) seen.add(id)
395
+ }
396
+ if (seen.size === 0) return
397
+ console.log(`📦 Авто-міграція ${CONFIG_FILE}:`)
398
+ for (const id of seen) {
399
+ const replacement = RULE_MIGRATIONS[id].join(', ')
400
+ console.log(` • ${id} → ${replacement}`)
401
+ }
402
+ console.log('')
403
+ }
404
+
405
+ /**
406
+ * Витягує чистий id правила без шляху і без .mdc.
407
+ * "npm/rules/text/text.mdc" → "text"
408
+ * "text.mdc" → "text"
409
+ * "text" → "text"
410
+ * @param {string} ruleName шлях або базове ім'я, з суфіксом .mdc або без
411
+ * @returns {string} id правила (без .mdc, без шляху)
412
+ */
413
+ function normalizeRuleName(ruleName) {
414
+ const name = basename(String(ruleName).trim())
415
+ return name.endsWith('.mdc') ? name.slice(0, -'.mdc'.length) : name
416
+ }
417
+
418
+ /**
419
+ * Читає вміст правила з каталогу `rules/<id>/main.mdc` установленого пакету
420
+ * (наприклад `node_modules/@7n/rules/rules/<id>/main.mdc` або кеш npx).
421
+ * @param {string} rule елемент масиву rules з `.n-rules.json`
422
+ * @param {string} [bundledRulesDir] каталог `rules/` у корені пакету-джерела
423
+ * @returns {Promise<string>} текст правила для запису в `.cursor/rules/n-*.mdc`
424
+ */
425
+ async function readBundledRuleContent(rule, bundledRulesDir = BUNDLED_RULES_DIR) {
426
+ const id = normalizeRuleName(rule)
427
+ const bundledPath = join(bundledRulesDir, id, 'main.mdc')
428
+ if (!existsSync(bundledPath)) {
429
+ throw new Error(
430
+ `Немає файлу ${id}/main.mdc у ${bundledRulesDir}. Оновіть ${PACKAGE_NAME} або приберіть "${rule}" з rules у ${CONFIG_FILE}.`
431
+ )
432
+ }
433
+ const text = await readFile(bundledPath, 'utf8')
434
+ const withTemplates = await inlineTemplateLinks(text, dirname(bundledPath))
435
+ return appendDiscoveredMdcFiles(withTemplates, dirname(bundledPath))
436
+ }
437
+
438
+ /**
439
+ * Нормалізує id skill з конфігу до форми без префікса n- (як «fix»)
440
+ * @param {string} skillName елемент масиву skills або ім'я каталогу
441
+ * @returns {string} id без префікса n-
442
+ */
443
+ function normalizeSkillId(skillName) {
444
+ let s = basename(String(skillName).trim())
445
+ if (s.startsWith(RULE_PREFIX)) {
446
+ s = s.slice(RULE_PREFIX.length)
447
+ }
448
+ return s
449
+ }
450
+
451
+ /**
452
+ * Ім'я керованого каталогу skill у .cursor/skills (префікс n-)
453
+ * @param {string} skillId id без префікса (або з префіксом n- у конфігу — нормалізується)
454
+ * @returns {string} наприклад n-fix
455
+ */
456
+ function managedSkillDirName(skillId) {
457
+ return `${RULE_PREFIX}${normalizeSkillId(skillId)}`
458
+ }
459
+
460
+ /**
461
+ * Витягує текст description з YAML frontmatter SKILL.md (формат description: >-)
462
+ * @param {string} text повний вміст SKILL.md
463
+ * @returns {string | null} один рядок опису або null
464
+ */
465
+ function extractSkillDescription(text) {
466
+ const fm = text.match(YAML_FRONTMATTER_RE)
467
+ if (!fm) {
468
+ return null
469
+ }
470
+ const lines = fm[1].split(NEWLINE_RE)
471
+ const start = lines.findIndex(line => line.trim() === 'description: >-')
472
+ if (start === -1) {
473
+ return null
474
+ }
475
+ const descLines = []
476
+ for (const line of lines.slice(start + 1)) {
477
+ if (!LEADING_SPACES_RE.test(line)) {
478
+ break
479
+ }
480
+ descLines.push(line.replace(LEADING_SPACES_RE, '').trimEnd())
481
+ }
482
+ if (descLines.length === 0) {
483
+ return null
484
+ }
485
+ return descLines.join(' ').trim()
486
+ }
487
+
488
+ /**
489
+ * Підготовка опису skill для вставки в звичайний markdown (заголовок H1, bullet без code fence).
490
+ * Послідовність `<id>` сприймається markdownlint (MD033) як inline HTML — замінюємо на `{id}`.
491
+ * @param {string} desc один рядок з YAML frontmatter SKILL.md
492
+ * @returns {string} той самий рядок після заміни літералу з кутовими дужками навколо id на плейсхолдер у фігурних дужках (MD033).
493
+ */
494
+ function skillDescriptionSafeForMarkdownInline(desc) {
495
+ return desc.replaceAll('<id>', '{id}')
496
+ }
497
+
498
+ /**
499
+ * YAML frontmatter для `.claude/commands/*.md`: поле `description` потрібне розширенню VSCode,
500
+ * щоб команди з’являлись у списку. Текст збігається з полем `description` у frontmatter `SKILL.md`.
501
+ * @param {string} descriptionRaw значення з `extractSkillDescription` (може бути порожнім)
502
+ * @returns {string} блок `---` … `---` і порожній рядок після
503
+ */
504
+ function formatClaudeCommandFrontmatter(descriptionRaw) {
505
+ let text = skillDescriptionSafeForMarkdownInline(String(descriptionRaw || '').trim())
506
+ if (!text) {
507
+ text = 'Див. SKILL.md у каталозі скілу в .cursor/skills.'
508
+ }
509
+ return `---\ndescription: >-\n ${text}\n---\n\n`
510
+ }
511
+
512
+ /**
513
+ * YAML frontmatter для `.pi/skills/<dir>/SKILL.md` згідно зі специфікацією pi.dev:
514
+ * обов'язкові поля `name` (1-64, `[a-z0-9-]`) і `description` (≤ 1024). Текст description збігається
515
+ * з полем `description` у frontmatter джерельного `SKILL.md`.
516
+ * @param {string} skillName ім'я скілу (наприклад `n-fix`); має бути валідним pi-name
517
+ * @param {string} descriptionRaw значення з `extractSkillDescription` (може бути порожнім)
518
+ * @returns {string} блок `---` … `---` і порожній рядок після
519
+ */
520
+ function formatPiSkillFrontmatter(skillName, descriptionRaw) {
521
+ let text = skillDescriptionSafeForMarkdownInline(String(descriptionRaw || '').trim())
522
+ if (!text) {
523
+ text = 'Див. SKILL.md у каталозі скілу в .cursor/skills.'
524
+ }
525
+ return `---\nname: ${skillName}\ndescription: >-\n ${text}\n---\n\n`
526
+ }
527
+
528
+ /**
529
+ * Базові імена файлів .mdc, які очікуються згідно з .n-rules.json (префікс n-).
530
+ * @param {string[]} configRules елементи масиву rules з конфігу
531
+ * @returns {Set<string>} множина очікуваних імен файлів (наприклад n-bun.mdc)
532
+ */
533
+ function expectedManagedRuleBasenames(configRules) {
534
+ return new Set(configRules.map(rule => `${RULE_PREFIX}${normalizeRuleName(rule)}.mdc`))
535
+ }
536
+
537
+ /**
538
+ * Видаляє з каталогу правил файли *.mdc з префіксом n-, яких немає у конфігурації.
539
+ * Файли без префікса n- не змінює.
540
+ * @param {string} rulesDir абсолютний шлях до .cursor/rules
541
+ * @param {string[]} configRules елементи масиву rules з .n-rules.json
542
+ * @returns {Promise<string[]>} відсортовані імена видалених файлів
543
+ */
544
+ async function removeOrphanManagedRuleFiles(rulesDir, configRules) {
545
+ if (!existsSync(rulesDir)) {
546
+ return []
547
+ }
548
+ const expected = expectedManagedRuleBasenames(configRules)
549
+ const names = await readdir(rulesDir)
550
+ const removed = []
551
+ for (const name of names) {
552
+ if (!(name.endsWith('.mdc') && name.startsWith(RULE_PREFIX) && !expected.has(name))) {
553
+ continue
554
+ }
555
+
556
+ await unlink(join(rulesDir, name))
557
+ removed.push(name)
558
+ }
559
+ return removed.toSorted((a, b) => a.localeCompare(b))
560
+ }
561
+
562
+ /**
563
+ * Повертає відсортований список директорій skills у `.cursor/skills`.
564
+ * Директорія вважається skill-каталогом, якщо це підкаталог (без префікса `.`).
565
+ * @returns {Promise<string[]>} імена директорій (наприклад `n-fix`, `custom-skill`)
566
+ */
567
+ async function listProjectSkillDirNames() {
568
+ const skillsRoot = join(cwd(), SKILLS_DIR)
569
+ if (!existsSync(skillsRoot)) {
570
+ return []
571
+ }
572
+ const entries = await readdir(skillsRoot, { withFileTypes: true })
573
+ return entries
574
+ .filter(entry => entry.isDirectory() && !entry.name.startsWith('.'))
575
+ .map(entry => entry.name)
576
+ .toSorted((a, b) => a.localeCompare(b))
577
+ }
578
+
579
+ /**
580
+ * Формує markdown-рядки для секції Skills у AGENTS.md з усіх skill-директорій на диску.
581
+ * @returns {Promise<{ name: string }[]>} елементи з полем name для Mustache-секції skills
582
+ */
583
+ async function buildSkillBulletItems() {
584
+ const skillsRoot = join(cwd(), SKILLS_DIR)
585
+ const skillDirNames = await listProjectSkillDirNames()
586
+ const items = []
587
+ for (const dirName of skillDirNames) {
588
+ const skillMdPath = join(skillsRoot, dirName, 'SKILL.md')
589
+ let desc = ''
590
+ if (existsSync(skillMdPath)) {
591
+ const text = await readFile(skillMdPath, 'utf8')
592
+ const parsed = extractSkillDescription(text)
593
+ if (parsed) {
594
+ desc = skillDescriptionSafeForMarkdownInline(parsed)
595
+ }
596
+ }
597
+ const pathLine = `- \`${SKILLS_DIR}/${dirName}/SKILL.md\``
598
+ const line = desc ? `${pathLine} — ${desc}` : pathLine
599
+ items.push({ name: line })
600
+ }
601
+ return items
602
+ }
603
+
604
+ /**
605
+ * Видаляє каталоги n-* у .cursor/skills, яких немає у конфігурації skills
606
+ * @param {string} skillsRoot абсолютний шлях до .cursor/skills
607
+ * @param {string[]} configSkills елементи масиву skills з .n-rules.json
608
+ * @returns {Promise<string[]>} імена видалених каталогів
609
+ */
610
+ async function removeOrphanManagedSkillDirs(skillsRoot, configSkills) {
611
+ if (!existsSync(skillsRoot)) {
612
+ return []
613
+ }
614
+ const expected = new Set(configSkills.map(s => managedSkillDirName(s)))
615
+ const entries = await readdir(skillsRoot, { withFileTypes: true })
616
+ const removed = []
617
+ for (const e of entries) {
618
+ const isManagedDir = e.isDirectory() && e.name.startsWith(RULE_PREFIX)
619
+ const isOrphan = isManagedDir && !expected.has(e.name)
620
+ if (isOrphan) {
621
+ await rm(join(skillsRoot, e.name), { recursive: true, force: true })
622
+ removed.push(e.name)
623
+ }
624
+ }
625
+ return removed.toSorted((a, b) => a.localeCompare(b))
626
+ }
627
+
628
+ /**
629
+ * Рендерить коротку секцію для CLAUDE.md: full-прогони lint мають вбудовану
630
+ * глобальну чергу з видимим прогресом, дельта-запуски йдуть паралельно без лока.
631
+ * @returns {string[]} рядки для вставки (з порожнім рядком на початку)
632
+ */
633
+ function buildClaudeLintParallelismSectionLines() {
634
+ return [
635
+ '',
636
+ '## Лінт і ESLint (паралелізм)',
637
+ '',
638
+ 'Дельта-`lint` (типовий задачний прогін) — **без черги**, паралельні запуски по різних файлах дозволені. `npx @7n/rules lint --full` має **вбудовану глобальну чергу** (spec 2026-07-03): один full-прогін на машину; запуск у черзі показує свою позицію, решту черги і живий прогрес-бар активного прогону (`⏳ lint --full у черзі #… · працює pid … [██…] 5/12 · …` — штатна черга, не зависання; fail-closed таймаут 45 хв). Ідентичний повтор --full на незміненому дереві дедуплікується (`♻️ … пропускаю`). Координувати запуски вручну не треба. Деталі: `.cursor/skills/n-lint/SKILL.md`.',
639
+ ''
640
+ ]
641
+ }
642
+
643
+ /**
644
+ * Рендерить секцію для CLAUDE.md: скіли з `main.json.worktree === true` запускаються
645
+ * лише в окремому git-worktree (дублює fail-fast банер `SKILL.md` як завжди-читане правило).
646
+ * @returns {string[]} рядки для вставки (з порожнім рядком на початку)
647
+ */
648
+ function buildClaudeWorktreeEnforcementSectionLines() {
649
+ return [
650
+ '',
651
+ '## Worktree-only skills (`main.json` → `worktree: true`)',
652
+ '',
653
+ 'Скіл із **`worktree: true`** у `main.json` запускається **виключно** в окремому git-worktree (`.worktrees/<current-branch>-<suffix>/`) — **не** в основному дереві й **не** паралельно. Перший крок такого скіла (блок `n-rules:worktree:start` у його `SKILL.md`) — **preflight**: якщо `git rev-parse --show-toplevel` не вказує під `.worktrees/`, **STOP** і не питай користувача про назву гілки; створи worktree від поточної гілки готовим snippet з `SKILL.md` за конвенцією `<current-branch>-<suffix>` і без shell expansion (без command substitution, variable expansion чи backticks). Чисте робоче дерево — **не** привід пропустити preflight.',
654
+ ''
655
+ ]
656
+ }
657
+
658
+ /**
659
+ * Рендерить секцію для CLAUDE.md: doc-files — обовʼязковий крок задачі (як lint).
660
+ * Після зміни кодового файлу його дока має бути перегенерована; застарілість
661
+ * детермінується за CRC і контролюється Stop-hook'ом.
662
+ * @returns {string[]} рядки для вставки (з порожнім рядком на початку)
663
+ */
664
+ function buildClaudeDocFilesSectionLines() {
665
+ return [
666
+ '',
667
+ '## Файлова документація (`doc-files` — обовʼязковий крок, як lint)',
668
+ '',
669
+ 'Після зміни чи додавання кодового файлу його файлова дока (`<dir>/docs/<stem>.md`) має бути **актуальною** — це **обовʼязковий крок кожної задачі**, нарівні з lint. Застарілість детермінується за **CRC** джерела у frontmatter доки. PostToolUse hook (`hook --post-tool-use`) **сигналить** про дрейф після правки через per-file lint правила. Регенерація — `/doc-files` (JS-оркестрована, не диспатч субагентів). Агрегуюча дока (module-summary, доменні) — окремий скіл `/doc-aggregate`, за запитом.',
670
+ ''
671
+ ]
672
+ }
673
+
674
+ /**
675
+ * Рендерить секцію Skills для CLAUDE.md з урахуванням наявних slash-команд.
676
+ * @returns {Promise<string[]>} готові рядки секції (або порожній масив)
677
+ */
678
+ async function buildClaudeSkillsSectionLines() {
679
+ const skillDirNames = await listProjectSkillDirNames()
680
+ if (skillDirNames.length === 0) {
681
+ return []
682
+ }
683
+
684
+ const lines = ['', '## Skills', '']
685
+ const skillsRoot = join(cwd(), SKILLS_DIR)
686
+ const commandsRoot = join(cwd(), COMMANDS_DIR)
687
+ for (const dirName of skillDirNames) {
688
+ const skillMdPath = join(skillsRoot, dirName, 'SKILL.md')
689
+ const commandPath = join(commandsRoot, `${dirName}.md`)
690
+ let desc = ''
691
+ if (existsSync(skillMdPath)) {
692
+ const text = await readFile(skillMdPath, 'utf8')
693
+ const parsed = extractSkillDescription(text)
694
+ if (parsed) {
695
+ desc = skillDescriptionSafeForMarkdownInline(parsed)
696
+ }
697
+ }
698
+ const ref = `- \`${SKILLS_DIR}/${dirName}/SKILL.md\``
699
+ lines.push(desc ? `${ref} — ${desc}` : ref)
700
+ if (existsSync(commandPath)) {
701
+ lines.push(` Команда: \`/${dirName}\``)
702
+ }
703
+ }
704
+ return lines
705
+ }
706
+
707
+ /**
708
+ * Генерує CLAUDE.md у корені cwd з at-імпортами всіх .mdc-правил та посиланнями на skills.
709
+ * Завдяки цьому Claude Code автоматично завантажує вміст кожного правила при старті.
710
+ * @returns {Promise<void>}
711
+ */
712
+ /**
713
+ * @param {string[]} [ignore] директорії заборонені для редагування
714
+ */
715
+ async function syncClaudeMd(ignore) {
716
+ const lines = [`<!-- Цей файл генерується автоматично через \`npx ${PACKAGE_NAME}\`. Не редагуй вручну. -->`, '']
717
+
718
+ if (Array.isArray(ignore) && ignore.length > 0) {
719
+ lines.push('## Захищені директорії', '', 'Ніколи не змінюй, не видаляй і не створюй файли у цих директоріях:')
720
+ for (const dir of ignore) {
721
+ let d = dir
722
+ while (d.endsWith('/')) d = d.slice(0, -1)
723
+ lines.push(`- \`${d}/\``)
724
+ }
725
+ lines.push('')
726
+ }
727
+
728
+ const mdcFiles = await listProjectRulesMdcFiles()
729
+ for (const mdcFile of mdcFiles) {
730
+ lines.push(`@${RULES_DIR}/${mdcFile}`)
731
+ }
732
+
733
+ lines.push(
734
+ ...buildClaudeLintParallelismSectionLines(),
735
+ ...buildClaudeWorktreeEnforcementSectionLines(),
736
+ ...buildClaudeDocFilesSectionLines()
737
+ )
738
+
739
+ const skillsSectionLines = await buildClaudeSkillsSectionLines()
740
+ lines.push(...skillsSectionLines)
741
+ const claudeMdPath = join(cwd(), 'CLAUDE.md')
742
+ const hadFile = existsSync(claudeMdPath)
743
+ await writeFile(claudeMdPath, formatGeneratedMarkdownLines(lines), 'utf8')
744
+ console.log(hadFile ? `📝 Оновлено CLAUDE.md` : `📝 Створено CLAUDE.md`)
745
+ }
746
+
747
+ /**
748
+ * Повністю перезаписує AGENTS.md у корені cwd з npm/AGENTS.template.md
749
+ * @param {string} [agentsTemplatePath] шлях до AGENTS.template.md у корені пакету-джерела
750
+ * @returns {Promise<void>} завершення запису файлу
751
+ */
752
+ async function syncAgentsMd(agentsTemplatePath = BUNDLED_AGENTS_TEMPLATE_PATH) {
753
+ if (!existsSync(agentsTemplatePath)) {
754
+ throw new Error(
755
+ `Не знайдено шаблон ${AGENTS_TEMPLATE_FILE} у пакеті.\n` +
756
+ `Очікуваний шлях: ${agentsTemplatePath}\n` +
757
+ `Перевстановіть ${PACKAGE_NAME}.`
758
+ )
759
+ }
760
+ const templateText = await readFile(agentsTemplatePath, 'utf8')
761
+ const mdcFiles = await listProjectRulesMdcFiles()
762
+ const skillItems = await buildSkillBulletItems()
763
+ const commandItems = await buildAgentsCommandBulletItems(cwd())
764
+ const body = renderAgentsTemplate(templateText, mdcFiles, skillItems, commandItems)
765
+ const agentsPath = join(cwd(), AGENTS_FILE)
766
+ const hadFile = existsSync(agentsPath)
767
+ await writeFile(agentsPath, body.endsWith('\n') ? body : `${body}\n`, 'utf8')
768
+ console.log(
769
+ hadFile
770
+ ? `📝 Оновлено ${AGENTS_FILE} з ${AGENTS_TEMPLATE_FILE}`
771
+ : `📝 Створено ${AGENTS_FILE} з ${AGENTS_TEMPLATE_FILE}`
772
+ )
773
+ }
774
+
775
+ /**
776
+ * Копіює лише skills зі списку configSkills (джерело: skills/<id>/ у пакеті)
777
+ * @param {string[]} configSkills id без префікса n-
778
+ * @param {string} [bundledSkillsDir] каталог `skills/` у корені пакету-джерела
779
+ * @returns {Promise<{ success: number, fail: number }>} лічильники успішних і невдалих копіювань
780
+ */
781
+ async function syncSkills(configSkills, bundledSkillsDir = BUNDLED_SKILLS_DIR) {
782
+ if (configSkills.length === 0 || !existsSync(bundledSkillsDir)) {
783
+ return { success: 0, fail: 0 }
784
+ }
785
+
786
+ const skillsRoot = join(cwd(), SKILLS_DIR)
787
+ await mkdir(skillsRoot, { recursive: true })
788
+
789
+ let success = 0
790
+ let fail = 0
791
+
792
+ for (const skillId of configSkills) {
793
+ const id = normalizeSkillId(skillId)
794
+ const srcDir = join(bundledSkillsDir, id)
795
+ const destDirName = managedSkillDirName(skillId)
796
+ const destDir = join(skillsRoot, destDirName)
797
+
798
+ process.stdout.write(` ⬇ ${id} → ${SKILLS_DIR}/${destDirName} ... `)
799
+ if (existsSync(srcDir)) {
800
+ try {
801
+ await mkdir(destDir, { recursive: true })
802
+ const meta = readSkillMetaRaw(srcDir)
803
+ const worktree = meta?.worktree === true
804
+ // root-guard для in-place скілів (мутують CWD без worktree-ізоляції).
805
+ // Worktree-скіли root-assert уже мають у worktree-блоці, тож тут лише !worktree.
806
+ const rootOnly = !worktree && meta?.requireRoot === true
807
+ const entries = await readdir(srcDir, { withFileTypes: true })
808
+ for (const entry of entries) {
809
+ // Лише top-level файли скіла. `main.json` — метадані (не для споживача);
810
+ // підкаталоги (`js/` — скіл-специфічний код) виконуються з пакета через
811
+ // `npx`, у проєкт не копіюються (як `npm/rules/<id>/js/`). Див. scripts.mdc.
812
+ if (!entry.isFile() || entry.name === 'main.json') continue
813
+ let content = await readFile(join(srcDir, entry.name), 'utf8')
814
+ if (entry.name === 'SKILL.md') {
815
+ content = injectWorktreeNotice(content, worktree)
816
+ content = injectRootNotice(content, rootOnly)
817
+ }
818
+ await writeFile(join(destDir, entry.name), content, 'utf8')
819
+ }
820
+ console.log(`✅`)
821
+ success++
822
+ } catch (error) {
823
+ console.log(`❌`)
824
+ console.error(` Помилка: ${error.message}`)
825
+ fail++
826
+ }
827
+ } else {
828
+ console.log(`❌`)
829
+ console.error(` Немає каталогу в пакеті: skills/${id}`)
830
+ fail++
831
+ }
832
+ }
833
+ return { success, fail }
834
+ }
835
+
836
+ /**
837
+ * Синхронізує .claude/commands/n-<id>.md зі skills пакету.
838
+ * У кожному файлі обов’язково YAML frontmatter з `description` (як у `SKILL.md`), інакше команди
839
+ * не з’являються у розширенні VSCode; далі — заголовок H1 лише з імені команди (без повтору опису) і посилання на `.cursor/skills/…/SKILL.md`.
840
+ * @param {string[]} configSkills id без префікса n-
841
+ * @param {string} [bundledSkillsDir] каталог `skills/` у корені пакету-джерела
842
+ * @returns {Promise<{ success: number, fail: number }>} лічильники успішних і невдалих записів
843
+ */
844
+ async function syncCommands(configSkills, bundledSkillsDir = BUNDLED_SKILLS_DIR) {
845
+ if (configSkills.length === 0 || !existsSync(bundledSkillsDir)) {
846
+ return { success: 0, fail: 0 }
847
+ }
848
+
849
+ const commandsDir = join(cwd(), COMMANDS_DIR)
850
+ await mkdir(commandsDir, { recursive: true })
851
+
852
+ let success = 0
853
+ let fail = 0
854
+
855
+ for (const skillId of configSkills) {
856
+ const id = normalizeSkillId(skillId)
857
+ const srcSkillMd = join(bundledSkillsDir, id, 'SKILL.md')
858
+ const destDirName = managedSkillDirName(skillId)
859
+ const destFile = join(commandsDir, `${RULE_PREFIX}${id}.md`)
860
+
861
+ process.stdout.write(` ⬇ ${id} → ${COMMANDS_DIR}/${RULE_PREFIX}${id}.md ... `)
862
+ if (existsSync(srcSkillMd)) {
863
+ try {
864
+ const raw = await readFile(srcSkillMd, 'utf8')
865
+ const descRaw = extractSkillDescription(raw)
866
+ const frontmatter = formatClaudeCommandFrontmatter(descRaw || '')
867
+ const header = `# ${RULE_PREFIX}${id}\n\n`
868
+ const body = `${frontmatter}${header}Виконай інструкції зі скілу \`.cursor/skills/${destDirName}/SKILL.md\`.\n`
869
+ await writeFile(destFile, body, 'utf8')
870
+ console.log(`✅`)
871
+ success++
872
+ } catch (error) {
873
+ console.log(`❌`)
874
+ console.error(` Помилка: ${error.message}`)
875
+ fail++
876
+ }
877
+ } else {
878
+ console.log(`❌`)
879
+ console.error(` Немає SKILL.md у пакеті: skills/${id}`)
880
+ fail++
881
+ }
882
+ }
883
+ return { success, fail }
884
+ }
885
+
886
+ /**
887
+ * Видаляє файли n-*.md у .claude/commands, яких немає у конфігурації skills
888
+ * @param {string} commandsDir абсолютний шлях до .claude/commands
889
+ * @param {string[]} configSkills id без префікса n-
890
+ * @returns {Promise<string[]>} імена видалених файлів
891
+ */
892
+ async function removeOrphanManagedCommandFiles(commandsDir, configSkills) {
893
+ if (!existsSync(commandsDir)) {
894
+ return []
895
+ }
896
+ const expected = new Set(configSkills.map(s => `${RULE_PREFIX}${normalizeSkillId(s)}.md`))
897
+ const names = await readdir(commandsDir)
898
+ const removed = []
899
+ for (const name of names) {
900
+ if (!(name.endsWith('.md') && name.startsWith(RULE_PREFIX) && !expected.has(name))) {
901
+ continue
902
+ }
903
+
904
+ await unlink(join(commandsDir, name))
905
+ removed.push(name)
906
+ }
907
+ return removed.toSorted((a, b) => a.localeCompare(b))
908
+ }
909
+
910
+ /**
911
+ * Синхронізує .claude/commands/{dirName}.md для всіх локальних скілів з .cursor/skills/
912
+ * що не керуються пакетом (відсутні в configSkills). Frontmatter `description` — як у відповідному SKILL.md.
913
+ * @param {string[]} configSkills id керованих skills (вже оброблені syncCommands)
914
+ * @returns {Promise<{ success: number, fail: number }>} лічильники успішних і невдалих записів
915
+ */
916
+ async function syncLocalOnlySkillCommands(configSkills) {
917
+ const skillsRoot = join(cwd(), SKILLS_DIR)
918
+ if (!existsSync(skillsRoot)) return { success: 0, fail: 0 }
919
+
920
+ const commandsDir = join(cwd(), COMMANDS_DIR)
921
+ await mkdir(commandsDir, { recursive: true })
922
+
923
+ const managedDirNames = new Set(configSkills.map(s => managedSkillDirName(s)))
924
+ const allDirNames = await listProjectSkillDirNames()
925
+ const localOnly = allDirNames.filter(d => !managedDirNames.has(d))
926
+
927
+ let success = 0
928
+ let fail = 0
929
+
930
+ for (const dirName of localOnly) {
931
+ const skillMdPath = join(skillsRoot, dirName, 'SKILL.md')
932
+ const destFile = join(commandsDir, `${dirName}.md`)
933
+
934
+ process.stdout.write(` ⬇ ${dirName} → ${COMMANDS_DIR}/${dirName}.md ... `)
935
+ try {
936
+ let descRaw = ''
937
+ if (existsSync(skillMdPath)) {
938
+ const raw = await readFile(skillMdPath, 'utf8')
939
+ const parsed = extractSkillDescription(raw)
940
+ if (parsed) descRaw = parsed
941
+ }
942
+ const frontmatter = formatClaudeCommandFrontmatter(descRaw)
943
+ const header = `# ${dirName}\n\n`
944
+ const body = `${frontmatter}${header}Виконай інструкції зі скілу \`${SKILLS_DIR}/${dirName}/SKILL.md\`.\n`
945
+ await writeFile(destFile, body, 'utf8')
946
+ console.log(`✅`)
947
+ success++
948
+ } catch (error) {
949
+ console.log(`❌`)
950
+ console.error(` Помилка: ${errorMessage(error)}`)
951
+ fail++
952
+ }
953
+ }
954
+ return { success, fail }
955
+ }
956
+
957
+ /**
958
+ * Видаляє .claude/commands/{dirName}.md файли локальних скілів, яких більше немає в .cursor/skills/
959
+ * @param {string} commandsDir абсолютний шлях до .claude/commands
960
+ * @param {string[]} configSkills id керованих skills
961
+ * @returns {Promise<string[]>} імена видалених файлів
962
+ */
963
+ async function removeOrphanLocalSkillCommandFiles(commandsDir, configSkills) {
964
+ if (!existsSync(commandsDir)) return []
965
+
966
+ const managedDirNames = new Set(configSkills.map(s => managedSkillDirName(s)))
967
+ const allDirNames = new Set(await listProjectSkillDirNames())
968
+ const names = await readdir(commandsDir)
969
+ const removed = []
970
+
971
+ for (const name of names) {
972
+ if (!name.endsWith('.md') || name.startsWith(RULE_PREFIX)) continue
973
+ const dirName = name.slice(0, -3)
974
+ if (!managedDirNames.has(dirName) && !allDirNames.has(dirName)) {
975
+ await unlink(join(commandsDir, name))
976
+ removed.push(name)
977
+ }
978
+ }
979
+ return removed.toSorted((a, b) => a.localeCompare(b))
980
+ }
981
+
982
+ /**
983
+ * Синхронізує .pi/skills/n-<id>/SKILL.md зі skills пакету для pi.dev-сумісності.
984
+ * Pi-skill — це директорія з SKILL.md (frontmatter `name`+`description`), тіло-делегат на джерельний
985
+ * `.cursor/skills/<dir>/SKILL.md`. Симетрично до `syncCommands`, але дир замість `.md`-файлу.
986
+ * @param {string[]} configSkills id без префікса n-
987
+ * @param {string} [bundledSkillsDir] каталог `skills/` у корені пакету-джерела
988
+ * @returns {Promise<{ success: number, fail: number }>} лічильники успішних і невдалих записів
989
+ */
990
+ async function syncPiSkills(configSkills, bundledSkillsDir = BUNDLED_SKILLS_DIR) {
991
+ if (configSkills.length === 0 || !existsSync(bundledSkillsDir)) {
992
+ return { success: 0, fail: 0 }
993
+ }
994
+
995
+ const piSkillsRoot = join(cwd(), PI_SKILLS_DIR)
996
+ await mkdir(piSkillsRoot, { recursive: true })
997
+
998
+ let success = 0
999
+ let fail = 0
1000
+
1001
+ for (const skillId of configSkills) {
1002
+ const id = normalizeSkillId(skillId)
1003
+ const srcSkillMd = join(bundledSkillsDir, id, 'SKILL.md')
1004
+ const destDirName = managedSkillDirName(skillId)
1005
+ const destDir = join(piSkillsRoot, destDirName)
1006
+ const destFile = join(destDir, 'SKILL.md')
1007
+
1008
+ process.stdout.write(` ⬇ ${id} → ${PI_SKILLS_DIR}/${destDirName}/SKILL.md ... `)
1009
+ if (existsSync(srcSkillMd)) {
1010
+ try {
1011
+ const raw = await readFile(srcSkillMd, 'utf8')
1012
+ const descRaw = extractSkillDescription(raw)
1013
+ await mkdir(destDir, { recursive: true })
1014
+ const frontmatter = formatPiSkillFrontmatter(destDirName, descRaw || '')
1015
+ const header = `# ${destDirName}\n\n`
1016
+ const body = `${frontmatter}${header}Виконай інструкції зі скілу \`.cursor/skills/${destDirName}/SKILL.md\`.\n`
1017
+ await writeFile(destFile, body, 'utf8')
1018
+ console.log(`✅`)
1019
+ success++
1020
+ } catch (error) {
1021
+ console.log(`❌`)
1022
+ console.error(` Помилка: ${errorMessage(error)}`)
1023
+ fail++
1024
+ }
1025
+ } else {
1026
+ console.log(`❌`)
1027
+ console.error(` Немає SKILL.md у пакеті: skills/${id}`)
1028
+ fail++
1029
+ }
1030
+ }
1031
+ return { success, fail }
1032
+ }
1033
+
1034
+ /**
1035
+ * Синхронізує .pi/skills/{dirName}/SKILL.md для всіх локальних скілів з .cursor/skills/
1036
+ * що не керуються пакетом. Симетрично до `syncLocalOnlySkillCommands`.
1037
+ * @param {string[]} configSkills id керованих skills (уже оброблені syncPiSkills)
1038
+ * @returns {Promise<{ success: number, fail: number }>} лічильники успішних і невдалих записів
1039
+ */
1040
+ async function syncLocalOnlyPiSkills(configSkills) {
1041
+ const skillsRoot = join(cwd(), SKILLS_DIR)
1042
+ if (!existsSync(skillsRoot)) return { success: 0, fail: 0 }
1043
+
1044
+ const piSkillsRoot = join(cwd(), PI_SKILLS_DIR)
1045
+ await mkdir(piSkillsRoot, { recursive: true })
1046
+
1047
+ const managedDirNames = new Set(configSkills.map(s => managedSkillDirName(s)))
1048
+ const allDirNames = await listProjectSkillDirNames()
1049
+ const localOnly = allDirNames.filter(d => !managedDirNames.has(d))
1050
+
1051
+ let success = 0
1052
+ let fail = 0
1053
+
1054
+ for (const dirName of localOnly) {
1055
+ const skillMdPath = join(skillsRoot, dirName, 'SKILL.md')
1056
+ const destDir = join(piSkillsRoot, dirName)
1057
+ const destFile = join(destDir, 'SKILL.md')
1058
+
1059
+ process.stdout.write(` ⬇ ${dirName} → ${PI_SKILLS_DIR}/${dirName}/SKILL.md ... `)
1060
+ try {
1061
+ let descRaw = ''
1062
+ if (existsSync(skillMdPath)) {
1063
+ const raw = await readFile(skillMdPath, 'utf8')
1064
+ const parsed = extractSkillDescription(raw)
1065
+ if (parsed) descRaw = parsed
1066
+ }
1067
+ await mkdir(destDir, { recursive: true })
1068
+ const frontmatter = formatPiSkillFrontmatter(dirName, descRaw)
1069
+ const header = `# ${dirName}\n\n`
1070
+ const body = `${frontmatter}${header}Виконай інструкції зі скілу \`${SKILLS_DIR}/${dirName}/SKILL.md\`.\n`
1071
+ await writeFile(destFile, body, 'utf8')
1072
+ console.log(`✅`)
1073
+ success++
1074
+ } catch (error) {
1075
+ console.log(`❌`)
1076
+ console.error(` Помилка: ${errorMessage(error)}`)
1077
+ fail++
1078
+ }
1079
+ }
1080
+ return { success, fail }
1081
+ }
1082
+
1083
+ /**
1084
+ * Видаляє n-* директорії у .pi/skills, яких немає у конфігурації skills.
1085
+ * @param {string} piSkillsDir абсолютний шлях до .pi/skills
1086
+ * @param {string[]} configSkills id без префікса n-
1087
+ * @returns {Promise<string[]>} імена видалених директорій
1088
+ */
1089
+ async function removeOrphanManagedPiSkillDirs(piSkillsDir, configSkills) {
1090
+ if (!existsSync(piSkillsDir)) return []
1091
+ const expected = new Set(configSkills.map(s => managedSkillDirName(s)))
1092
+ const entries = await readdir(piSkillsDir, { withFileTypes: true })
1093
+ const removed = []
1094
+ for (const entry of entries) {
1095
+ if (!(entry.isDirectory() && entry.name.startsWith(RULE_PREFIX) && !expected.has(entry.name))) {
1096
+ continue
1097
+ }
1098
+
1099
+ await rm(join(piSkillsDir, entry.name), { recursive: true, force: true })
1100
+ removed.push(entry.name)
1101
+ }
1102
+ return removed.toSorted((a, b) => a.localeCompare(b))
1103
+ }
1104
+
1105
+ /**
1106
+ * Видаляє .pi/skills/{dirName} директорії локальних скілів, яких більше немає в .cursor/skills/.
1107
+ * @param {string} piSkillsDir абсолютний шлях до .pi/skills
1108
+ * @param {string[]} configSkills id керованих skills
1109
+ * @returns {Promise<string[]>} імена видалених директорій
1110
+ */
1111
+ async function removeOrphanLocalPiSkillDirs(piSkillsDir, configSkills) {
1112
+ if (!existsSync(piSkillsDir)) return []
1113
+ const managedDirNames = new Set(configSkills.map(s => managedSkillDirName(s)))
1114
+ const allDirNames = new Set(await listProjectSkillDirNames())
1115
+ const entries = await readdir(piSkillsDir, { withFileTypes: true })
1116
+ const removed = []
1117
+ for (const entry of entries) {
1118
+ if (!entry.isDirectory() || entry.name.startsWith(RULE_PREFIX)) continue
1119
+ if (!managedDirNames.has(entry.name) && !allDirNames.has(entry.name)) {
1120
+ await rm(join(piSkillsDir, entry.name), { recursive: true, force: true })
1121
+ removed.push(entry.name)
1122
+ }
1123
+ }
1124
+ return removed.toSorted((a, b) => a.localeCompare(b))
1125
+ }
1126
+
1127
+ /**
1128
+ * Людинозрозумілий текст винятку для логів.
1129
+ * @param {unknown} error виняток із catch
1130
+ * @returns {string} текст повідомлення
1131
+ */
1132
+ function errorMessage(error) {
1133
+ return error instanceof Error ? error.message : String(error)
1134
+ }
1135
+
1136
+ /**
1137
+ * Виконує крок синхронізації з уніфікованим логуванням помилки.
1138
+ * @template T
1139
+ * @param {string} prefix префікс повідомлення про помилку
1140
+ * @param {() => Promise<T>} action операція
1141
+ * @returns {Promise<T>} результат операції
1142
+ */
1143
+ async function runSyncStep(prefix, action) {
1144
+ try {
1145
+ return await action()
1146
+ } catch (error) {
1147
+ console.error(`${prefix}${errorMessage(error)}`)
1148
+ throw error
1149
+ }
1150
+ }
1151
+
1152
+ /**
1153
+ * Виконує `action`, буферизуючи весь його stdout/console-вивід.
1154
+ *
1155
+ * Мотивація: за успішного прогону sync-блоку рядки `⬇ … ✅` і підсумок
1156
+ * (`🧩 Skills: N скопійовано, 0 з помилками`) не несуть користі й лише
1157
+ * захаращують термінал. Тому буфер скидається в реальний stdout **лише**
1158
+ * коли крок повернув `fail > 0` (або кинув виняток); за `fail === 0` —
1159
+ * відкидається мовчки.
1160
+ * @template T
1161
+ * @param {() => Promise<T>} action крок синку, що повертає обʼєкт із лічильником помилок `fail`
1162
+ * @returns {Promise<T>} результат `action` без змін
1163
+ */
1164
+ async function captureOutput(action) {
1165
+ const buffer = []
1166
+ const realStdoutWrite = process.stdout.write
1167
+ const realLog = console.log
1168
+ const realError = console.error
1169
+ const flush = () => realStdoutWrite.call(process.stdout, buffer.join(''))
1170
+ process.stdout.write = (...args) => {
1171
+ const [chunk] = args
1172
+ buffer.push(typeof chunk === 'string' ? chunk : chunk.toString())
1173
+ const cb = args.find(arg => typeof arg === 'function')
1174
+ if (cb) cb()
1175
+ return true
1176
+ }
1177
+ console.log = (...args) => {
1178
+ buffer.push(`${args.map(String).join(' ')}\n`)
1179
+ }
1180
+ console.error = (...args) => {
1181
+ buffer.push(`${args.map(String).join(' ')}\n`)
1182
+ }
1183
+ try {
1184
+ const result = await action()
1185
+ if (result?.fail > 0) flush()
1186
+ return result
1187
+ } catch (error) {
1188
+ flush()
1189
+ throw error
1190
+ } finally {
1191
+ process.stdout.write = realStdoutWrite
1192
+ console.log = realLog
1193
+ console.error = realError
1194
+ }
1195
+ }
1196
+
1197
+ /**
1198
+ * Копіює керовані `.mdc` файли з пакету до `.cursor/rules`.
1199
+ * @param {string[]} rules список rules з конфігу
1200
+ * @param {string} bundledRulesDir каталог `rules` пакету-джерела
1201
+ * @param {string} rulesDir абсолютний шлях до `.cursor/rules`
1202
+ * @returns {Promise<{ successCount: number, failCount: number }>} статистика копіювання
1203
+ */
1204
+ async function syncManagedRuleFiles(rules, bundledRulesDir, rulesDir) {
1205
+ let successCount = 0
1206
+ let failCount = 0
1207
+ for (const rule of rules) {
1208
+ const fileName = `${RULE_PREFIX}${normalizeRuleName(rule)}.mdc`
1209
+ const destPath = join(rulesDir, fileName)
1210
+ try {
1211
+ process.stdout.write(` ⬇ ${rule} → ${RULES_DIR}/${fileName} ... `)
1212
+ const content = await readBundledRuleContent(rule, bundledRulesDir)
1213
+ await writeFile(destPath, content, 'utf8')
1214
+ console.log(`✅`)
1215
+ successCount++
1216
+ } catch (error) {
1217
+ console.log(`❌`)
1218
+ console.error(` Помилка: ${errorMessage(error)}`)
1219
+ failCount++
1220
+ }
1221
+ }
1222
+ return { successCount, failCount }
1223
+ }
1224
+
1225
+ /**
1226
+ * Логує видалені керовані правила/skills/commands у єдиному форматі.
1227
+ * @param {string} title назва сутностей
1228
+ * @param {string} basePath базовий шлях для виводу
1229
+ * @param {string[]} names перелік елементів
1230
+ * @returns {void}
1231
+ */
1232
+ function logRemovedManagedItems(title, basePath, names) {
1233
+ if (names.length === 0) {
1234
+ return
1235
+ }
1236
+ console.log(`\n🧹 Видалено ${title} поза списком ${CONFIG_FILE} (${names.length}):`)
1237
+ for (const name of names) {
1238
+ console.log(` − ${basePath}/${name}`)
1239
+ }
1240
+ }
1241
+
1242
+ /**
1243
+ * Читає поле `version` з `package.json` пакету за абсолютним шляхом до його кореня.
1244
+ * @param {string} packageRoot корінь пакету (тека з `package.json`)
1245
+ * @returns {Promise<string | null>} semver рядком або null, якщо файлу/поля немає або JSON некоректний
1246
+ */
1247
+ async function readBundledVersionAt(packageRoot) {
1248
+ const p = join(packageRoot, 'package.json')
1249
+ if (!existsSync(p)) {
1250
+ return null
1251
+ }
1252
+ try {
1253
+ const pkg = JSON.parse(await readFile(p, 'utf8'))
1254
+ return typeof pkg.version === 'string' ? pkg.version : null
1255
+ } catch {
1256
+ return null
1257
+ }
1258
+ }
1259
+
1260
+ /**
1261
+ * Якщо `upgradeNRulesToLatestAndBunInstall` встановив у `node_modules/@7n/rules` версію,
1262
+ * відмінну від тієї, з якої стартував поточний процес (наприклад, з npx-кешу), запускає бінар нової
1263
+ * версії через `spawnSync` і завершує поточний процес із успадкованим exit-кодом. Re-exec потрібен,
1264
+ * бо ES-модулі вже завантажені у V8 (RULE_MIGRATIONS, detectAutoRules тощо) і нова логіка
1265
+ * без повної заміни процесу не підхопиться. Захист від нескінченного циклу — env `NITRA_CURSOR_REEXEC=1`.
1266
+ * @param {string} effectivePackageRoot шлях, повернутий `upgradeNRulesToLatestAndBunInstall`
1267
+ * @returns {Promise<void>} повертається лише якщо re-exec не потрібен; інакше кидає `ReexecHandoff`,
1268
+ * який ловить top-level catch і прокидає exit-код у `process.exitCode`
1269
+ */
1270
+ async function reexecIfPackageVersionChanged(effectivePackageRoot) {
1271
+ if (env.NITRA_CURSOR_REEXEC === '1') {
1272
+ return
1273
+ }
1274
+ if (effectivePackageRoot === BUNDLED_PACKAGE_ROOT) {
1275
+ return
1276
+ }
1277
+ const currentVersion = await readBundledVersionAt(BUNDLED_PACKAGE_ROOT)
1278
+ const installedVersion = await readBundledVersionAt(effectivePackageRoot)
1279
+ if (!currentVersion || !installedVersion || currentVersion === installedVersion) {
1280
+ return
1281
+ }
1282
+ const newBinPath = join(effectivePackageRoot, 'bin', 'n-rules.js')
1283
+ if (!existsSync(newBinPath)) {
1284
+ return
1285
+ }
1286
+ console.log(
1287
+ `🔁 Перезапуск ${PACKAGE_NAME}: процес стартував на ${currentVersion}, ` +
1288
+ `після self-upgrade встановлено ${installedVersion}.\n` +
1289
+ ` Re-exec свіжого бінаря, щоб підхопити нову логіку (RULE_MIGRATIONS, auto-detect тощо).\n`
1290
+ )
1291
+ const result = spawnSync(process.execPath, [newBinPath, ...process.argv.slice(2)], {
1292
+ stdio: 'inherit',
1293
+ env: { ...env, NITRA_CURSOR_REEXEC: '1' }
1294
+ })
1295
+ if (result.error) {
1296
+ throw result.error
1297
+ }
1298
+ throw new ReexecHandoff(typeof result.status === 'number' ? result.status : 1)
1299
+ }
1300
+
1301
+ /**
1302
+ * Сентинельна помилка, яку кидає `reexecIfPackageVersionChanged` після успішного re-exec.
1303
+ * Top-level catch розпізнає її й виставляє `process.exitCode = code` без stack-trace —
1304
+ * процес тоді коректно завершується з тим самим кодом, що й child re-exec-у.
1305
+ */
1306
+ class ReexecHandoff extends Error {
1307
+ /**
1308
+ * @param {number} code exit-код, який повернув child-процес
1309
+ */
1310
+ constructor(code) {
1311
+ super('reexec-handoff')
1312
+ this.name = 'ReexecHandoff'
1313
+ this.code = code
1314
+ }
1315
+ }
1316
+
1317
+ /**
1318
+ * Копіює правила з каталогу `mdc/` установленого пакету та синхронізує `.cursor/rules`
1319
+ * @returns {Promise<void>}
1320
+ */
1321
+ async function runSync() {
1322
+ console.log(`\n🔧 ${PACKAGE_NAME} — завантаження cursor-правил\n`)
1323
+
1324
+ const projectRoot = cwd()
1325
+ const effectivePackageRoot = await runSyncStep(`❌ Не вдалося оновити ${PACKAGE_NAME} або виконати bun i: `, () =>
1326
+ upgradeNRulesToLatestAndBunInstall(projectRoot, BUNDLED_PACKAGE_ROOT)
1327
+ )
1328
+
1329
+ await reexecIfPackageVersionChanged(effectivePackageRoot)
1330
+
1331
+ const bundledRulesDir = join(effectivePackageRoot, 'rules')
1332
+ const bundledSkillsDir = join(effectivePackageRoot, 'skills')
1333
+ const bundledAgentsTemplatePath = join(effectivePackageRoot, AGENTS_TEMPLATE_FILE)
1334
+
1335
+ const config = await runSyncStep('❌ ', () => readConfig({ bundledRulesDir, bundledSkillsDir }))
1336
+
1337
+ const { rules, skills, version, ignore } = config
1338
+ const claudeConfigEnabled = config['claude-config'] !== false
1339
+ const bundledVer = await readBundledVersionAt(effectivePackageRoot)
1340
+ if (bundledVer) {
1341
+ const line =
1342
+ effectivePackageRoot === BUNDLED_PACKAGE_ROOT
1343
+ ? `📦 Джерело правил: ${PACKAGE_NAME}@${bundledVer}`
1344
+ : `📦 Джерело правил: ${PACKAGE_NAME}@${bundledVer} (шлях: ${effectivePackageRoot})`
1345
+ console.log(`${line}\n`)
1346
+ }
1347
+ if (version) {
1348
+ console.log(`⚠️ Поле "version" у ${CONFIG_FILE} ігнорується; правила беруться з установленого пакету.\n`)
1349
+ }
1350
+ console.log(`📋 Правил до завантаження: ${rules.length}`)
1351
+ console.log(`📋 Skills до синхронізації: ${skills.length}`)
1352
+
1353
+ await runSyncStep('❌ Не вдалося записати setup-bun-deps action: ', () =>
1354
+ captureOutput(async () => {
1355
+ const { destPath } = await syncSetupBunDepsAction(cwd(), effectivePackageRoot)
1356
+ console.log(`📝 Оновлено ${destPath} (composite setup-bun-deps з пакету)\n`)
1357
+ })
1358
+ )
1359
+
1360
+ const rulesDir = join(cwd(), RULES_DIR)
1361
+ await mkdir(rulesDir, { recursive: true })
1362
+ const { successCount, failCount } = await captureOutput(async () => {
1363
+ const stats = await syncManagedRuleFiles(rules, bundledRulesDir, rulesDir)
1364
+ return { ...stats, fail: stats.failCount }
1365
+ })
1366
+
1367
+ await runSyncStep(`❌ Не вдалося прибрати зайві файли в ${RULES_DIR}: `, async () => {
1368
+ const removed = await removeOrphanManagedRuleFiles(rulesDir, rules)
1369
+ logRemovedManagedItems('правила', RULES_DIR, removed)
1370
+ })
1371
+
1372
+ await runSyncStep('❌ Skills: ', async () => {
1373
+ const { fail: skillFail } = await captureOutput(async () => {
1374
+ const { success: skillOk, fail } = await syncSkills(skills, bundledSkillsDir)
1375
+ if (skills.length > 0) {
1376
+ console.log(`\n🧩 Skills: ${skillOk} скопійовано, ${fail} з помилками`)
1377
+ }
1378
+ return { fail }
1379
+ })
1380
+ const removedSkills = await removeOrphanManagedSkillDirs(join(cwd(), SKILLS_DIR), skills)
1381
+ logRemovedManagedItems('skills', SKILLS_DIR, removedSkills)
1382
+ if (skillFail > 0) {
1383
+ throw new Error(`Не вдалося скопіювати ${skillFail} з ${skills.length} skills`)
1384
+ }
1385
+ })
1386
+
1387
+ await runSyncStep('❌ Commands: ', async () => {
1388
+ const { fail: totalFail } = await captureOutput(async () => {
1389
+ const { success: cmdOk, fail: cmdFail } = await syncCommands(skills, bundledSkillsDir)
1390
+ const { success: localOk, fail: localFail } = await syncLocalOnlySkillCommands(skills)
1391
+ const totalOk = cmdOk + localOk
1392
+ const fail = cmdFail + localFail
1393
+ if (totalOk + fail > 0) {
1394
+ console.log(`\n⌨️ Commands: ${totalOk} скопійовано, ${fail} з помилками`)
1395
+ }
1396
+ return { fail }
1397
+ })
1398
+ const commandsDir = join(cwd(), COMMANDS_DIR)
1399
+ const removedCmds = await removeOrphanManagedCommandFiles(commandsDir, skills)
1400
+ logRemovedManagedItems('commands', COMMANDS_DIR, removedCmds)
1401
+ const removedLocalCmds = await removeOrphanLocalSkillCommandFiles(commandsDir, skills)
1402
+ logRemovedManagedItems('commands (local)', COMMANDS_DIR, removedLocalCmds)
1403
+ if (totalFail > 0) {
1404
+ throw new Error(`Не вдалося скопіювати ${totalFail} commands`)
1405
+ }
1406
+ })
1407
+
1408
+ await runSyncStep('❌ Pi skills: ', async () => {
1409
+ const { fail: totalFail } = await captureOutput(async () => {
1410
+ const { success: piOk, fail: piFail } = await syncPiSkills(skills, bundledSkillsDir)
1411
+ const { success: piLocalOk, fail: piLocalFail } = await syncLocalOnlyPiSkills(skills)
1412
+ const totalOk = piOk + piLocalOk
1413
+ const fail = piFail + piLocalFail
1414
+ if (totalOk + fail > 0) {
1415
+ console.log(`\n🥧 Pi skills: ${totalOk} скопійовано, ${fail} з помилками`)
1416
+ }
1417
+ return { fail }
1418
+ })
1419
+ const piSkillsDir = join(cwd(), PI_SKILLS_DIR)
1420
+ const removedPi = await removeOrphanManagedPiSkillDirs(piSkillsDir, skills)
1421
+ logRemovedManagedItems('pi skills', PI_SKILLS_DIR, removedPi)
1422
+ const removedLocalPi = await removeOrphanLocalPiSkillDirs(piSkillsDir, skills)
1423
+ logRemovedManagedItems('pi skills (local)', PI_SKILLS_DIR, removedLocalPi)
1424
+ if (totalFail > 0) {
1425
+ throw new Error(`Не вдалося скопіювати ${totalFail} pi skills`)
1426
+ }
1427
+ })
1428
+
1429
+ await runSyncStep(`❌ Не вдалося оновити ${AGENTS_FILE}: `, () =>
1430
+ captureOutput(() => syncAgentsMd(bundledAgentsTemplatePath))
1431
+ )
1432
+ await runSyncStep('❌ Не вдалося оновити CLAUDE.md: ', () =>
1433
+ captureOutput(() => syncClaudeMd(/** @type {string[] | undefined} */ (ignore)))
1434
+ )
1435
+
1436
+ await runSyncStep('❌ Не вдалося синхронізувати Claude-конфіг: ', () =>
1437
+ captureOutput(async () => {
1438
+ const result = await syncClaudeConfig({
1439
+ projectRoot: cwd(),
1440
+ bundledPackageRoot: effectivePackageRoot,
1441
+ enabled: claudeConfigEnabled,
1442
+ rules
1443
+ })
1444
+ if (!claudeConfigEnabled) {
1445
+ console.log('🤖 Claude-конфіг: пропущено (claude-config: false у .n-rules.json)')
1446
+ return
1447
+ }
1448
+ const parts = []
1449
+ if (result.settings) parts.push('.claude/settings.json')
1450
+ if (result.cursorHooks) parts.push('.cursor/hooks.json')
1451
+ if (result.commands.length > 0) parts.push(`${result.commands.length} slash-commands`)
1452
+ if (result.adrHook) parts.push('.claude/hooks/capture-decisions.sh')
1453
+ if (result.adrNormalizeHook) parts.push('.claude/hooks/normalize-decisions.sh')
1454
+ if (result.adrHookLib?.length > 0) {
1455
+ for (const libPath of result.adrHookLib) {
1456
+ parts.push(libPath)
1457
+ }
1458
+ }
1459
+ if (result.gitignoreAdr) parts.push('.gitignore (adr fragment)')
1460
+ if (result.piExtension) parts.push('.pi/extensions/n-rules-adr/')
1461
+ if (parts.length > 0) {
1462
+ console.log(`🤖 Claude-конфіг: ${parts.join(', ')}`)
1463
+ }
1464
+ })
1465
+ )
1466
+
1467
+ await runSyncStep('❌ Не вдалося оновити .gitignore (worktree): ', async () => {
1468
+ const { written } = await syncGitignoreWorktree(cwd())
1469
+ if (written) console.log('🌳 .gitignore (worktree): додано .worktrees/')
1470
+ })
1471
+
1472
+ console.log(`\n✨ Готово: ${successCount} завантажено, ${failCount} з помилками\n`)
1473
+ if (failCount > 0) {
1474
+ throw new Error(`Не вдалося завантажити ${failCount} з ${rules.length} правил`)
1475
+ }
1476
+ }
1477
+
1478
+ /**
1479
+ * Команди, що мутують проєкт у CWD і вимагають кореня репо. `undefined`/`''` —
1480
+ * дефолтний sync; `check` — deprecated-alias `fix`. Решта (read-only,
1481
+ * `--root`-команди `doc-aggregate`/`rename-yaml-extensions`,
1482
+ * sub-лінтери) гард не зачіпає.
1483
+ */
1484
+ const ROOT_GUARDED_COMMANDS = new Set([undefined, '', 'lint', 'release'])
1485
+
1486
+ /**
1487
+ * Короткий опис дії для тексту root-guard помилки за іменем команди.
1488
+ * @param {string | undefined} cmd підкоманда CLI (або undefined для дефолтного sync)
1489
+ * @returns {string} фраза «що саме мутує CWD»
1490
+ */
1491
+ function describeRootGuardedAction(cmd) {
1492
+ switch (cmd) {
1493
+ case undefined:
1494
+ case '': {
1495
+ return 'Дефолтна синхронізація скаффолдить .cursor/, .claude/, CLAUDE.md, .n-rules.json і робить bun install у поточному каталозі'
1496
+ }
1497
+ case 'lint': {
1498
+ return '`lint` за замовчуванням авто-fix лінтерів (oxfmt/eslint --fix/stylelint --fix) і конформності (--full) у поточному каталозі'
1499
+ }
1500
+ case 'release': {
1501
+ return '`release` бампає version і переписує CHANGELOG у поточному каталозі'
1502
+ }
1503
+ default: {
1504
+ return 'Команда @7n/rules мутує проєкт у поточному каталозі'
1505
+ }
1506
+ }
1507
+ }
1508
+
1509
+ /**
1510
+ * Довідка для `n-rules lint --help`: прапори unified lint surface
1511
+ * (spec 2026-06-29 fix-by-default, spec 2026-07-03 глобальна черга --full).
1512
+ */
1513
+ function printLintHelp() {
1514
+ console.log(`Використання: npx @7n/rules lint [rule|concern ...] [прапори]
1515
+
1516
+ За замовчуванням лінт іде дельтою (змінені файли vs origin) і одразу
1517
+ виправляє (auto-fix: oxfmt/eslint --fix/stylelint --fix + LLM-ladder).
1518
+
1519
+ Прапори:
1520
+ --full Прогін по всьому репозиторію (конформність), не лише дельта.
1521
+ Один --full на машину: паралельні запуски стають у чергу
1522
+ й бачать живий прогрес активного прогону.
1523
+ --no-fix Лише детекція, без мутацій.
1524
+ --cwd <path> Робочий каталог замість поточного.
1525
+ --verbose Розширений вивід детекції та виправлення.
1526
+ --help, -h Ця довідка.
1527
+
1528
+ Позиційні аргументи — фільтр за назвою правила чи concern, наприклад:
1529
+ npx @7n/rules lint ga rego k8s docker text
1530
+
1531
+ Приклади:
1532
+ npx @7n/rules lint
1533
+ npx @7n/rules lint --full
1534
+ npx @7n/rules lint --no-fix eslint
1535
+ npx @7n/rules lint --cwd ./packages/foo --verbose
1536
+ `)
1537
+ }
1538
+
1539
+ // CLI: маршрутизація команд
1540
+ let [command, ...args] = process.argv.slice(2)
1541
+
1542
+ // Deprecated-аліаси до уніфікації lint-поверхні (spec 2026-06-29): старі
1543
+ // підкоманди `lint-<scope>` замінені на `lint <scope>`. Аліас тут — щоб
1544
+ // існуючі package.json/CI споживачів (lint-text/lint-ga тощо) не ламались
1545
+ // мовчки з непрозорим "Невідома команда" при мажорному бампі пакету.
1546
+ const LEGACY_LINT_COMMAND_ALIASES = {
1547
+ 'lint-ga': 'ga',
1548
+ 'lint-text': 'text',
1549
+ 'lint-rego': 'rego',
1550
+ 'lint-k8s': 'k8s',
1551
+ 'lint-docker': 'docker'
1552
+ }
1553
+ if (command in LEGACY_LINT_COMMAND_ALIASES) {
1554
+ const scope = LEGACY_LINT_COMMAND_ALIASES[command]
1555
+ console.error(`⚠️ "${command}" застаріла назва команди — використовуйте "n-rules lint ${scope}"`)
1556
+ args = [scope, ...args]
1557
+ command = 'lint'
1558
+ }
1559
+
1560
+ // `lint --help`/`-h` — чиста довідка, без root-guard і без мутації devDependencies.
1561
+ const isLintHelp = command === 'lint' && (args.includes('--help') || args.includes('-h'))
1562
+
1563
+ try {
1564
+ if (isLintHelp) {
1565
+ printLintHelp()
1566
+ } else {
1567
+ // Root-guard до перших мутацій: дефолтний sync скаффолдить .cursor/.claude/CLAUDE.md/
1568
+ // .n-rules.json + bun install, а lint/release переписують файли в CWD —
1569
+ // усе це ключиться на cwd(). Запуск із піддиректорії git-репо (типово прямий
1570
+ // `bun npm/bin/n-rules.js` не з кореня) зачепив би не той каталог → STOP. Read-only та
1571
+ // `--root`-команди (doc-aggregate, rename-yaml-extensions) не зачіпаємо.
1572
+ // `lint` з явним `--cwd` — свідомий вибір кореня (MT ганяє `## Check` вузла з
1573
+ // node-dir усередині worktree, live e2e 2026-07-12): guard і devDeps-ensure
1574
+ // цілять у ЦІЛЬОВИЙ каталог, а не процесний cwd.
1575
+ const lintCwdIdx = command === 'lint' ? args.indexOf('--cwd') : -1
1576
+ const effectiveRoot = lintCwdIdx !== -1 && args[lintCwdIdx + 1] ? resolve(args[lintCwdIdx + 1]) : cwd()
1577
+ if (ROOT_GUARDED_COMMANDS.has(command)) {
1578
+ assertCwdIsProjectRoot(effectiveRoot, describeRootGuardedAction(command))
1579
+ }
1580
+ // mt-run-node — MT-екзекутор у worktree вузла: жодних side-effect-ів поза фіксом
1581
+ // (devDeps-ensure мутив би package.json чужого worktree).
1582
+ if (command !== 'mt-run-node') await ensureNRulesInRootDevDependencies(effectiveRoot)
1583
+ // Підкоманди-оркестратори (hook/lint/skill/adr-normalize-local/taze/release тощо)
1584
+ // можуть спавнити внутрішню agent/LLM-сесію — ADR Stop-hooks (capture/normalize)
1585
+ // мають пропустити її як технічний шум, не людську думку (spec 2026-06-30).
1586
+ env.ADR_HOOKS_SKIP = '1'
1587
+ switch (command) {
1588
+ case 'rename-yaml-extensions': {
1589
+ const code = await runRenameYamlExtensionsCli(args)
1590
+ if (code !== 0) {
1591
+ process.exitCode = 1
1592
+ }
1593
+
1594
+ break
1595
+ }
1596
+ case 'hook': {
1597
+ // Thin hook entrypoint для Claude Code hooks. Делегує в runLint, перекодовує exit 1→2.
1598
+ // --post-tool-use PostToolUse: file_path зі stdin JSON
1599
+ // --stop Stop: робоче дерево vs HEAD
1600
+ const { runHookCli } = await import('../scripts/hook.mjs')
1601
+ process.exitCode = await runHookCli(args)
1602
+
1603
+ break
1604
+ }
1605
+ case 'lint': {
1606
+ // Unified lint surface (spec 2026-06-29). Осі: --full (весь репо vs дельта) ×
1607
+ // --no-fix (detect-only). Позиційні аргументи — scoped rule/concern фільтр.
1608
+ // Fix-by-default: detect → T0 → LLM-ladder (run-fix). --no-fix: лише detect.
1609
+ const cwdIdx = args.indexOf('--cwd')
1610
+ const cwdArg = cwdIdx === -1 ? cwd() : resolve(args[cwdIdx + 1])
1611
+ const rules = args.filter((a, i) => !a.startsWith('-') && !(cwdIdx !== -1 && i === cwdIdx + 1))
1612
+ const lintOpts = { cwd: cwdArg, full: args.includes('--full'), rules, verbose: args.includes('--verbose') }
1613
+ const noFix = args.includes('--no-fix')
1614
+ // Глобальна черга full-прогонів (spec 2026-07-03): одночасно виконується один
1615
+ // `lint --full` на машину, паралельні --full чекають лока і бачать чергу та
1616
+ // живий прогрес активного прогону; не-full запуски йдуть без лока
1617
+ // (див. lint-lock.mjs). Publisher пише знімки прогресу для черги.
1618
+ const { withGlobalLintLock, createProgressPublisher } =
1619
+ await import('../scripts/lib/lint-surface/lint-lock.mjs')
1620
+ process.exitCode = await withGlobalLintLock({ ...lintOpts, noFix }, async () => {
1621
+ const publisher = lintOpts.full ? createProgressPublisher() : null
1622
+ const runOpts = publisher ? { ...lintOpts, onProgress: publisher.onUpdate } : lintOpts
1623
+ try {
1624
+ if (noFix) {
1625
+ const { detectAll } = await import('../scripts/lib/lint-surface/run-detectors.mjs')
1626
+ // окрема змінна замість (await detectAll(...)).exitCode — no-await-expression-member (oxlint)
1627
+ const detectResult = await detectAll(runOpts)
1628
+ return detectResult.exitCode
1629
+ }
1630
+ const { runFixPipeline } = await import('../scripts/lib/lint-surface/run-fix.mjs')
1631
+ return await runFixPipeline(runOpts)
1632
+ } finally {
1633
+ publisher?.stop()
1634
+ }
1635
+ })
1636
+
1637
+ break
1638
+ }
1639
+ case 'taze': {
1640
+ // n-rules taze diff — read-only semver-diff package.json ↔ package.json.taze-bak
1641
+ // (root + воркспейси) для скілу n-taze: скрипт класифікує major-оновлення,
1642
+ // агент отримує готовий список замість ручного порівняння бекапів.
1643
+ const { runTazeCli } = await import('../skills/taze/js/diff.mjs')
1644
+ process.exitCode = await runTazeCli(args)
1645
+
1646
+ break
1647
+ }
1648
+ case 'release': {
1649
+ const { runReleaseCli } = await import('../rules/release/release.mjs')
1650
+ process.exitCode = await runReleaseCli(args)
1651
+
1652
+ break
1653
+ }
1654
+ case 'skill': {
1655
+ process.exitCode = await runSkillsCli(args)
1656
+
1657
+ break
1658
+ }
1659
+ case 'adr-normalize-local': {
1660
+ // Local-backend ADR-нормалізації: викликається з .claude/hooks/normalize-decisions.sh
1661
+ // як заміна single-shot LLM-виклику. Проганяє конвеєр (retrieval→edge-judge→
1662
+ // cluster→gen) на малій локальній моделі й друкує `{operations}` JSON у stdout.
1663
+ const { runAdrNormalizeLocalCli } = await import('../scripts/lib/adr/normalize-cli.mjs')
1664
+ process.exitCode = await runAdrNormalizeLocalCli(args)
1665
+
1666
+ break
1667
+ }
1668
+ case 'mt-run-node': {
1669
+ // B2 executor-міст (Фаза B): MT node_executor спавнить це для fix-вузла.
1670
+ // Виконує вузол нашим pi-harness (тир-канон); stdout={applied,touchedFiles}.
1671
+ const { runNodeCli } = await import('../scripts/lib/lint-surface/mt-run-node.mjs')
1672
+ process.exitCode = await runNodeCli(args)
1673
+
1674
+ break
1675
+ }
1676
+ case undefined:
1677
+ case '': {
1678
+ await runSync()
1679
+
1680
+ break
1681
+ }
1682
+ default: {
1683
+ console.error(`❌ Невідома команда: ${command}`)
1684
+ console.error(
1685
+ ` Очікується: (без аргументів) синхронізація правил, rename-yaml-extensions, hook, adr-normalize-local, lint (включно зі scope: lint ga|rego|k8s|docker|text), taze, release, skill, doc-aggregate`
1686
+ )
1687
+ process.exitCode = 1
1688
+ }
1689
+ }
1690
+ }
1691
+ } catch (error) {
1692
+ if (error instanceof ReexecHandoff) {
1693
+ process.exitCode = error.code
1694
+ } else if (error instanceof Error && error.message) {
1695
+ console.error(error.message)
1696
+ process.exitCode = 1
1697
+ } else {
1698
+ console.error(error)
1699
+ process.exitCode = 1
1700
+ }
1701
+ }
1702
+
1703
+ // Pi-агент тримає TCP keep-alive сокет відкритим — явний вихід, щоб не висіти.
1704
+ // Відтворюємо семантику process.exit() без самого виклику (n/no-process-exit):
1705
+ // фіксуємо код, віддаємо 'exit'-слухачам, далі — примусове завершення через reallyExit.
1706
+ const exitCode = process.exitCode ?? 0
1707
+ process.exitCode = exitCode
1708
+ process.emit('exit', exitCode)
1709
+ process.reallyExit(exitCode)