@nitra/cursor 12.8.9 → 12.10.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 (274) hide show
  1. package/.claude-template/settings.template.json +1 -23
  2. package/CHANGELOG.md +14 -1
  3. package/bin/n-cursor.js +21 -48
  4. package/docs/stryker.config.md +0 -2
  5. package/lib/docs/llm.md +16 -21
  6. package/lib/docs/omlx.md +20 -25
  7. package/lib/llm.mjs +25 -7
  8. package/lib/omlx.mjs +10 -3
  9. package/package.json +5 -5
  10. package/rules/abie/docs/index.md +0 -1
  11. package/rules/abie/docs/main.md +0 -2
  12. package/rules/abie/lib/docs/http-route.md +9 -12
  13. package/rules/abie/lib/http-route.mjs +3 -0
  14. package/rules/abie/main.mdc +0 -22
  15. package/rules/abie/policy/health_check_policy/health_check_policy.mdc +3 -1
  16. package/rules/abie/policy/health_check_policy/health_check_policy.rego +27 -0
  17. package/rules/adr/docs/index.md +0 -1
  18. package/rules/adr/docs/main.md +0 -2
  19. package/rules/adr/js/madr_format.mdc +13 -1
  20. package/rules/adr/main.mdc +0 -9
  21. package/rules/bun/docs/index.md +0 -1
  22. package/rules/bun/docs/main.md +0 -2
  23. package/rules/bun/main.mdc +1 -15
  24. package/rules/bun/policy/package_json/package_json.rego +12 -0
  25. package/rules/capacitor/docs/index.md +0 -1
  26. package/rules/capacitor/docs/main.md +0 -2
  27. package/rules/capacitor/main.mdc +0 -6
  28. package/rules/changelog/docs/index.md +0 -1
  29. package/rules/changelog/docs/main.md +0 -2
  30. package/rules/changelog/js/agent-workflow.mdc +1 -1
  31. package/rules/changelog/js/consistency.mjs +3 -3
  32. package/rules/changelog/js/docs/consistency.md +18 -23
  33. package/rules/changelog/main.mdc +0 -5
  34. package/rules/ci4/docs/index.md +0 -1
  35. package/rules/ci4/docs/main.md +0 -2
  36. package/rules/ci4/main.mdc +0 -5
  37. package/rules/doc-files/docs/index.md +0 -1
  38. package/rules/doc-files/docs/main.md +7 -11
  39. package/rules/doc-files/js/docs/docgen-crc.md +0 -2
  40. package/rules/doc-files/js/docs/docgen-extract.md +0 -2
  41. package/rules/doc-files/js/docs/docgen-files-batch.md +0 -2
  42. package/rules/doc-files/js/docs/docgen-gen.md +0 -2
  43. package/rules/doc-files/js/docs/docgen-judge-measure.md +0 -2
  44. package/rules/doc-files/js/docs/docgen-judge.md +0 -2
  45. package/rules/doc-files/js/docs/docgen-scan.md +0 -2
  46. package/rules/doc-files/js/docs/run-lint.md +0 -2
  47. package/rules/doc-files/main.mjs +2 -3
  48. package/rules/docker/docs/index.md +0 -1
  49. package/rules/docker/docs/main.md +0 -2
  50. package/rules/docker/js/docs/lint.md +0 -2
  51. package/rules/docker/lib/docs/docker-hadolint.md +0 -2
  52. package/rules/docker/main.mdc +1 -21
  53. package/rules/efes/docs/index.md +0 -1
  54. package/rules/efes/docs/main.md +0 -2
  55. package/rules/efes/main.mdc +0 -1
  56. package/rules/feedback/docs/index.md +0 -1
  57. package/rules/feedback/docs/main.md +0 -2
  58. package/rules/ga/docs/index.md +0 -1
  59. package/rules/ga/docs/main.md +0 -2
  60. package/rules/ga/js/docs/index.md +0 -1
  61. package/rules/ga/main.mdc +1 -31
  62. package/rules/graphql/docs/index.md +0 -1
  63. package/rules/graphql/docs/main.md +0 -2
  64. package/rules/graphql/main.mdc +0 -5
  65. package/rules/hasura/docs/index.md +0 -1
  66. package/rules/hasura/docs/main.md +0 -2
  67. package/rules/hasura/js/docs/index.md +1 -0
  68. package/rules/hasura/js/docs/migrations.md +28 -0
  69. package/rules/hasura/js/migrations.mjs +47 -0
  70. package/rules/hasura/main.mdc +1 -11
  71. package/rules/image-avif/docs/index.md +0 -1
  72. package/rules/image-avif/docs/main.md +0 -2
  73. package/rules/image-avif/main.mdc +1 -9
  74. package/rules/image-compress/docs/index.md +0 -1
  75. package/rules/image-compress/docs/main.md +0 -2
  76. package/rules/image-compress/js/docs/index.md +0 -1
  77. package/rules/image-compress/main.mdc +1 -9
  78. package/rules/js/docs/index.md +0 -1
  79. package/rules/js/docs/main.md +0 -2
  80. package/rules/js/js/dep-policy.mjs +91 -0
  81. package/rules/js/js/docs/check.md +0 -2
  82. package/rules/js/js/docs/dep-policy.md +34 -0
  83. package/rules/js/js/docs/index.md +5 -4
  84. package/rules/js/js/docs/tooling.md +0 -2
  85. package/rules/js/js/docs/utils_imports.md +0 -2
  86. package/rules/js/main.mdc +0 -31
  87. package/rules/js/policy/package_json/package_json.rego +16 -0
  88. package/rules/js-bun-db/docs/index.md +0 -1
  89. package/rules/js-bun-db/docs/main.md +0 -2
  90. package/rules/js-bun-db/js/docs/safety.md +18 -23
  91. package/rules/js-bun-db/js/safety.mjs +31 -3
  92. package/rules/js-bun-db/lib/bun-sql-scan.mjs +123 -0
  93. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +37 -331
  94. package/rules/js-bun-db/main.mdc +1 -23
  95. package/rules/js-bun-redis/docs/index.md +0 -1
  96. package/rules/js-bun-redis/docs/main.md +0 -2
  97. package/rules/js-bun-redis/main.mdc +0 -5
  98. package/rules/js-mssql/docs/index.md +0 -1
  99. package/rules/js-mssql/docs/main.md +0 -2
  100. package/rules/js-mssql/main.mdc +0 -12
  101. package/rules/js-run/docs/index.md +0 -1
  102. package/rules/js-run/docs/main.md +0 -2
  103. package/rules/js-run/js/docs/runtime.md +15 -13
  104. package/rules/js-run/js/runtime.mjs +48 -4
  105. package/rules/js-run/main.mdc +0 -25
  106. package/rules/k8s/docs/index.md +0 -1
  107. package/rules/k8s/docs/main.md +0 -2
  108. package/rules/k8s/main.mdc +0 -45
  109. package/rules/nginx-default-tpl/docs/index.md +0 -1
  110. package/rules/nginx-default-tpl/docs/main.md +0 -2
  111. package/rules/nginx-default-tpl/main.mdc +0 -13
  112. package/rules/npm-module/docs/index.md +0 -1
  113. package/rules/npm-module/docs/main.md +0 -2
  114. package/rules/npm-module/js/docs/header_doc_pointer.md +0 -2
  115. package/rules/npm-module/js/docs/rule_meta.md +0 -2
  116. package/rules/npm-module/js/docs/skill_meta.md +0 -2
  117. package/rules/npm-module/main.mdc +1 -15
  118. package/rules/php/docs/index.md +0 -1
  119. package/rules/php/docs/main.md +0 -2
  120. package/rules/php/js/docs/index.md +0 -1
  121. package/rules/php/main.mdc +1 -9
  122. package/rules/python/docs/index.md +0 -1
  123. package/rules/python/docs/main.md +0 -2
  124. package/rules/python/js/docs/index.md +0 -1
  125. package/rules/python/main.mdc +1 -13
  126. package/rules/rego/docs/index.md +0 -1
  127. package/rules/rego/docs/main.md +0 -2
  128. package/rules/rego/js/docs/index.md +1 -1
  129. package/rules/rego/js/docs/tooling.md +26 -0
  130. package/rules/rego/js/tooling.mdc +14 -0
  131. package/rules/rego/js/tooling.mjs +24 -0
  132. package/rules/rego/main.mdc +0 -9
  133. package/rules/rego/policy/package_json/package_json.mdc +12 -0
  134. package/rules/rego/policy/package_json/package_json.rego +21 -0
  135. package/rules/rego/policy/package_json/target.json +4 -0
  136. package/rules/release/docs/index.md +0 -1
  137. package/rules/release/docs/main.md +0 -2
  138. package/rules/release/main.mdc +2 -2
  139. package/rules/rust/docs/index.md +0 -1
  140. package/rules/rust/docs/main.md +0 -2
  141. package/rules/rust/js/docs/index.md +0 -1
  142. package/rules/rust/main.mdc +1 -11
  143. package/rules/rust/policy/lint_rust_yml/lint_rust_yml.rego +24 -0
  144. package/rules/rust/policy/package_json/package_json.mdc +12 -0
  145. package/rules/rust/policy/package_json/package_json.rego +20 -0
  146. package/rules/rust/policy/package_json/target.json +4 -0
  147. package/rules/security/docs/index.md +0 -1
  148. package/rules/security/docs/main.md +0 -2
  149. package/rules/security/js/docs/index.md +0 -1
  150. package/rules/security/main.mdc +0 -13
  151. package/rules/style/docs/index.md +0 -1
  152. package/rules/style/docs/main.md +0 -2
  153. package/rules/style/js/docs/index.md +0 -1
  154. package/rules/style/js/docs/tooling.md +12 -10
  155. package/rules/style/js/tooling.mjs +8 -2
  156. package/rules/style/main.mdc +1 -23
  157. package/rules/style/policy/lint_style_yml/lint_style_yml.rego +5 -0
  158. package/rules/tauri/docs/index.md +0 -1
  159. package/rules/tauri/docs/main.md +0 -2
  160. package/rules/tauri/main.mdc +1 -11
  161. package/rules/test/docs/index.md +0 -1
  162. package/rules/test/docs/main.md +0 -2
  163. package/rules/test/js/docs/index.md +2 -0
  164. package/rules/test/js/docs/no-console-store-restore.md +30 -0
  165. package/rules/test/js/docs/sandbox-aware-test.md +30 -0
  166. package/rules/test/js/docs/stryker_config.md +0 -2
  167. package/rules/test/js/docs/vitest-config-pool-forks.md +0 -2
  168. package/rules/test/js/no-console-store-restore.mjs +88 -0
  169. package/rules/test/js/sandbox-aware-test.mjs +89 -0
  170. package/rules/test/main.mdc +1 -21
  171. package/rules/text/docs/index.md +0 -1
  172. package/rules/text/docs/main.md +0 -2
  173. package/rules/text/js/docs/cspell-fix.md +0 -2
  174. package/rules/text/js/docs/run-dotenv-linter.md +0 -2
  175. package/rules/text/js/docs/run-shellcheck.md +0 -2
  176. package/rules/text/js/docs/run-v8r.md +0 -2
  177. package/rules/text/main.mdc +0 -33
  178. package/rules/tool-surface/docs/index.md +0 -1
  179. package/rules/tool-surface/docs/main.md +0 -2
  180. package/rules/vue/docs/index.md +0 -1
  181. package/rules/vue/docs/main.md +0 -2
  182. package/rules/vue/main.mdc +0 -22
  183. package/rules/worktree/docs/index.md +0 -1
  184. package/rules/worktree/docs/main.md +0 -2
  185. package/scripts/docs/auto-rules.md +0 -2
  186. package/scripts/docs/auto-skills.md +0 -2
  187. package/scripts/docs/hook.md +30 -0
  188. package/scripts/docs/index.md +1 -2
  189. package/scripts/docs/post-tool-use-check.md +0 -2
  190. package/scripts/docs/sync-claude-config.md +1 -3
  191. package/scripts/docs/sync-setup-bun-deps-action.md +0 -2
  192. package/scripts/hook.mjs +71 -0
  193. package/scripts/lib/docs/check-mdc-template-refs.md +0 -2
  194. package/scripts/lib/docs/index.md +35 -36
  195. package/scripts/lib/docs/inline-template-links.md +6 -8
  196. package/scripts/lib/docs/list-project-rules-mdc.md +0 -2
  197. package/scripts/lib/docs/list-rule-ids.md +0 -2
  198. package/scripts/lib/docs/mirror-parity.md +8 -10
  199. package/scripts/lib/docs/read-n-cursor-config-lite.md +0 -2
  200. package/scripts/lib/docs/rule-meta.md +0 -2
  201. package/scripts/lib/docs/run-lint.md +9 -10
  202. package/scripts/lib/docs/run-rule-cli.md +0 -2
  203. package/scripts/lib/docs/run-rule.md +7 -9
  204. package/scripts/lib/docs/run-standard-lint.md +0 -2
  205. package/scripts/lib/docs/run-standard-rule.md +0 -2
  206. package/scripts/lib/docs/skill-meta.md +0 -2
  207. package/scripts/lib/docs/timing-summary.md +0 -2
  208. package/scripts/lib/docs/worktree-notice.md +0 -2
  209. package/scripts/lib/fix/docs/analyze-escalation.md +0 -2
  210. package/scripts/lib/fix/docs/index.md +10 -10
  211. package/scripts/lib/fix/docs/llm-worker.md +18 -8
  212. package/scripts/lib/fix/docs/orchestrator.md +44 -20
  213. package/scripts/lib/fix/docs/run-conformance-check.md +0 -2
  214. package/scripts/lib/fix/docs/t0.md +0 -2
  215. package/scripts/lib/fix/docs/verbose-block.md +27 -0
  216. package/scripts/lib/fix/llm-worker.mjs +75 -22
  217. package/scripts/lib/fix/orchestrator.mjs +13 -3
  218. package/scripts/lib/fix/verbose-block.mjs +82 -0
  219. package/scripts/lib/inline-template-links.mjs +32 -22
  220. package/scripts/lib/mirror-parity.mjs +2 -2
  221. package/scripts/lib/run-lint.mjs +15 -2
  222. package/scripts/lib/run-rule.mjs +1 -2
  223. package/scripts/sync-claude-config.mjs +7 -4
  224. package/scripts/utils/docs/resolve-js-root.md +0 -2
  225. package/skills/adr-normalize/SKILL.md +1 -0
  226. package/skills/coverage-fix/SKILL.md +1 -0
  227. package/skills/doc-aggregate/SKILL.md +1 -0
  228. package/skills/doc-files/SKILL.md +10 -24
  229. package/skills/lint/SKILL.md +24 -19
  230. package/skills/llm-patch/SKILL.md +5 -4
  231. package/skills/publish-telegram/SKILL.md +1 -0
  232. package/skills/start-check/SKILL.md +1 -0
  233. package/skills/taze/SKILL.md +3 -2
  234. package/types/bin/n-cursor.d.ts +1 -1
  235. package/rules/abie/docs/fix.md +0 -37
  236. package/rules/adr/docs/fix.md +0 -37
  237. package/rules/bun/docs/fix.md +0 -30
  238. package/rules/capacitor/docs/fix.md +0 -36
  239. package/rules/changelog/docs/fix.md +0 -37
  240. package/rules/ci4/docs/fix.md +0 -32
  241. package/rules/doc-files/docs/fix.md +0 -29
  242. package/rules/docker/docs/fix.md +0 -35
  243. package/rules/efes/docs/fix.md +0 -37
  244. package/rules/feedback/docs/fix.md +0 -30
  245. package/rules/ga/docs/fix.md +0 -30
  246. package/rules/graphql/docs/fix.md +0 -37
  247. package/rules/hasura/docs/fix.md +0 -39
  248. package/rules/image-avif/docs/fix.md +0 -28
  249. package/rules/image-compress/docs/fix.md +0 -27
  250. package/rules/js/docs/fix.md +0 -37
  251. package/rules/js-bun-db/docs/fix.md +0 -30
  252. package/rules/js-bun-redis/docs/fix.md +0 -32
  253. package/rules/js-mssql/docs/fix.md +0 -30
  254. package/rules/js-run/docs/fix.md +0 -36
  255. package/rules/k8s/docs/fix.md +0 -31
  256. package/rules/nginx-default-tpl/docs/fix.md +0 -35
  257. package/rules/npm-module/docs/fix.md +0 -34
  258. package/rules/php/docs/fix.md +0 -35
  259. package/rules/python/docs/fix.md +0 -38
  260. package/rules/rego/docs/fix.md +0 -31
  261. package/rules/release/docs/fix.md +0 -28
  262. package/rules/rust/docs/fix.md +0 -32
  263. package/rules/security/docs/fix.md +0 -33
  264. package/rules/style/docs/fix.md +0 -28
  265. package/rules/tauri/docs/fix.md +0 -39
  266. package/rules/test/docs/fix.md +0 -31
  267. package/rules/text/docs/fix.md +0 -37
  268. package/rules/tool-surface/docs/fix.md +0 -32
  269. package/rules/vue/docs/fix.md +0 -32
  270. package/rules/worktree/docs/fix.md +0 -40
  271. package/scripts/docs/post-tool-use-fix.md +0 -32
  272. package/scripts/docs/worktree-cli.md +0 -27
  273. package/scripts/lib/docs/worktree.md +0 -42
  274. package/scripts/lib/fix/docs/run-fix-check.md +0 -33
