@endora-commerce/cli 0.100.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 (356) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/bin/endora.d.ts +28 -0
  4. package/dist/bin/endora.d.ts.map +1 -0
  5. package/dist/bin/endora.js +926 -0
  6. package/dist/bin/endora.js.map +1 -0
  7. package/dist/check/estate.d.ts +189 -0
  8. package/dist/check/estate.d.ts.map +1 -0
  9. package/dist/check/estate.js +1037 -0
  10. package/dist/check/estate.js.map +1 -0
  11. package/dist/check/hosts.d.ts +25 -0
  12. package/dist/check/hosts.d.ts.map +1 -0
  13. package/dist/check/hosts.js +1347 -0
  14. package/dist/check/hosts.js.map +1 -0
  15. package/dist/check/index.d.ts +60 -0
  16. package/dist/check/index.d.ts.map +1 -0
  17. package/dist/check/index.js +111 -0
  18. package/dist/check/index.js.map +1 -0
  19. package/dist/check/layout.d.ts +136 -0
  20. package/dist/check/layout.d.ts.map +1 -0
  21. package/dist/check/layout.js +262 -0
  22. package/dist/check/layout.js.map +1 -0
  23. package/dist/check/ledger.d.ts +98 -0
  24. package/dist/check/ledger.d.ts.map +1 -0
  25. package/dist/check/ledger.js +173 -0
  26. package/dist/check/ledger.js.map +1 -0
  27. package/dist/check/peer-owners.d.ts +116 -0
  28. package/dist/check/peer-owners.d.ts.map +1 -0
  29. package/dist/check/peer-owners.js +225 -0
  30. package/dist/check/peer-owners.js.map +1 -0
  31. package/dist/check/report.d.ts +33 -0
  32. package/dist/check/report.d.ts.map +1 -0
  33. package/dist/check/report.js +107 -0
  34. package/dist/check/report.js.map +1 -0
  35. package/dist/check/run.d.ts +147 -0
  36. package/dist/check/run.d.ts.map +1 -0
  37. package/dist/check/run.js +111 -0
  38. package/dist/check/run.js.map +1 -0
  39. package/dist/checks.d.ts +17 -0
  40. package/dist/checks.d.ts.map +1 -0
  41. package/dist/checks.js +17 -0
  42. package/dist/checks.js.map +1 -0
  43. package/dist/dev/index.d.ts +83 -0
  44. package/dist/dev/index.d.ts.map +1 -0
  45. package/dist/dev/index.js +298 -0
  46. package/dist/dev/index.js.map +1 -0
  47. package/dist/generate/divergence.d.ts +38 -0
  48. package/dist/generate/divergence.d.ts.map +1 -0
  49. package/dist/generate/divergence.js +237 -0
  50. package/dist/generate/divergence.js.map +1 -0
  51. package/dist/generate/index.d.ts +90 -0
  52. package/dist/generate/index.d.ts.map +1 -0
  53. package/dist/generate/index.js +369 -0
  54. package/dist/generate/index.js.map +1 -0
  55. package/dist/index.d.ts +23 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +35 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/inputs/declaration.d.ts +18 -0
  60. package/dist/inputs/declaration.d.ts.map +1 -0
  61. package/dist/inputs/declaration.js +64 -0
  62. package/dist/inputs/declaration.js.map +1 -0
  63. package/dist/inputs/env-file.d.ts +73 -0
  64. package/dist/inputs/env-file.d.ts.map +1 -0
  65. package/dist/inputs/env-file.js +134 -0
  66. package/dist/inputs/env-file.js.map +1 -0
  67. package/dist/inputs/prompt.d.ts +21 -0
  68. package/dist/inputs/prompt.d.ts.map +1 -0
  69. package/dist/inputs/prompt.js +59 -0
  70. package/dist/inputs/prompt.js.map +1 -0
  71. package/dist/inputs/resolve.d.ts +163 -0
  72. package/dist/inputs/resolve.d.ts.map +1 -0
  73. package/dist/inputs/resolve.js +290 -0
  74. package/dist/inputs/resolve.js.map +1 -0
  75. package/dist/install/host.d.ts +27 -0
  76. package/dist/install/host.d.ts.map +1 -0
  77. package/dist/install/host.js +90 -0
  78. package/dist/install/host.js.map +1 -0
  79. package/dist/install/index.d.ts +173 -0
  80. package/dist/install/index.d.ts.map +1 -0
  81. package/dist/install/index.js +793 -0
  82. package/dist/install/index.js.map +1 -0
  83. package/dist/install/wizard.d.ts +144 -0
  84. package/dist/install/wizard.d.ts.map +1 -0
  85. package/dist/install/wizard.js +362 -0
  86. package/dist/install/wizard.js.map +1 -0
  87. package/dist/lib/admin-artefacts.d.ts +70 -0
  88. package/dist/lib/admin-artefacts.d.ts.map +1 -0
  89. package/dist/lib/admin-artefacts.js +354 -0
  90. package/dist/lib/admin-artefacts.js.map +1 -0
  91. package/dist/lib/admin-surfaces.d.ts +298 -0
  92. package/dist/lib/admin-surfaces.d.ts.map +1 -0
  93. package/dist/lib/admin-surfaces.js +669 -0
  94. package/dist/lib/admin-surfaces.js.map +1 -0
  95. package/dist/lib/delegated-composer.d.ts +85 -0
  96. package/dist/lib/delegated-composer.d.ts.map +1 -0
  97. package/dist/lib/delegated-composer.js +241 -0
  98. package/dist/lib/delegated-composer.js.map +1 -0
  99. package/dist/lib/divergence-artefacts.d.ts +305 -0
  100. package/dist/lib/divergence-artefacts.d.ts.map +1 -0
  101. package/dist/lib/divergence-artefacts.js +828 -0
  102. package/dist/lib/divergence-artefacts.js.map +1 -0
  103. package/dist/lib/divergence.d.ts +337 -0
  104. package/dist/lib/divergence.d.ts.map +1 -0
  105. package/dist/lib/divergence.js +1005 -0
  106. package/dist/lib/divergence.js.map +1 -0
  107. package/dist/lib/docs-artefacts.d.ts +395 -0
  108. package/dist/lib/docs-artefacts.d.ts.map +1 -0
  109. package/dist/lib/docs-artefacts.js +781 -0
  110. package/dist/lib/docs-artefacts.js.map +1 -0
  111. package/dist/lib/emitted-exports.d.ts +21 -0
  112. package/dist/lib/emitted-exports.d.ts.map +1 -0
  113. package/dist/lib/emitted-exports.js +96 -0
  114. package/dist/lib/emitted-exports.js.map +1 -0
  115. package/dist/lib/emitted-freshness.d.ts +112 -0
  116. package/dist/lib/emitted-freshness.d.ts.map +1 -0
  117. package/dist/lib/emitted-freshness.js +288 -0
  118. package/dist/lib/emitted-freshness.js.map +1 -0
  119. package/dist/lib/entity-index-artefact.d.ts +95 -0
  120. package/dist/lib/entity-index-artefact.d.ts.map +1 -0
  121. package/dist/lib/entity-index-artefact.js +210 -0
  122. package/dist/lib/entity-index-artefact.js.map +1 -0
  123. package/dist/lib/instance-build-inputs.d.ts +108 -0
  124. package/dist/lib/instance-build-inputs.d.ts.map +1 -0
  125. package/dist/lib/instance-build-inputs.js +165 -0
  126. package/dist/lib/instance-build-inputs.js.map +1 -0
  127. package/dist/lib/module-docs.d.ts +473 -0
  128. package/dist/lib/module-docs.d.ts.map +1 -0
  129. package/dist/lib/module-docs.js +711 -0
  130. package/dist/lib/module-docs.js.map +1 -0
  131. package/dist/lib/module-package-subpaths.d.ts +56 -0
  132. package/dist/lib/module-package-subpaths.d.ts.map +1 -0
  133. package/dist/lib/module-package-subpaths.js +223 -0
  134. package/dist/lib/module-package-subpaths.js.map +1 -0
  135. package/dist/lib/module-packages.d.ts +200 -0
  136. package/dist/lib/module-packages.d.ts.map +1 -0
  137. package/dist/lib/module-packages.js +580 -0
  138. package/dist/lib/module-packages.js.map +1 -0
  139. package/dist/lib/module-population.d.ts +129 -0
  140. package/dist/lib/module-population.d.ts.map +1 -0
  141. package/dist/lib/module-population.js +172 -0
  142. package/dist/lib/module-population.js.map +1 -0
  143. package/dist/lib/module-roots.d.ts +337 -0
  144. package/dist/lib/module-roots.d.ts.map +1 -0
  145. package/dist/lib/module-roots.js +586 -0
  146. package/dist/lib/module-roots.js.map +1 -0
  147. package/dist/lib/nested-checkouts.d.ts +33 -0
  148. package/dist/lib/nested-checkouts.d.ts.map +1 -0
  149. package/dist/lib/nested-checkouts.js +160 -0
  150. package/dist/lib/nested-checkouts.js.map +1 -0
  151. package/dist/lib/platform-root.d.ts +43 -0
  152. package/dist/lib/platform-root.d.ts.map +1 -0
  153. package/dist/lib/platform-root.js +134 -0
  154. package/dist/lib/platform-root.js.map +1 -0
  155. package/dist/lib/platform-surface.d.ts +235 -0
  156. package/dist/lib/platform-surface.d.ts.map +1 -0
  157. package/dist/lib/platform-surface.js +393 -0
  158. package/dist/lib/platform-surface.js.map +1 -0
  159. package/dist/lib/port-registrations.d.ts +223 -0
  160. package/dist/lib/port-registrations.d.ts.map +1 -0
  161. package/dist/lib/port-registrations.js +532 -0
  162. package/dist/lib/port-registrations.js.map +1 -0
  163. package/dist/lib/read-size.d.ts +154 -0
  164. package/dist/lib/read-size.d.ts.map +1 -0
  165. package/dist/lib/read-size.js +182 -0
  166. package/dist/lib/read-size.js.map +1 -0
  167. package/dist/lib/registration-owners.d.ts +79 -0
  168. package/dist/lib/registration-owners.d.ts.map +1 -0
  169. package/dist/lib/registration-owners.js +77 -0
  170. package/dist/lib/registration-owners.js.map +1 -0
  171. package/dist/lib/release-index.d.ts +53 -0
  172. package/dist/lib/release-index.d.ts.map +1 -0
  173. package/dist/lib/release-index.js +162 -0
  174. package/dist/lib/release-index.js.map +1 -0
  175. package/dist/lib/repeating-timers.d.ts +79 -0
  176. package/dist/lib/repeating-timers.d.ts.map +1 -0
  177. package/dist/lib/repeating-timers.js +189 -0
  178. package/dist/lib/repeating-timers.js.map +1 -0
  179. package/dist/lib/source-text.d.ts +34 -0
  180. package/dist/lib/source-text.d.ts.map +1 -0
  181. package/dist/lib/source-text.js +80 -0
  182. package/dist/lib/source-text.js.map +1 -0
  183. package/dist/lib/specifiers.d.ts +20 -0
  184. package/dist/lib/specifiers.d.ts.map +1 -0
  185. package/dist/lib/specifiers.js +130 -0
  186. package/dist/lib/specifiers.js.map +1 -0
  187. package/dist/lib/sql-tables.d.ts +166 -0
  188. package/dist/lib/sql-tables.d.ts.map +1 -0
  189. package/dist/lib/sql-tables.js +464 -0
  190. package/dist/lib/sql-tables.js.map +1 -0
  191. package/dist/lib/switchable-modules.d.ts +54 -0
  192. package/dist/lib/switchable-modules.d.ts.map +1 -0
  193. package/dist/lib/switchable-modules.js +104 -0
  194. package/dist/lib/switchable-modules.js.map +1 -0
  195. package/dist/lib/tailwind-sources.d.ts +136 -0
  196. package/dist/lib/tailwind-sources.d.ts.map +1 -0
  197. package/dist/lib/tailwind-sources.js +307 -0
  198. package/dist/lib/tailwind-sources.js.map +1 -0
  199. package/dist/lib/ui-layer.d.ts +54 -0
  200. package/dist/lib/ui-layer.d.ts.map +1 -0
  201. package/dist/lib/ui-layer.js +57 -0
  202. package/dist/lib/ui-layer.js.map +1 -0
  203. package/dist/lib/workspace-packages.d.ts +186 -0
  204. package/dist/lib/workspace-packages.d.ts.map +1 -0
  205. package/dist/lib/workspace-packages.js +351 -0
  206. package/dist/lib/workspace-packages.js.map +1 -0
  207. package/dist/new-instance/deploy.d.ts +211 -0
  208. package/dist/new-instance/deploy.d.ts.map +1 -0
  209. package/dist/new-instance/deploy.js +1381 -0
  210. package/dist/new-instance/deploy.js.map +1 -0
  211. package/dist/new-instance/docs-toolchain.d.ts +66 -0
  212. package/dist/new-instance/docs-toolchain.d.ts.map +1 -0
  213. package/dist/new-instance/docs-toolchain.js +69 -0
  214. package/dist/new-instance/docs-toolchain.js.map +1 -0
  215. package/dist/new-instance/host.d.ts +124 -0
  216. package/dist/new-instance/host.d.ts.map +1 -0
  217. package/dist/new-instance/host.js +276 -0
  218. package/dist/new-instance/host.js.map +1 -0
  219. package/dist/new-instance/index.d.ts +118 -0
  220. package/dist/new-instance/index.d.ts.map +1 -0
  221. package/dist/new-instance/index.js +567 -0
  222. package/dist/new-instance/index.js.map +1 -0
  223. package/dist/new-instance/modules.d.ts +188 -0
  224. package/dist/new-instance/modules.d.ts.map +1 -0
  225. package/dist/new-instance/modules.js +392 -0
  226. package/dist/new-instance/modules.js.map +1 -0
  227. package/dist/new-instance/template.d.ts +505 -0
  228. package/dist/new-instance/template.d.ts.map +1 -0
  229. package/dist/new-instance/template.js +1886 -0
  230. package/dist/new-instance/template.js.map +1 -0
  231. package/dist/new-module/emit.d.ts +67 -0
  232. package/dist/new-module/emit.d.ts.map +1 -0
  233. package/dist/new-module/emit.js +1393 -0
  234. package/dist/new-module/emit.js.map +1 -0
  235. package/dist/new-module/host.d.ts +67 -0
  236. package/dist/new-module/host.d.ts.map +1 -0
  237. package/dist/new-module/host.js +224 -0
  238. package/dist/new-module/host.js.map +1 -0
  239. package/dist/new-module/index.d.ts +32 -0
  240. package/dist/new-module/index.d.ts.map +1 -0
  241. package/dist/new-module/index.js +193 -0
  242. package/dist/new-module/index.js.map +1 -0
  243. package/dist/new-module/spec.d.ts +188 -0
  244. package/dist/new-module/spec.d.ts.map +1 -0
  245. package/dist/new-module/spec.js +404 -0
  246. package/dist/new-module/spec.js.map +1 -0
  247. package/dist/new-module/text.d.ts +15 -0
  248. package/dist/new-module/text.d.ts.map +1 -0
  249. package/dist/new-module/text.js +22 -0
  250. package/dist/new-module/text.js.map +1 -0
  251. package/dist/new-storefront/dockerfile.d.ts +20 -0
  252. package/dist/new-storefront/dockerfile.d.ts.map +1 -0
  253. package/dist/new-storefront/dockerfile.js +131 -0
  254. package/dist/new-storefront/dockerfile.js.map +1 -0
  255. package/dist/new-storefront/gitignore.d.ts +23 -0
  256. package/dist/new-storefront/gitignore.d.ts.map +1 -0
  257. package/dist/new-storefront/gitignore.js +39 -0
  258. package/dist/new-storefront/gitignore.js.map +1 -0
  259. package/dist/new-storefront/index.d.ts +93 -0
  260. package/dist/new-storefront/index.d.ts.map +1 -0
  261. package/dist/new-storefront/index.js +329 -0
  262. package/dist/new-storefront/index.js.map +1 -0
  263. package/dist/new-storefront/npmrc.d.ts +98 -0
  264. package/dist/new-storefront/npmrc.d.ts.map +1 -0
  265. package/dist/new-storefront/npmrc.js +217 -0
  266. package/dist/new-storefront/npmrc.js.map +1 -0
  267. package/dist/new-storefront/reference.d.ts +189 -0
  268. package/dist/new-storefront/reference.d.ts.map +1 -0
  269. package/dist/new-storefront/reference.js +430 -0
  270. package/dist/new-storefront/reference.js.map +1 -0
  271. package/dist/new-storefront/rewrite.d.ts +171 -0
  272. package/dist/new-storefront/rewrite.d.ts.map +1 -0
  273. package/dist/new-storefront/rewrite.js +701 -0
  274. package/dist/new-storefront/rewrite.js.map +1 -0
  275. package/dist/release-index.json +284 -0
  276. package/dist/rules/action-route-permissions.d.ts +141 -0
  277. package/dist/rules/action-route-permissions.d.ts.map +1 -0
  278. package/dist/rules/action-route-permissions.js +556 -0
  279. package/dist/rules/action-route-permissions.js.map +1 -0
  280. package/dist/rules/bundle-pairing.d.ts +74 -0
  281. package/dist/rules/bundle-pairing.d.ts.map +1 -0
  282. package/dist/rules/bundle-pairing.js +281 -0
  283. package/dist/rules/bundle-pairing.js.map +1 -0
  284. package/dist/rules/channel-resolution.d.ts +17 -0
  285. package/dist/rules/channel-resolution.d.ts.map +1 -0
  286. package/dist/rules/channel-resolution.js +382 -0
  287. package/dist/rules/channel-resolution.js.map +1 -0
  288. package/dist/rules/command-coverage.d.ts +210 -0
  289. package/dist/rules/command-coverage.d.ts.map +1 -0
  290. package/dist/rules/command-coverage.js +714 -0
  291. package/dist/rules/command-coverage.js.map +1 -0
  292. package/dist/rules/container-imports.d.ts +60 -0
  293. package/dist/rules/container-imports.d.ts.map +1 -0
  294. package/dist/rules/container-imports.js +158 -0
  295. package/dist/rules/container-imports.js.map +1 -0
  296. package/dist/rules/default-language-prose.d.ts +212 -0
  297. package/dist/rules/default-language-prose.d.ts.map +1 -0
  298. package/dist/rules/default-language-prose.js +710 -0
  299. package/dist/rules/default-language-prose.js.map +1 -0
  300. package/dist/rules/diacritic-folds.d.ts +238 -0
  301. package/dist/rules/diacritic-folds.d.ts.map +1 -0
  302. package/dist/rules/diacritic-folds.js +681 -0
  303. package/dist/rules/diacritic-folds.js.map +1 -0
  304. package/dist/rules/entity-tenant-classification.d.ts +171 -0
  305. package/dist/rules/entity-tenant-classification.d.ts.map +1 -0
  306. package/dist/rules/entity-tenant-classification.js +323 -0
  307. package/dist/rules/entity-tenant-classification.js.map +1 -0
  308. package/dist/rules/entry-presence.d.ts +142 -0
  309. package/dist/rules/entry-presence.d.ts.map +1 -0
  310. package/dist/rules/entry-presence.js +339 -0
  311. package/dist/rules/entry-presence.js.map +1 -0
  312. package/dist/rules/entry-scope.d.ts +91 -0
  313. package/dist/rules/entry-scope.d.ts.map +1 -0
  314. package/dist/rules/entry-scope.js +404 -0
  315. package/dist/rules/entry-scope.js.map +1 -0
  316. package/dist/rules/env-inputs.d.ts +222 -0
  317. package/dist/rules/env-inputs.d.ts.map +1 -0
  318. package/dist/rules/env-inputs.js +951 -0
  319. package/dist/rules/env-inputs.js.map +1 -0
  320. package/dist/rules/kernel-boundary.d.ts +37 -0
  321. package/dist/rules/kernel-boundary.d.ts.map +1 -0
  322. package/dist/rules/kernel-boundary.js +195 -0
  323. package/dist/rules/kernel-boundary.js.map +1 -0
  324. package/dist/rules/nul-bytes.d.ts +233 -0
  325. package/dist/rules/nul-bytes.d.ts.map +1 -0
  326. package/dist/rules/nul-bytes.js +332 -0
  327. package/dist/rules/nul-bytes.js.map +1 -0
  328. package/dist/rules/platform-surface.d.ts +479 -0
  329. package/dist/rules/platform-surface.d.ts.map +1 -0
  330. package/dist/rules/platform-surface.js +749 -0
  331. package/dist/rules/platform-surface.js.map +1 -0
  332. package/dist/rules/port-catches.d.ts +225 -0
  333. package/dist/rules/port-catches.d.ts.map +1 -0
  334. package/dist/rules/port-catches.js +1374 -0
  335. package/dist/rules/port-catches.js.map +1 -0
  336. package/dist/rules/port-shape.d.ts +213 -0
  337. package/dist/rules/port-shape.d.ts.map +1 -0
  338. package/dist/rules/port-shape.js +670 -0
  339. package/dist/rules/port-shape.js.map +1 -0
  340. package/dist/rules/queue-names.d.ts +108 -0
  341. package/dist/rules/queue-names.d.ts.map +1 -0
  342. package/dist/rules/queue-names.js +395 -0
  343. package/dist/rules/queue-names.js.map +1 -0
  344. package/dist/rules/singleton-identity.d.ts +205 -0
  345. package/dist/rules/singleton-identity.d.ts.map +1 -0
  346. package/dist/rules/singleton-identity.js +830 -0
  347. package/dist/rules/singleton-identity.js.map +1 -0
  348. package/dist/rules/subscribe-seam.d.ts +121 -0
  349. package/dist/rules/subscribe-seam.d.ts.map +1 -0
  350. package/dist/rules/subscribe-seam.js +594 -0
  351. package/dist/rules/subscribe-seam.js.map +1 -0
  352. package/dist/rules/transaction-context.d.ts +40 -0
  353. package/dist/rules/transaction-context.d.ts.map +1 -0
  354. package/dist/rules/transaction-context.js +294 -0
  355. package/dist/rules/transaction-context.js.map +1 -0
  356. package/package.json +59 -0
@@ -0,0 +1,710 @@
1
+ /**
2
+ * CI check — **English is the default**, so a prose literal in another language,
3
+ * outside a per-language structure, in a module's own sources is a defect
4
+ * (`specs/094-translation-boundary/`,
5
+ * `contracts/default-language-prose-check.md`).
6
+ *
7
+ * The **second signal** for `contracts/translation-boundary.md` § 3 clause 1,
8
+ * and a check of its own rather than a widening of either instrument that looks
9
+ * like its home. `../research.md` § 8 D-8 records why:
10
+ * `check:untranslated-delivery`'s predicate is *a delivery call that passes
11
+ * prose and no key*, and a `return` is not a call, an array push is not a call
12
+ * and a field assignment is not a call; `check:language`'s scope is **comments**
13
+ * by constitutional design (Principle VIII v3.0.0 permits a string literal in
14
+ * any language), and widening it would make every `{ en, pl }` map a violation.
15
+ *
16
+ * ## The predicate keys on the language of the string, not on how it is carried
17
+ *
18
+ * `specs/093-backend-delivered-prose/` named its population after three delivery
19
+ * **mechanisms** — a notification port, a mailer, a pdfmake renderer — and
20
+ * mechanisms are unbounded: six more shapes were in the tree the whole time (a
21
+ * `return` from a route handler, a push into a persisted operation log, a
22
+ * persisted document line item, a Polish default behind an operator-editable
23
+ * prop, a CMS seed, a bilingual ternary), and **30 of the 36 sites were outside
24
+ * the survey**. A string does not become user-facing by being carried; it
25
+ * becomes user-facing by being read.
26
+ *
27
+ * So this check asks one question of the literal itself, and it answers for the
28
+ * seventh shape nobody has written yet.
29
+ *
30
+ * ## The asymmetry is the rule rather than a limitation
31
+ *
32
+ * It finds non-English prose and says nothing about English prose. Under a
33
+ * ruling that makes English the default, an English literal is not a finding —
34
+ * so the check enforces the ruling **as written**. It is not an instrument for
35
+ * "is this string translated"; that is `check:untranslated-delivery`'s question
36
+ * for case 2 and `tsc`'s for the storefront.
37
+ *
38
+ * ## What "outside a per-language structure" means, derived from the syntax
39
+ *
40
+ * A filename rule is refused (`check:diacritic-folds`' exemption discipline):
41
+ * the exemption is a property of the **structure**, so a per-language map in a
42
+ * file with an ordinary name is exempt and a bare literal in a file called
43
+ * `translations.pl-PL.ts` is not. Three shapes, all decided from the syntax:
44
+ *
45
+ * * **`language-keyed-property`** — an enclosing property assignment, at any
46
+ * depth, whose key is a shipped language or a locale whose primary subtag is
47
+ * one: `'pl-PL': { … }`, `pl: 'Przesyłka wysłana'`. It is one shape and not
48
+ * two, because § 1.2's "an English sibling at the same depth under a
49
+ * language key" is the same fact read from the other member — the key that
50
+ * reaches *this* literal is already a language.
51
+ * * **`language-declaring-object`** — the enclosing object literal declares
52
+ * which language it is written in, in a property whose **name** is one of
53
+ * {@link LANGUAGE_DECLARING_FIELDS} and whose value is a shipped language.
54
+ * A `dictionaries` seed row is the shape —
55
+ * `{ entryType: 'currency', entryCode: 'PLN', languageCode: 'pl-PL', label: '…' }`
56
+ * — and the field **name** is load-bearing: the same row's
57
+ * `entryCode: 'en-US'` is the row's *subject*, not its language, so a rule
58
+ * reading "any sibling holding a language code" would exempt a countries
59
+ * seed keyed `code: 'pl'` as well.
60
+ * * **`single-language-file`** — the file's own declared subject is one
61
+ * language, evidenced by a language **code** in a declared export name
62
+ * (`export const PL_MESSAGES`, `messagesPlPL`). The other evidence § 1.2
63
+ * names, a manifest-declared bundle path, is vacuous here rather than
64
+ * unimplemented: a bundle directory holds `en.json` and `pl.json`, and this
65
+ * walk opens no JSON.
66
+ *
67
+ * ## Detection, and its declared bound
68
+ *
69
+ * Non-English detection is **Polish only**, by diacritic plus a stopword list,
70
+ * reusing `scripts/check-language.sh`'s machinery and its `proper_nouns` list
71
+ * ({@link POLISH_PROPER_NOUNS}, held to the shell list by the companion test).
72
+ * The bound is declared rather than discovered: a check that claimed to detect
73
+ * "any non-English prose" and detected Polish would be the thing this repository
74
+ * refuses. Two consequences:
75
+ *
76
+ * * **Polish prose carrying no diacritic and no stopword is invisible.**
77
+ * `'Nowa Organizacja'` is the measured example, and it is why the stopword
78
+ * list sits *beside* the diacritic class rather than instead of it:
79
+ * `'Zamówienie nie zostało opłacone'` is caught by `nie` when its diacritics
80
+ * are typed as `Zamowienie`.
81
+ * * **A third shipped language needs its own detector.** The set is derived
82
+ * from `SUPPORTED_LANGUAGES`, so the gap is visible the moment it grows: the
83
+ * `read:` line reconciles the two, and a shipped language with no detector
84
+ * is exit 2 rather than a silent implied coverage claim.
85
+ *
86
+ * ## What it does not see, stated rather than discovered later
87
+ *
88
+ * * **A string assembled from fragments**, as a whole. Each fragment that is
89
+ * itself Polish prose is still a site — the two consent seeders are two
90
+ * concatenated literals each — but a sentence split so that no fragment is
91
+ * prose on its own is invisible.
92
+ * * **A single-word literal**, outside the prop-default position. A currency
93
+ * numeral table (`'pięć'`, `'sześćdziesiąt'`) and a unit (`'usł.'`) are not
94
+ * prose, and a token-count floor is the only thing separating them from a
95
+ * label; in the prop-default position the *position* supplies what the token
96
+ * count otherwise has to infer, so `str(props, 'labelPaid', 'Zapłacono')` is
97
+ * a finding and the numeral table is not.
98
+ * * **A module's `migrations/`.** Two reasons, and the first is sufficient: an
99
+ * applied migration cannot be edited, so the repair for a Polish seed there
100
+ * is a *new* migration and a ledger entry over the site would never drain.
101
+ * The second is that this tree's migration prose is per-language JSON inside
102
+ * a SQL string (`'{"en-US":"Card","pl-PL":"Karta płatnicza"}'::jsonb`), which
103
+ * a TypeScript structure analysis cannot see into and would report as bare
104
+ * Polish. The CMS consent seeders are services, not migrations, and stay in.
105
+ * * **A string read from a database or a fixture**, prose in a **comment**
106
+ * (`check:language`'s, deliberately), and `admin/` and `storefront/`
107
+ * sources, which are `i18n:hardcoded`'s and `tsc`'s respectively. `.tsx` is
108
+ * out of the walk for that last reason.
109
+ *
110
+ * ## The ledger
111
+ *
112
+ * `NON_ENGLISH_DEFAULTS`, sharded per module under {@link LEDGER_ROOT}
113
+ * (`scripts/ledgers/non-english-defaults/`), one file per module exporting an
114
+ * `entries` record, two-way, keyed `(file, digest of the literal)` — a **digest, never a line**, so an insertion above the site does not
115
+ * red it and an edited sentence does.
116
+ *
117
+ * It opens holding the sites standing when it lands and is expected to
118
+ * **empty**. An entry says why a non-English default is right; the only entries
119
+ * that should survive are domain-mandated ones — the KSeF offline marking on a
120
+ * Polish invoice is the standing example.
121
+ *
122
+ * Usage: `tsx scripts/check-default-language-prose.ts [--list]`
123
+ * Exit 0 = every prose literal outside a per-language structure is English;
124
+ * exit 1 = at least one is not, or an entry no longer describes one; exit 2 =
125
+ * the run could not see the population it judges — no shipped language, no
126
+ * registered module, a module walk that came back short, no literal read at
127
+ * all, or no detector for a shipped language.
128
+ */
129
+ import { createHash } from 'node:crypto';
130
+ import { existsSync, readdirSync, statSync } from 'node:fs';
131
+ import { join } from 'node:path';
132
+ import { pathToFileURL } from 'node:url';
133
+ import ts from 'typescript';
134
+ /**
135
+ * Polish diacritics — `scripts/check-language.sh`'s own `pattern`, character for
136
+ * character, so the two instruments cannot come to disagree about what a Polish
137
+ * letter is. `ó` is in it although it is shared with Icelandic and Spanish:
138
+ * dropping it would make `'Potwierdzenie zamówienia'` invisible, which is a
139
+ * larger hole than the two English currency labels it costs.
140
+ */
141
+ export const POLISH_DIACRITICS = 'ąĄćĆęĘłŁńŃóÓśŚźŹżŻ';
142
+ const POLISH_DIACRITIC_RE = new RegExp(`[${POLISH_DIACRITICS}]`, 'u');
143
+ /**
144
+ * Polish function words, for the prose that carries no diacritic.
145
+ *
146
+ * Function words only, and deliberately: a domain vocabulary would be a
147
+ * population defined by the presence of the words somebody thought to enumerate
148
+ * (issue #244). Every entry is a form that is not also an English word — `pod`,
149
+ * `ten`, `to`, `do`, `on`, `z` and `we` are all excluded for that reason, and
150
+ * their absence is why `'Data wystawienia'` is one of the blind spots § 1.3
151
+ * declares. The exclusions are measured rather than assumed, twice:
152
+ *
153
+ * * **`we`** (Polish "in") was in the list for one run and reported five
154
+ * English sentences, `'…and "we do not want files" is not a business
155
+ * decision'` among them.
156
+ * * **`w`** (Polish "in") is the single most useful Polish function word and is
157
+ * out for a different reason: it is **one letter**, and a one-letter token is
158
+ * not a word in a tree full of SQL aliases and XML namespaces. Measured: it
159
+ * found two real sites (`'Faktura wystawiona w trybie offline.'` and
160
+ * `'Numer w KSeF'`, both diacritic-free Polish) and eight wrong ones — a
161
+ * `select w.id, w.code …` join alias and seven `http://www.w3.org/…`
162
+ * signature algorithm URIs. Its absence is the second half of § 1.3's
163
+ * declared bound, and the two sites it costs are Polish legal artefacts a
164
+ * ledger entry would keep either way.
165
+ *
166
+ * So the stopword pass keeps {@link proseTokens}' floor: a token is a word when
167
+ * it has two letters.
168
+ */
169
+ export const POLISH_STOPWORDS = [
170
+ 'aby',
171
+ 'albo',
172
+ 'bardzo',
173
+ 'bez',
174
+ 'czy',
175
+ 'dla',
176
+ 'gdy',
177
+ 'gdzie',
178
+ 'jak',
179
+ 'jako',
180
+ 'jest',
181
+ 'jeszcze',
182
+ 'jeśli',
183
+ 'już',
184
+ 'która',
185
+ 'które',
186
+ 'który',
187
+ 'lub',
188
+ 'może',
189
+ 'można',
190
+ 'nad',
191
+ 'nasz',
192
+ 'nasza',
193
+ 'nasze',
194
+ 'nie',
195
+ 'niż',
196
+ 'oraz',
197
+ 'przez',
198
+ 'przy',
199
+ 'się',
200
+ 'są',
201
+ 'także',
202
+ 'tego',
203
+ 'temu',
204
+ 'tylko',
205
+ 'tym',
206
+ 'twoja',
207
+ 'twoje',
208
+ 'twój',
209
+ 'wszystkie',
210
+ 'wtedy',
211
+ 'więc',
212
+ 'zostało',
213
+ 'zostały',
214
+ 'została',
215
+ 'został',
216
+ 'ze',
217
+ 'że',
218
+ 'żeby',
219
+ ];
220
+ const STOPWORDS = new Set(POLISH_STOPWORDS);
221
+ /**
222
+ * Polish proper nouns that stay Polish inside English prose — the list
223
+ * `scripts/check-language.sh` carries, blanked before the detector runs for the
224
+ * same reason it blanks them: an English sentence routinely names a Polish
225
+ * institution, and translating the name would make it wrong rather than more
226
+ * English.
227
+ *
228
+ * Held to the shell list, in both directions, by the companion test. Matched
229
+ * **case-sensitively and as a whole phrase**, exactly as the shell does, so
230
+ * `'Metoda płatności'` — an invoice label with a lower-case second word — is
231
+ * still judged while the feature name `Metoda Płatności` is not.
232
+ */
233
+ export const POLISH_PROPER_NOUNS = [
234
+ 'Ministerstwo Finansów',
235
+ 'Biała lista',
236
+ 'Metoda Płatności',
237
+ 'Szybkie Zamówienia',
238
+ ];
239
+ /**
240
+ * Property names whose value declares which language the enclosing object is
241
+ * written in — the `language-declaring-object` exemption.
242
+ *
243
+ * A vocabulary and not a derivation, because there is nothing in the tree that
244
+ * declares it; it is kept to the four spellings that mean exactly this, and the
245
+ * reason it cannot be widened to "a sibling holding a language code" is in the
246
+ * header: a seed row's `entryCode: 'en-US'` is the row's subject.
247
+ */
248
+ export const LANGUAGE_DECLARING_FIELDS = [
249
+ 'lang',
250
+ 'language',
251
+ 'languageCode',
252
+ 'locale',
253
+ ];
254
+ const DECLARING_FIELDS = new Set(LANGUAGE_DECLARING_FIELDS);
255
+ /** The languages a detector exists for. Polish, and § 1.3 declares the bound. */
256
+ export const DETECTED_LANGUAGES = ['pl'];
257
+ // --- the detector ----------------------------------------------------------
258
+ /** The word tokens a literal holds — letters only, so a placeholder splits. */
259
+ export function proseTokens(text) {
260
+ return [...text.matchAll(/\p{L}[\p{L}'’-]*/gu)]
261
+ .map((match) => match[0])
262
+ .filter((token) => token.length >= 2);
263
+ }
264
+ /**
265
+ * Whether the literal is prose at all.
266
+ *
267
+ * Two word tokens, or one in the prop-default position — see the header: there
268
+ * the position establishes that the string is a label a human reads, which is
269
+ * what the token count otherwise has to infer.
270
+ */
271
+ export function isProse(text, inPropDefault = false) {
272
+ const tokens = proseTokens(text);
273
+ return inPropDefault ? tokens.length >= 1 : tokens.length >= 2;
274
+ }
275
+ /** The proper nouns removed, so a citation is not read as prose. */
276
+ export function blankProperNouns(text) {
277
+ let out = text;
278
+ for (const noun of POLISH_PROPER_NOUNS)
279
+ out = out.split(noun).join(' ');
280
+ return out;
281
+ }
282
+ /** Whether the literal holds a Polish function word. */
283
+ export function hasPolishStopword(text) {
284
+ return proseTokens(text.toLowerCase()).some((token) => STOPWORDS.has(token));
285
+ }
286
+ /**
287
+ * The non-English language this literal is in, or `null`.
288
+ *
289
+ * `languages` gates the answer: a detector for a language the platform does not
290
+ * ship would report a finding nobody can act on.
291
+ */
292
+ export function detectLanguage(text, languages) {
293
+ const shipped = new Set(languages.map(primarySubtag));
294
+ const cleaned = blankProperNouns(text);
295
+ if (shipped.has('pl') && (POLISH_DIACRITIC_RE.test(cleaned) || hasPolishStopword(cleaned))) {
296
+ return 'pl';
297
+ }
298
+ return null;
299
+ }
300
+ /** `pl-PL` → `pl`, and `PL` → `pl`. Case and region folded, nothing else. */
301
+ export function primarySubtag(code) {
302
+ const cut = code.indexOf('-');
303
+ return (cut === -1 ? code : code.slice(0, cut)).toLowerCase();
304
+ }
305
+ /** Whether a property key or export-name token names a shipped language. */
306
+ function namesLanguage(token, languages) {
307
+ const shipped = new Set(languages.map(primarySubtag));
308
+ return shipped.has(primarySubtag(token));
309
+ }
310
+ // --- the structure analysis ------------------------------------------------
311
+ /**
312
+ * What an interpolation collapses to.
313
+ *
314
+ * Written as an escape and not as the character, because a raw control byte in
315
+ * a source file is invisible in every diff that would reveal it — the shape
316
+ * `check:nul-bytes` exists for. An ellipsis rather than a space: it breaks a
317
+ * token, so `` `Kwota${x}netto` `` reads as two words rather than one, and it
318
+ * prints legibly in a finding and in a ledger entry's own label.
319
+ */
320
+ const INTERPOLATION_PLACEHOLDER = '\u2026';
321
+ /** The text of a literal node, with an interpolation collapsed to a token break. */
322
+ function literalTextOf(node) {
323
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
324
+ return node.text;
325
+ if (ts.isTemplateExpression(node)) {
326
+ return (node.head.text +
327
+ node.templateSpans
328
+ .map((span) => `${INTERPOLATION_PLACEHOLDER}${span.literal.text}`)
329
+ .join(''));
330
+ }
331
+ return null;
332
+ }
333
+ /**
334
+ * Whether the literal is in a position where it is not prose at all: a module
335
+ * specifier, a property **key**, a type, an enum member name.
336
+ *
337
+ * A key is excluded rather than judged because `{ 'Faktura korygująca': … }`
338
+ * would be a data shape, not a sentence — and because the language-keyed
339
+ * exemption reads keys itself.
340
+ */
341
+ function isNonProsePosition(node) {
342
+ const parent = node.parent;
343
+ if (parent === undefined)
344
+ return true;
345
+ if (ts.isImportDeclaration(parent) || ts.isExportDeclaration(parent))
346
+ return true;
347
+ if (ts.isImportTypeNode(parent) || ts.isModuleDeclaration(parent))
348
+ return true;
349
+ if (ts.isLiteralTypeNode(parent))
350
+ return true;
351
+ if (ts.isPropertyAssignment(parent) && parent.name === node)
352
+ return true;
353
+ if (ts.isPropertySignature(parent) && parent.name === node)
354
+ return true;
355
+ if (ts.isEnumMember(parent) && parent.name === node)
356
+ return true;
357
+ if (ts.isComputedPropertyName(parent))
358
+ return true;
359
+ if (ts.isExternalModuleReference(parent))
360
+ return true;
361
+ return false;
362
+ }
363
+ /**
364
+ * Whether the literal sits in the **default** position of an operator-editable
365
+ * prop read.
366
+ *
367
+ * Two shapes, both read off the call rather than off a callee's name — a name
368
+ * rule would answer for `str(` and stop answering for whatever the next module
369
+ * calls its accessor:
370
+ *
371
+ * * the **last** argument of a call taking three or more, at least one of the
372
+ * earlier ones being a string literal (the prop key) —
373
+ * `str(props, 'labelSaleDate', 'Data sprzedaży')`;
374
+ * * the right operand of a `??` or `||` whose left operand is a property or
375
+ * element access — `props['labelSaleDate'] ?? 'Data sprzedaży'`.
376
+ *
377
+ * A ternary default (`typeof props['text'] === 'string' ? … : 'Dziękujemy…'`) is
378
+ * outside it and lands in `non-english-default` instead: recognising it needs
379
+ * the condition and the branch to be tied to one property, and the finding it
380
+ * would move is reported either way.
381
+ */
382
+ function isPropDefaultPosition(node) {
383
+ const parent = node.parent;
384
+ if (parent === undefined)
385
+ return false;
386
+ if (ts.isCallExpression(parent)) {
387
+ const args = parent.arguments;
388
+ if (args.length < 3)
389
+ return false;
390
+ if (args[args.length - 1] !== node)
391
+ return false;
392
+ return args
393
+ .slice(0, -1)
394
+ .some((argument) => ts.isStringLiteral(argument) || ts.isNoSubstitutionTemplateLiteral(argument));
395
+ }
396
+ if (ts.isBinaryExpression(parent) && parent.right === node) {
397
+ const operator = parent.operatorToken.kind;
398
+ if (operator !== ts.SyntaxKind.QuestionQuestionToken &&
399
+ operator !== ts.SyntaxKind.BarBarToken) {
400
+ return false;
401
+ }
402
+ return (ts.isPropertyAccessExpression(parent.left) || ts.isElementAccessExpression(parent.left));
403
+ }
404
+ return false;
405
+ }
406
+ /** Every name a file declares as an export, for the `single-language-file` shape. */
407
+ export function declaredExportNames(sf) {
408
+ const names = [];
409
+ const exported = (node) => ts.canHaveModifiers(node) &&
410
+ (ts.getModifiers(node) ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
411
+ for (const statement of sf.statements) {
412
+ if (ts.isVariableStatement(statement) && exported(statement)) {
413
+ for (const declaration of statement.declarationList.declarations) {
414
+ if (ts.isIdentifier(declaration.name))
415
+ names.push(declaration.name.text);
416
+ }
417
+ continue;
418
+ }
419
+ if ((ts.isFunctionDeclaration(statement) ||
420
+ ts.isClassDeclaration(statement) ||
421
+ ts.isInterfaceDeclaration(statement) ||
422
+ ts.isTypeAliasDeclaration(statement) ||
423
+ ts.isEnumDeclaration(statement)) &&
424
+ exported(statement) &&
425
+ statement.name !== undefined) {
426
+ names.push(statement.name.text);
427
+ }
428
+ }
429
+ return names;
430
+ }
431
+ /** `POLISH_TRANSLATION_SEED` → `['POLISH', 'TRANSLATION', 'SEED']`. */
432
+ function nameTokens(name) {
433
+ return name
434
+ .split(/[_\-.]/)
435
+ .flatMap((part) => part.split(/(?<=[a-z0-9])(?=[A-Z])/))
436
+ .filter((part) => part.length > 0);
437
+ }
438
+ /**
439
+ * Whether the file's own declared subject is one language — § 1.2's third shape.
440
+ *
441
+ * The evidence is a language **code** in a declared export name. It deliberately
442
+ * does not fire on `POLISH_TRANSLATION_SEED`, which is a language *name*: that
443
+ * file is exempt through `language-declaring-object` instead, from the
444
+ * `languageCode: 'pl-PL'` its rows carry, which is the structural answer and
445
+ * needs no mapping from a code to an English word.
446
+ */
447
+ export function isSingleLanguageFile(sf, languages) {
448
+ return declaredExportNames(sf).some((name) => nameTokens(name).some((token) => namesLanguage(token, languages)));
449
+ }
450
+ /**
451
+ * Where the literal sits relative to the per-language structures enclosing it.
452
+ *
453
+ * A computed key anywhere on the path is `unclassifiable` and **not** a skip
454
+ * (issue #113): `{ [language]: 'Przesyłka wysłana' }` may or may not be a
455
+ * per-language map, and treating it as exempt is the direction that agrees with
456
+ * the defect.
457
+ */
458
+ export function placementOf(node, languages, singleLanguageFile) {
459
+ let unclassifiable = false;
460
+ for (let current = node.parent; current; current = current.parent) {
461
+ if (ts.isPropertyAssignment(current)) {
462
+ if (ts.isComputedPropertyName(current.name)) {
463
+ unclassifiable = true;
464
+ }
465
+ else if ((ts.isIdentifier(current.name) ||
466
+ ts.isStringLiteral(current.name) ||
467
+ ts.isNoSubstitutionTemplateLiteral(current.name)) &&
468
+ namesLanguage(current.name.text, languages)) {
469
+ return 'language-keyed-property';
470
+ }
471
+ }
472
+ if (ts.isObjectLiteralExpression(current) && declaresItsLanguage(current, languages)) {
473
+ return 'language-declaring-object';
474
+ }
475
+ }
476
+ if (singleLanguageFile)
477
+ return 'single-language-file';
478
+ return unclassifiable ? 'unclassifiable' : 'outside';
479
+ }
480
+ /** Whether the object literal names the language it is written in. */
481
+ function declaresItsLanguage(object, languages) {
482
+ return object.properties.some((property) => {
483
+ if (!ts.isPropertyAssignment(property))
484
+ return false;
485
+ const name = ts.isIdentifier(property.name)
486
+ ? property.name.text
487
+ : ts.isStringLiteral(property.name)
488
+ ? property.name.text
489
+ : null;
490
+ if (name === null || !DECLARING_FIELDS.has(name))
491
+ return false;
492
+ const value = property.initializer;
493
+ return ((ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value)) &&
494
+ namesLanguage(value.text, languages));
495
+ });
496
+ }
497
+ /** Every non-English prose literal in one file, and how many it classified. */
498
+ export function analyzeSource(file, source, languages) {
499
+ const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
500
+ const singleLanguageFile = isSingleLanguageFile(sf, languages);
501
+ const sites = [];
502
+ let classified = 0;
503
+ const visit = (node) => {
504
+ const text = literalTextOf(node);
505
+ if (text !== null && !isNonProsePosition(node)) {
506
+ classified += 1;
507
+ const propDefault = isPropDefaultPosition(node);
508
+ if (isProse(text, propDefault)) {
509
+ const language = detectLanguage(text, languages);
510
+ if (language !== null) {
511
+ const placement = placementOf(node, languages, singleLanguageFile);
512
+ if (placement === 'outside' || placement === 'unclassifiable') {
513
+ sites.push({
514
+ file,
515
+ line: sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1,
516
+ text,
517
+ language,
518
+ placement,
519
+ kind: placement === 'unclassifiable'
520
+ ? 'unclassifiable-literal'
521
+ : propDefault
522
+ ? 'polish-default-behind-a-prop'
523
+ : 'non-english-default',
524
+ });
525
+ }
526
+ }
527
+ }
528
+ }
529
+ ts.forEachChild(node, visit);
530
+ };
531
+ visit(sf);
532
+ return { sites, classified };
533
+ }
534
+ /** The digest half of a ledger key — never a line (§ 2.1). */
535
+ export function literalDigest(text) {
536
+ return createHash('sha256').update(text, 'utf8').digest('hex').slice(0, 12);
537
+ }
538
+ /** `<file>:<digest>`, so an insertion above the site does not red it. */
539
+ export function ledgerKey(site) {
540
+ return `${site.file}:${literalDigest(site.text)}`;
541
+ }
542
+ /**
543
+ * The findings, over the sources and shipped languages handed in.
544
+ *
545
+ * Pure: a red proof enters here, over **source text**, which is where a real run
546
+ * enters (issue #130). A fixture handing in a classified literal would prove the
547
+ * reporter and leave the parser, the position filter, the detector and the whole
548
+ * structure analysis unproven.
549
+ */
550
+ export function checkDefaultLanguageProse(input, shards = [], moduleOf = () => null) {
551
+ const findings = [];
552
+ let classified = 0;
553
+ for (const [file, source] of input.sources) {
554
+ const analysis = analyzeSource(file, source, input.languages);
555
+ findings.push(...analysis.sites);
556
+ classified += analysis.classified;
557
+ }
558
+ const owners = new Map();
559
+ const reasons = new Map();
560
+ for (const shard of shards) {
561
+ for (const key of Object.keys(shard.entries)) {
562
+ owners.set(key, shard.moduleId);
563
+ reasons.set(key, shard.entries[key] ?? '');
564
+ }
565
+ }
566
+ const seen = new Set();
567
+ const violations = [];
568
+ const ledgered = [];
569
+ const misfiled = [];
570
+ for (const site of findings) {
571
+ const key = ledgerKey(site);
572
+ seen.add(key);
573
+ if (!reasons.has(key)) {
574
+ violations.push(site);
575
+ continue;
576
+ }
577
+ ledgered.push(site);
578
+ const owner = moduleOf(site.file);
579
+ if (owner !== null && owners.get(key) !== owner)
580
+ misfiled.push(key);
581
+ }
582
+ const stale = [...reasons.keys()].filter((key) => !seen.has(key)).sort();
583
+ return { findings, violations, ledgered, stale, misfiled, classified };
584
+ }
585
+ // --- the CLI ---------------------------------------------------------------
586
+ /** Directories a module walk never descends into. */
587
+ /**
588
+ * Directory names this rule's walk prunes wherever they occur under its root.
589
+ *
590
+ * `migrations` is the one that is a *rule* rather than a convenience, and its
591
+ * reason is in this file's header: an applied migration cannot be edited, so a
592
+ * ledger entry over a literal in one would never drain.
593
+ */
594
+ export const PRUNED_DIRECTORIES = [
595
+ 'node_modules',
596
+ 'dist',
597
+ 'build',
598
+ 'migrations',
599
+ ];
600
+ const SKIPPED = new Set(PRUNED_DIRECTORIES);
601
+ /**
602
+ * Whether a file is in this rule's population — the **one** membership
603
+ * decision, asked by the walk and by anything that needs to know what the walk
604
+ * would open.
605
+ *
606
+ * It is one function called from two places on purpose, in the idiom
607
+ * `command-coverage.ts` states: while the prune the walk does for speed and the
608
+ * rule the population states were two expressions of one predicate, one could go
609
+ * missing without the other noticing. The second caller is `endora check`'s
610
+ * package-scope floor, which has to know that a declared `./migrations` layer is
611
+ * **not** expected of this rule — otherwise a package that publishes migrations
612
+ * reports a short walk for a layer the rule excludes by design.
613
+ */
614
+ export function isScannedPath(absolutePath, root) {
615
+ const posix = absolutePath.split('\\').join('/');
616
+ const within = posix.slice(root.split('\\').join('/').length).split('/');
617
+ if (within.slice(0, -1).some((segment) => SKIPPED.has(segment)))
618
+ return false;
619
+ return posix.endsWith('.ts') && !posix.endsWith('.d.ts') && !posix.endsWith('.test.ts');
620
+ }
621
+ /**
622
+ * Every TypeScript source under `dir`, with the rule's own prunes applied.
623
+ *
624
+ * Exported because the population is part of the rule: two hosts computing
625
+ * "which files this check reads" two ways is the shape that lets one of them go
626
+ * half-blind.
627
+ */
628
+ export function collectProseSources(dir, out = []) {
629
+ if (!existsSync(dir))
630
+ return out;
631
+ for (const name of readdirSync(dir)) {
632
+ const full = join(dir, name);
633
+ let directory;
634
+ try {
635
+ directory = statSync(full).isDirectory();
636
+ }
637
+ catch {
638
+ continue;
639
+ }
640
+ if (directory) {
641
+ if (SKIPPED.has(name))
642
+ continue;
643
+ collectProseSources(full, out);
644
+ continue;
645
+ }
646
+ if (name.endsWith('.ts') && !name.endsWith('.test.ts') && !name.endsWith('.d.ts')) {
647
+ out.push(full);
648
+ }
649
+ }
650
+ return out;
651
+ }
652
+ /**
653
+ * Every shard in `directory`, loaded.
654
+ *
655
+ * Rejects rather than returning an empty list when the directory is missing or a
656
+ * shard fails to import: both are "read nothing", and the CLI turns the
657
+ * rejection into exit 2.
658
+ */
659
+ export async function loadLedgerShards(directory) {
660
+ if (!existsSync(directory)) {
661
+ throw new Error('ledger directory missing — refusing to report a vacuous pass');
662
+ }
663
+ const shards = [];
664
+ for (const name of readdirSync(directory).sort()) {
665
+ if (!name.endsWith('.ts'))
666
+ continue;
667
+ const moduleId = name.replace(/\.ts$/, '');
668
+ let loaded;
669
+ try {
670
+ loaded = (await import(pathToFileURL(join(directory, name)).href));
671
+ }
672
+ catch (error) {
673
+ throw new Error(`ledger shard '${moduleId}' failed to load: ${String(error)}`);
674
+ }
675
+ const entries = loaded.entries;
676
+ if (typeof entries !== 'object' || entries === null) {
677
+ throw new Error(`ledger shard '${moduleId}' failed to load: it exports no 'entries' record`);
678
+ }
679
+ if (Object.keys(entries).length === 0) {
680
+ throw new Error(`ledger shard '${moduleId}' declares no entry — delete the file instead`);
681
+ }
682
+ shards.push({ moduleId, entries: entries });
683
+ }
684
+ return shards;
685
+ }
686
+ /**
687
+ * The remedy paragraph, one per finding kind.
688
+ *
689
+ * Exported because the remedy is part of the rule and not part of a host: an
690
+ * author outside this checkout reads the same sentence this repository's run
691
+ * prints, and a second wording would be a second answer to "what do I do".
692
+ */
693
+ export const REMEDIES = {
694
+ 'non-english-default': 'English is the default (AGENTS.md § i18n, owner ruling of 2026-09-01). Author the ' +
695
+ 'sentence in English and put the Polish in the module bundle. Which seam carries it is ' +
696
+ "`specs/094-translation-boundary/contracts/translation-boundary.md` § 2: if the backend " +
697
+ "can resolve the reader's language here, translate and ship the code (case 1); if it " +
698
+ 'cannot, ship a key, its params and an English fallback sentence (case 2); operator ' +
699
+ 'content is seeded per language and belongs in no bundle at all.',
700
+ 'polish-default-behind-a-prop': 'The prop is operator-editable and the **default** behind it is Polish, so an operator ' +
701
+ 'who never edits the template gets a Polish document on an English deployment. Having a ' +
702
+ 'mechanism is not having an English default — that conflation excluded 18 of these once ' +
703
+ 'already (`../research.md` § 7.3). Make the default English and translate through the ' +
704
+ "renderer's own `locale`.",
705
+ 'unclassifiable-literal': 'The property path reaching this literal carries a computed key, so the analysis cannot ' +
706
+ 'say whether it is inside a per-language structure. Reported rather than skipped ' +
707
+ '(issue #113): treating it as exempt is the direction that agrees with the defect. ' +
708
+ 'Write the language key as a literal, or move the literal out of the computed path.',
709
+ };
710
+ //# sourceMappingURL=default-language-prose.js.map