@@ -9,10 +9,12 @@ import {
9
9
  findBunSqlPgLeftoverCallInText,
10
10
  findBunSqlUnsafeUseWithoutAllowMarkerInText,
11
11
  findBunSqlUnsafeWithInterpolatedTemplateInText,
12
+ findJsonStringifyBeforeJsonbInText,
12
13
  findPgFormatLikeQueryWrapperInText,
13
14
  findPgFormatShimDefinitionInText,
14
15
  findPgLibImportInText,
15
16
  findPgListenNotifyUsageInText,
17
+ findSqlArrayWithoutTypeArgInText,
16
18
  findUnsafeBunSqlDynamicSqlListInText,
17
19
  findUnsafeBunSqlInListMissingEmptyGuardInText,
18
20
  isBunSqlScanSourceFile,
@@ -83,7 +85,9 @@ async function scanSourcesForBunSqlPatterns(sourcePaths, repoRoot, reporter) {
83
85
  inListGuard: 0,
84
86
  pgLeftover: 0,
85
87
  pgFormatShim: 0,
86
- queryWrapper: 0
88
+ queryWrapper: 0,
89
+ jsonStringifyJsonb: 0,
90
+ sqlArrayNoType: 0
87
91
  }
88
92
  let hasBunSqlImport = false
89
93
  /** @type {{ rel: string, imports: { line: number, snippet: string }[], listenNotify: { line: number, snippet: string, kind: string }[] }[]} */
@@ -127,7 +131,7 @@ function collectPgUsageForFile(content, rel, pgUsage) {
127
131
  * @param {string} content вміст файлу
128
132
  * @param {string} rel posix-шлях відносно `repoRoot`
129
133
  * @param {(msg: string) => void} fail callback при помилці
130
- * @param {{ perRequest: number, unsafeCall: number, dynamicList: number, inListGuard: number, pgLeftover: number, pgFormatShim: number, queryWrapper: number }} counts акумулятори
134
+ * @param {{ perRequest: number, unsafeCall: number, unsafeTemplateInterp: number, dynamicList: number, inListGuard: number, pgLeftover: number, pgFormatShim: number, queryWrapper: number, jsonStringifyJsonb: number, sqlArrayNoType: number }} counts акумулятори
131
135
  * @returns {void}
132
136
  */
133
137
  function scanFileForBunSqlPatterns(content, rel, fail, counts) {
@@ -202,6 +206,22 @@ function scanFileForBunSqlPatterns(content, rel, fail, counts) {
202
206
  `sql\`...\${value}...\` (js-bun-db.mdc): ${v.snippet}`
203
207
  )
204
208
  }
209
+ for (const v of findJsonStringifyBeforeJsonbInText(content, rel)) {
210
+ counts.jsonStringifyJsonb++
211
+ fail(
212
+ `js-bun-db: ${rel}:${v.line} — JSON.stringify(...) перед ::jsonb зайвий: Bun SQL серіалізує ` +
213
+ `об'єкти/масиви у JSON автоматично, явний stringify призводить до подвійної серіалізації ` +
214
+ `(js-bun-db.mdc query-safety): ${v.snippet}`
215
+ )
216
+ }
217
+ for (const v of findSqlArrayWithoutTypeArgInText(content, rel)) {
218
+ counts.sqlArrayNoType++
219
+ fail(
220
+ `js-bun-db: ${rel}:${v.line} — sql.array(arr) без другого аргументу типу — ` +
221
+ `вкажи явний pg-тип: sql.array(arr, 'int8') / sql.array(arr, 'uuid') тощо ` +
222
+ `(js-bun-db.mdc sql-array): ${v.snippet}`
223
+ )
224
+ }
205
225
  }
206
226
 
207
227
  /**
@@ -338,7 +358,9 @@ export async function check(cwd = process.cwd()) {
338
358
  inListGuard,
339
359
  pgLeftover,
340
360
  pgFormatShim,
341
- queryWrapper
361
+ queryWrapper,
362
+ jsonStringifyJsonb,
363
+ sqlArrayNoType
342
364
  } = await scanSourcesForBunSqlPatterns(sourcePaths, repoRoot, reporter)
343
365
 
344
366
  const { pgDepFails, pgImportFails, pgDepsFound, listenNotifyEvidence } = await checkPgDependencyAndUsage(
@@ -396,6 +418,12 @@ export async function check(cwd = process.cwd()) {
396
418
  if (queryWrapper === 0) {
397
419
  pass('js-bun-db: немає query(text, params)-обгорток над unsafe(...) у файлах з Bun SQL')
398
420
  }
421
+ if (jsonStringifyJsonb === 0) {
422
+ pass('js-bun-db: немає JSON.stringify(...) перед ::jsonb — Bun SQL серіалізує автоматично')
423
+ }
424
+ if (sqlArrayNoType === 0) {
425
+ pass('js-bun-db: усі sql.array() мають явний аргумент типу')
426
+ }
399
427
 
400
428
  return reporter.getExitCode()
401
429
  }
@@ -927,3 +927,126 @@ function kindFromListenNotifyMatch(text) {
927
927
  export function isBunSqlScanSourceFile(relativePathPosix) {
928
928
  return SOURCE_FILE_RE.test(relativePathPosix) && !relativePathPosix.endsWith('.d.ts')
929
929
  }
930
+
931
+ // Імена відомих SQL-інстансів, для яких перевіряємо .array() без типу.
932
+ const SQL_INSTANCE_NAMES = new Set(['sql', 'pgWrite', 'pgRead'])
933
+
934
+ /**
935
+ * Чи це виклик `JSON.stringify(...)` (JSON.stringify через MemberExpression).
936
+ * @param {unknown} node AST node
937
+ * @returns {boolean}
938
+ */
939
+ function isJsonStringifyCall(node) {
940
+ if (!node || typeof node !== 'object' || node.type !== 'CallExpression') return false
941
+ const callee = node.callee
942
+ if (!callee || callee.type !== 'MemberExpression' || callee.computed) return false
943
+ const obj = callee.object
944
+ const prop = callee.property
945
+ return (
946
+ !!obj &&
947
+ obj.type === 'Identifier' &&
948
+ obj.name === 'JSON' &&
949
+ !!prop &&
950
+ prop.type === 'Identifier' &&
951
+ prop.name === 'stringify'
952
+ )
953
+ }
954
+
955
+ /**
956
+ * Знаходить виклики `JSON.stringify(...)::jsonb` всередині SQL template literal-ів.
957
+ * Bun SQL серіалізує об'єкти/масиви у JSON автоматично — явний `JSON.stringify`
958
+ * перед `::jsonb` призводить до подвійної серіалізації (js-bun-db.mdc).
959
+ * @param {string} content вихідний код
960
+ * @param {string} [virtualPath] шлях для вибору lang
961
+ * @returns {{ line: number, snippet: string }[]} список порушень
962
+ */
963
+ export function findJsonStringifyBeforeJsonbInText(content, virtualPath = 'scan.ts') {
964
+ const program = parseProgramOrNull(content, virtualPath)
965
+ if (!program) return []
966
+
967
+ /** @type {{ line: number, snippet: string }[]} */
968
+ const out = []
969
+
970
+ walkAstWithAncestors(program, [], node => {
971
+ let template = null
972
+ if (node.type === 'TemplateLiteral') template = node
973
+ else if (node.type === 'TaggedTemplateExpression') template = node.quasi
974
+ if (!template || typeof template !== 'object' || template.type !== 'TemplateLiteral') return
975
+
976
+ const expressions = template.expressions
977
+ const quasis = template.quasis
978
+ if (!Array.isArray(expressions) || !Array.isArray(quasis)) return
979
+
980
+ for (const [i, expr] of expressions.entries()) {
981
+ // Перевіряємо прямий JSON.stringify(...) і JSON.stringify всередині sql.array(...)
982
+ const isDirectStringify = isJsonStringifyCall(expr)
983
+ // sql.array(batch.map(r => JSON.stringify(...)), 'jsonb')
984
+ const hasSqlArrayStringify =
985
+ !isDirectStringify &&
986
+ expr.type === 'CallExpression' &&
987
+ Array.isArray(expr.arguments) &&
988
+ expr.arguments.some(arg => {
989
+ if (isJsonStringifyCall(arg)) return true
990
+ // .map(r => JSON.stringify(...))
991
+ if (arg.type === 'CallExpression' && Array.isArray(arg.arguments)) {
992
+ const cb = arg.arguments[0]
993
+ if (!cb) return false
994
+ const body = cb.type === 'ArrowFunctionExpression' || cb.type === 'FunctionExpression' ? cb.body : null
995
+ if (body && isJsonStringifyCall(body)) return true
996
+ }
997
+ return false
998
+ })
999
+
1000
+ if (!isDirectStringify && !hasSqlArrayStringify) continue
1001
+
1002
+ // Quasi після expr (quasi[i+1]) — текст одразу після закриваючого }
1003
+ const nextQuasi = quasis[i + 1]
1004
+ const rawAfter =
1005
+ nextQuasi && typeof nextQuasi === 'object' && nextQuasi.value && typeof nextQuasi.value.raw === 'string'
1006
+ ? nextQuasi.value.raw
1007
+ : ''
1008
+
1009
+ if (/^\s*::jsonb/u.test(rawAfter) || hasSqlArrayStringify) {
1010
+ out.push({
1011
+ line: offsetToLine(content, expr.start),
1012
+ snippet: normalizeSnippet(content.slice(expr.start, expr.end))
1013
+ })
1014
+ }
1015
+ }
1016
+ })
1017
+ return out
1018
+ }
1019
+
1020
+ /**
1021
+ * Знаходить виклики `sql.array(arr)` / `pgWrite.array(arr)` / `pgRead.array(arr)` без
1022
+ * обов'язкового другого аргументу (типу pg-елемента). Без типу Bun не може вивести
1023
+ * pg-тип, що призводить до mismatch (js-bun-db.mdc).
1024
+ * @param {string} content вихідний код
1025
+ * @param {string} [virtualPath] шлях для вибору lang
1026
+ * @returns {{ line: number, snippet: string }[]} список порушень
1027
+ */
1028
+ export function findSqlArrayWithoutTypeArgInText(content, virtualPath = 'scan.ts') {
1029
+ const program = parseProgramOrNull(content, virtualPath)
1030
+ if (!program) return []
1031
+
1032
+ /** @type {{ line: number, snippet: string }[]} */
1033
+ const out = []
1034
+
1035
+ walkAstWithAncestors(program, [], node => {
1036
+ if (!node || node.type !== 'CallExpression') return
1037
+ const callee = node.callee
1038
+ if (!callee || callee.type !== 'MemberExpression' || callee.computed) return
1039
+ const prop = callee.property
1040
+ if (!prop || prop.type !== 'Identifier' || prop.name !== 'array') return
1041
+ const obj = callee.object
1042
+ if (!obj || obj.type !== 'Identifier' || !SQL_INSTANCE_NAMES.has(obj.name)) return
1043
+ const args = node.arguments
1044
+ if (!Array.isArray(args) || args.length !== 1) return
1045
+
1046
+ out.push({
1047
+ line: offsetToLine(content, node.start),
1048
+ snippet: normalizeSnippet(content.slice(node.start, node.end))
1049
+ })
1050
+ })
1051
+ return out
1052
+ }