mailwoman 7.1.0 → 7.2.1

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 (408) hide show
  1. package/api-engine.ts +366 -0
  2. package/cli-kit/index.ts +125 -0
  3. package/cli-kit/weights-guard.tsx +221 -0
  4. package/cli.ts +27 -0
  5. package/commands/autocomplete.tsx +156 -0
  6. package/commands/clients/generate.tsx +75 -0
  7. package/commands/corpus/align-shard.tsx +42 -0
  8. package/commands/corpus/audit.tsx +43 -0
  9. package/commands/corpus/build.tsx +129 -0
  10. package/commands/corpus/download.tsx +126 -0
  11. package/commands/corpus/fetch.tsx +150 -0
  12. package/commands/corpus/golden/expand.tsx +66 -0
  13. package/commands/corpus/golden/promote.tsx +58 -0
  14. package/commands/corpus/ingest-csv.tsx +38 -0
  15. package/commands/corpus/list.tsx +36 -0
  16. package/commands/corpus/overlay-manifest.tsx +50 -0
  17. package/commands/corpus/run.tsx +134 -0
  18. package/commands/corpus/shard/index.tsx +137 -0
  19. package/commands/corpus/shard/kryptonite.tsx +48 -0
  20. package/commands/corpus/shard/translit.tsx +54 -0
  21. package/commands/corpus/stats.tsx +42 -0
  22. package/commands/corpus/upload.tsx +134 -0
  23. package/commands/coverage/build.tsx +131 -0
  24. package/commands/dev/download/libpostal-resources.tsx +32 -0
  25. package/commands/dev/download/ssl-address.tsx +41 -0
  26. package/commands/dev/generate/country-reference.tsx +34 -0
  27. package/commands/dev/generate/language-types.tsx +34 -0
  28. package/commands/dev/generate/official-languages.tsx +44 -0
  29. package/commands/dev/generate/trace-fixture.tsx +51 -0
  30. package/commands/dev/jsonl-to-parquet.tsx +46 -0
  31. package/commands/dev/lint/corpus-shard.tsx +53 -0
  32. package/commands/dev/lint/mdx-angles.tsx +53 -0
  33. package/commands/dev/lint/shard-vocab.tsx +57 -0
  34. package/commands/eval/capability-manifest.tsx +43 -0
  35. package/commands/eval/error-analysis.tsx +50 -0
  36. package/commands/eval/es-postcode-centroids.tsx +42 -0
  37. package/commands/eval/fragment-board.tsx +54 -0
  38. package/commands/eval/fragment-dev.tsx +44 -0
  39. package/commands/eval/gate.tsx +59 -0
  40. package/commands/eval/gauntlet-build.tsx +58 -0
  41. package/commands/eval/gauntlet.tsx +53 -0
  42. package/commands/eval/ledger-append.tsx +56 -0
  43. package/commands/eval/mask-regression.tsx +48 -0
  44. package/commands/eval/oa-resolver.tsx +85 -0
  45. package/commands/eval/oracle-k.tsx +62 -0
  46. package/commands/eval/parity.tsx +89 -0
  47. package/commands/eval/preset-compare.tsx +35 -0
  48. package/commands/gazetteer/affix-relabel.tsx +92 -0
  49. package/commands/gazetteer/anchor-lexicon.tsx +184 -0
  50. package/commands/gazetteer/build/admin.tsx +86 -0
  51. package/commands/gazetteer/build/anchor-lookup.tsx +38 -0
  52. package/commands/gazetteer/build/candidate.tsx +115 -0
  53. package/commands/gazetteer/build/coincident-roles.tsx +88 -0
  54. package/commands/gazetteer/build/fts.tsx +82 -0
  55. package/commands/gazetteer/build/index.tsx +82 -0
  56. package/commands/gazetteer/build/nl-pc6.tsx +38 -0
  57. package/commands/gazetteer/build/poi.tsx +104 -0
  58. package/commands/gazetteer/build/postcode-locality.tsx +113 -0
  59. package/commands/gazetteer/build/postcode-shard.tsx +73 -0
  60. package/commands/gazetteer/conventions.tsx +183 -0
  61. package/commands/gazetteer/importance.tsx +242 -0
  62. package/commands/gazetteer/inspect/fst.tsx +87 -0
  63. package/commands/gazetteer/inspect/graph.tsx +129 -0
  64. package/commands/gazetteer/inspect/mermaid.tsx +165 -0
  65. package/commands/gazetteer/inspect/placetype-stats.tsx +180 -0
  66. package/commands/gazetteer/inspect/sync.tsx +128 -0
  67. package/commands/gazetteer/inspect/tree.tsx +128 -0
  68. package/commands/gazetteer/overture-ingest.tsx +298 -0
  69. package/commands/gazetteer/polygons.tsx +295 -0
  70. package/commands/gazetteer/postal-alias.tsx +151 -0
  71. package/commands/gazetteer/postal-city.tsx +161 -0
  72. package/commands/gazetteer/postcode-binary.tsx +191 -0
  73. package/commands/gazetteer/postcode-intl.tsx +398 -0
  74. package/commands/gazetteer/promote.tsx +53 -0
  75. package/commands/gazetteer/publish.tsx +97 -0
  76. package/commands/gazetteer/release.tsx +132 -0
  77. package/commands/gazetteer/verify.tsx +61 -0
  78. package/commands/geocode.tsx +414 -0
  79. package/commands/gnaf/assemble.tsx +88 -0
  80. package/commands/openapi.tsx +44 -0
  81. package/commands/parse.tsx +718 -0
  82. package/commands/placer/build-dataset.tsx +95 -0
  83. package/commands/placer/eval.tsx +97 -0
  84. package/commands/placer/probe-frontier.tsx +48 -0
  85. package/commands/placer/quantize.tsx +45 -0
  86. package/commands/placer/train.tsx +54 -0
  87. package/commands/poi.tsx +277 -0
  88. package/commands/registry/convert/tx-hhsc.tsx +46 -0
  89. package/commands/registry/dedup-ceiling.tsx +51 -0
  90. package/commands/registry/gold-set-sample.tsx +59 -0
  91. package/commands/registry/matcher-scale.tsx +49 -0
  92. package/commands/registry/run.tsx +558 -0
  93. package/commands/registry/scorer-eval.tsx +267 -0
  94. package/commands/registry/train-scorer.tsx +108 -0
  95. package/commands/registry/viz.tsx +101 -0
  96. package/commands/release/hf.tsx +62 -0
  97. package/commands/reverse.tsx +169 -0
  98. package/commands/serve.tsx +240 -0
  99. package/commands/situs/address-points.tsx +341 -0
  100. package/commands/situs/attribution-manifest.tsx +135 -0
  101. package/commands/situs/build.tsx +321 -0
  102. package/commands/situs/interpolation-shard.tsx +324 -0
  103. package/commands/situs/interpolation.tsx +620 -0
  104. package/commands/tiger/fetch.tsx +104 -0
  105. package/commands/tiger/race-dots-map.tsx +107 -0
  106. package/commands/tiger/race-dots.tsx +65 -0
  107. package/commands/tiger/redistricting.tsx +99 -0
  108. package/commands/tiles/publish.tsx +91 -0
  109. package/commands/wof/graph.tsx +22 -0
  110. package/commands/wof/mermaid.tsx +22 -0
  111. package/commands/wof/prepare.tsx +22 -0
  112. package/commands/wof/sync.tsx +22 -0
  113. package/commands/wof/tree.tsx +22 -0
  114. package/coverage-core.ts +464 -0
  115. package/data-release.ts +63 -0
  116. package/default-placer.ts +59 -0
  117. package/default-street-evidence.ts +46 -0
  118. package/dev-tools/capture-v1-parse.run.ts +48 -0
  119. package/dev-tools/convert-parity-fixtures.run.ts +154 -0
  120. package/dev-tools/extract-parity-corpus.run.ts +42 -0
  121. package/dev-tools/failure-census.run.ts +0 -0
  122. package/dev-tools/failure-report.run.ts +0 -0
  123. package/dev-tools/generate-trace-fixture.ts +60 -0
  124. package/dev-tools/lint-mdx-angles.ts +114 -0
  125. package/dev-tools/parity-extract.ts +107 -0
  126. package/dev-tools/router-kind-probe.run.ts +63 -0
  127. package/dev-tools/us-recall-flip-census.run.ts +102 -0
  128. package/eval-harness/baseline-assert.ts +319 -0
  129. package/eval-harness/capability-manifest.ts +348 -0
  130. package/eval-harness/digit-board.ts +189 -0
  131. package/eval-harness/error-analysis.ts +306 -0
  132. package/eval-harness/es-postcode-centroids.ts +129 -0
  133. package/eval-harness/fragment-board.ts +183 -0
  134. package/eval-harness/fragment-dev.ts +113 -0
  135. package/eval-harness/gauntlet/build-fdic-holdout.ts +106 -0
  136. package/eval-harness/gauntlet/build-regression-db.ts +78 -0
  137. package/eval-harness/gauntlet/cases/regression.ts +667 -0
  138. package/eval-harness/gauntlet/harness.ts +163 -0
  139. package/eval-harness/gauntlet/holdout.ts +239 -0
  140. package/eval-harness/gauntlet/metamorphic.ts +496 -0
  141. package/eval-harness/gauntlet/regression.ts +164 -0
  142. package/eval-harness/gauntlet/run.ts +129 -0
  143. package/eval-harness/gauntlet/schema.ts +115 -0
  144. package/eval-harness/ledger-append.ts +208 -0
  145. package/eval-harness/mask-regression.ts +334 -0
  146. package/eval-harness/oa-resolver-eval.ts +1255 -0
  147. package/eval-harness/oracle-k.ts +415 -0
  148. package/eval-harness/parity-corpus.ts +278 -0
  149. package/eval-harness/preset-compare.ts +62 -0
  150. package/eval-harness/promotion-gate-verdict.ts +261 -0
  151. package/eval-harness/promotion-gate.ts +531 -0
  152. package/eval-harness/v0-tree-adapter.ts +113 -0
  153. package/gazetteer-pipeline/admin/enrich.ts +99 -0
  154. package/gazetteer-pipeline/admin/fold-geonames.ts +64 -0
  155. package/gazetteer-pipeline/admin/fold-overture.ts +179 -0
  156. package/gazetteer-pipeline/admin/freeze.ts +112 -0
  157. package/gazetteer-pipeline/admin/index.ts +230 -0
  158. package/gazetteer-pipeline/admin/ingest-wof.ts +266 -0
  159. package/gazetteer-pipeline/anchor-lookup.ts +371 -0
  160. package/gazetteer-pipeline/defaults.ts +305 -0
  161. package/gazetteer-pipeline/fts.ts +35 -0
  162. package/gazetteer-pipeline/index.ts +321 -0
  163. package/gazetteer-pipeline/poi/build-poi.ts +538 -0
  164. package/gazetteer-pipeline/postcode/centroid-fills.ts +244 -0
  165. package/gazetteer-pipeline/postcode/index.ts +164 -0
  166. package/gazetteer-pipeline/postcode/nl-pc6.ts +120 -0
  167. package/gazetteer-pipeline/postcode/zcta-centroids.ts +203 -0
  168. package/gazetteer-pipeline/postcode-locality/base.ts +463 -0
  169. package/gazetteer-pipeline/postcode-locality/jp.ts +362 -0
  170. package/gazetteer-pipeline/postcode-locality/kr.ts +408 -0
  171. package/gazetteer-pipeline/postcode-locality/tw.ts +634 -0
  172. package/gazetteer-pipeline/verify-baseline.ts +222 -0
  173. package/gazetteer-pipeline/verify.ts +233 -0
  174. package/geocode-core.ts +877 -0
  175. package/geocode-stream.ts +85 -0
  176. package/geocode-worker.ts +54 -0
  177. package/index.ts +9 -0
  178. package/interp-calibration.ts +65 -0
  179. package/kbest-street-rerank.ts +248 -0
  180. package/out/api-engine.d.ts +44 -0
  181. package/out/cli-kit/index.d.ts +71 -0
  182. package/out/cli-kit/weights-guard.d.ts +64 -0
  183. package/out/cli.d.ts +8 -0
  184. package/out/commands/autocomplete.d.ts +45 -0
  185. package/out/commands/clients/generate.d.ts +26 -0
  186. package/out/commands/corpus/align-shard.d.ts +19 -0
  187. package/out/commands/corpus/audit.d.ts +19 -0
  188. package/out/commands/corpus/build.d.ts +28 -0
  189. package/out/commands/corpus/download.d.ts +24 -0
  190. package/out/commands/corpus/fetch.d.ts +45 -0
  191. package/out/commands/corpus/golden/expand.d.ts +29 -0
  192. package/out/commands/corpus/golden/promote.d.ts +24 -0
  193. package/out/commands/corpus/ingest-csv.d.ts +25 -0
  194. package/out/commands/corpus/list.d.ts +18 -0
  195. package/out/commands/corpus/overlay-manifest.d.ts +23 -0
  196. package/out/commands/corpus/run.d.ts +26 -0
  197. package/out/commands/corpus/shard/index.d.ts +43 -0
  198. package/out/commands/corpus/shard/kryptonite.d.ts +22 -0
  199. package/out/commands/corpus/shard/translit.d.ts +24 -0
  200. package/out/commands/corpus/stats.d.ts +19 -0
  201. package/out/commands/corpus/upload.d.ts +24 -0
  202. package/out/commands/coverage/build.d.ts +41 -0
  203. package/out/commands/dev/download/libpostal-resources.d.ts +17 -0
  204. package/out/commands/dev/download/ssl-address.d.ts +18 -0
  205. package/out/commands/dev/generate/country-reference.d.ts +12 -0
  206. package/out/commands/dev/generate/language-types.d.ts +12 -0
  207. package/out/commands/dev/generate/official-languages.d.ts +19 -0
  208. package/out/commands/dev/generate/trace-fixture.d.ts +18 -0
  209. package/out/commands/dev/jsonl-to-parquet.d.ts +21 -0
  210. package/out/commands/dev/lint/corpus-shard.d.ts +23 -0
  211. package/out/commands/dev/lint/mdx-angles.d.ts +17 -0
  212. package/out/commands/dev/lint/shard-vocab.d.ts +25 -0
  213. package/out/commands/eval/capability-manifest.d.ts +25 -0
  214. package/out/commands/eval/error-analysis.d.ts +26 -0
  215. package/out/commands/eval/es-postcode-centroids.d.ts +24 -0
  216. package/out/commands/eval/fragment-board.d.ts +25 -0
  217. package/out/commands/eval/fragment-dev.d.ts +21 -0
  218. package/out/commands/eval/gate.d.ts +30 -0
  219. package/out/commands/eval/gauntlet-build.d.ts +24 -0
  220. package/out/commands/eval/gauntlet.d.ts +31 -0
  221. package/out/commands/eval/ledger-append.d.ts +29 -0
  222. package/out/commands/eval/mask-regression.d.ts +27 -0
  223. package/out/commands/eval/oa-resolver.d.ts +54 -0
  224. package/out/commands/eval/oracle-k.d.ts +25 -0
  225. package/out/commands/eval/parity.d.ts +29 -0
  226. package/out/commands/eval/preset-compare.d.ts +20 -0
  227. package/out/commands/gazetteer/affix-relabel.d.ts +22 -0
  228. package/out/commands/gazetteer/anchor-lexicon.d.ts +35 -0
  229. package/out/commands/gazetteer/build/admin.d.ts +25 -0
  230. package/out/commands/gazetteer/build/anchor-lookup.d.ts +19 -0
  231. package/out/commands/gazetteer/build/candidate.d.ts +23 -0
  232. package/out/commands/gazetteer/build/coincident-roles.d.ts +21 -0
  233. package/out/commands/gazetteer/build/fts.d.ts +20 -0
  234. package/out/commands/gazetteer/build/index.d.ts +21 -0
  235. package/out/commands/gazetteer/build/nl-pc6.d.ts +19 -0
  236. package/out/commands/gazetteer/build/poi.d.ts +24 -0
  237. package/out/commands/gazetteer/build/poi.d.ts.map +1 -0
  238. package/out/commands/gazetteer/build/poi.js +83 -0
  239. package/out/commands/gazetteer/build/poi.js.map +1 -0
  240. package/out/commands/gazetteer/build/postcode-locality.d.ts +44 -0
  241. package/out/commands/gazetteer/build/postcode-shard.d.ts +25 -0
  242. package/out/commands/gazetteer/conventions.d.ts +33 -0
  243. package/out/commands/gazetteer/importance.d.ts +26 -0
  244. package/out/commands/gazetteer/importance.d.ts.map +1 -1
  245. package/out/commands/gazetteer/importance.js +2 -1
  246. package/out/commands/gazetteer/importance.js.map +1 -1
  247. package/out/commands/gazetteer/inspect/fst.d.ts +23 -0
  248. package/out/commands/gazetteer/inspect/graph.d.ts +25 -0
  249. package/out/commands/gazetteer/inspect/mermaid.d.ts +24 -0
  250. package/out/commands/gazetteer/inspect/placetype-stats.d.ts +25 -0
  251. package/out/commands/gazetteer/inspect/placetype-stats.d.ts.map +1 -1
  252. package/out/commands/gazetteer/inspect/placetype-stats.js.map +1 -1
  253. package/out/commands/gazetteer/inspect/sync.d.ts +23 -0
  254. package/out/commands/gazetteer/inspect/tree.d.ts +24 -0
  255. package/out/commands/gazetteer/overture-ingest.d.ts +43 -0
  256. package/out/commands/gazetteer/polygons.d.ts +42 -0
  257. package/out/commands/gazetteer/postal-alias.d.ts +37 -0
  258. package/out/commands/gazetteer/postal-city.d.ts +32 -0
  259. package/out/commands/gazetteer/postcode-binary.d.ts +34 -0
  260. package/out/commands/gazetteer/postcode-intl.d.ts +51 -0
  261. package/out/commands/gazetteer/promote.d.ts +18 -0
  262. package/out/commands/gazetteer/publish.d.ts +27 -0
  263. package/out/commands/gazetteer/release.d.ts +27 -0
  264. package/out/commands/gazetteer/verify.d.ts +21 -0
  265. package/out/commands/geocode.d.ts +52 -0
  266. package/out/commands/gnaf/assemble.d.ts +29 -0
  267. package/out/commands/openapi.d.ts +27 -0
  268. package/out/commands/parse.d.ts +57 -0
  269. package/out/commands/parse.d.ts.map +1 -1
  270. package/out/commands/parse.js +7 -5
  271. package/out/commands/parse.js.map +1 -1
  272. package/out/commands/placer/build-dataset.d.ts +31 -0
  273. package/out/commands/placer/eval.d.ts +33 -0
  274. package/out/commands/placer/probe-frontier.d.ts +21 -0
  275. package/out/commands/placer/quantize.d.ts +19 -0
  276. package/out/commands/placer/train.d.ts +23 -0
  277. package/out/commands/poi.d.ts +41 -0
  278. package/out/commands/poi.d.ts.map +1 -0
  279. package/out/commands/poi.js +219 -0
  280. package/out/commands/poi.js.map +1 -0
  281. package/out/commands/registry/convert/tx-hhsc.d.ts +20 -0
  282. package/out/commands/registry/dedup-ceiling.d.ts +22 -0
  283. package/out/commands/registry/gold-set-sample.d.ts +23 -0
  284. package/out/commands/registry/matcher-scale.d.ts +21 -0
  285. package/out/commands/registry/run.d.ts +97 -0
  286. package/out/commands/registry/scorer-eval.d.ts +60 -0
  287. package/out/commands/registry/train-scorer.d.ts +34 -0
  288. package/out/commands/registry/viz.d.ts +35 -0
  289. package/out/commands/release/hf.d.ts +34 -0
  290. package/out/commands/reverse.d.ts +32 -0
  291. package/out/commands/serve.d.ts +19 -0
  292. package/out/commands/situs/address-points.d.ts +44 -0
  293. package/out/commands/situs/attribution-manifest.d.ts +27 -0
  294. package/out/commands/situs/build.d.ts +49 -0
  295. package/out/commands/situs/interpolation-shard.d.ts +37 -0
  296. package/out/commands/situs/interpolation.d.ts +43 -0
  297. package/out/commands/tiger/fetch.d.ts +29 -0
  298. package/out/commands/tiger/race-dots-map.d.ts +27 -0
  299. package/out/commands/tiger/race-dots.d.ts +21 -0
  300. package/out/commands/tiger/redistricting.d.ts +24 -0
  301. package/out/commands/tiles/publish.d.ts +38 -0
  302. package/out/commands/wof/graph.d.ts +10 -0
  303. package/out/commands/wof/mermaid.d.ts +10 -0
  304. package/out/commands/wof/prepare.d.ts +10 -0
  305. package/out/commands/wof/sync.d.ts +10 -0
  306. package/out/commands/wof/tree.d.ts +10 -0
  307. package/out/coverage-core.d.ts +99 -0
  308. package/out/data-release.d.ts +26 -0
  309. package/out/default-placer.d.ts +31 -0
  310. package/out/default-street-evidence.d.ts +24 -0
  311. package/out/dev-tools/capture-v1-parse.run.d.ts +13 -0
  312. package/out/dev-tools/convert-parity-fixtures.run.d.ts +30 -0
  313. package/out/dev-tools/extract-parity-corpus.run.d.ts +11 -0
  314. package/out/dev-tools/failure-census.run.d.ts +2 -0
  315. package/out/dev-tools/failure-report.run.d.ts +23 -0
  316. package/out/dev-tools/generate-trace-fixture.d.ts +32 -0
  317. package/out/dev-tools/lint-mdx-angles.d.ts +37 -0
  318. package/out/dev-tools/parity-extract.d.ts +23 -0
  319. package/out/dev-tools/router-kind-probe.run.d.ts +2 -0
  320. package/out/dev-tools/us-recall-flip-census.run.d.ts +12 -0
  321. package/out/eval-harness/baseline-assert.d.ts +139 -0
  322. package/out/eval-harness/capability-manifest.d.ts +57 -0
  323. package/out/eval-harness/digit-board.d.ts +64 -0
  324. package/out/eval-harness/error-analysis.d.ts +57 -0
  325. package/out/eval-harness/es-postcode-centroids.d.ts +35 -0
  326. package/out/eval-harness/fragment-board.d.ts +63 -0
  327. package/out/eval-harness/fragment-dev.d.ts +30 -0
  328. package/out/eval-harness/gauntlet/build-fdic-holdout.d.ts +19 -0
  329. package/out/eval-harness/gauntlet/build-regression-db.d.ts +13 -0
  330. package/out/eval-harness/gauntlet/cases/regression.d.ts +33 -0
  331. package/out/eval-harness/gauntlet/harness.d.ts +44 -0
  332. package/out/eval-harness/gauntlet/holdout.d.ts +46 -0
  333. package/out/eval-harness/gauntlet/metamorphic.d.ts +32 -0
  334. package/out/eval-harness/gauntlet/regression.d.ts +39 -0
  335. package/out/eval-harness/gauntlet/run.d.ts +57 -0
  336. package/out/eval-harness/gauntlet/schema.d.ts +69 -0
  337. package/out/eval-harness/ledger-append.d.ts +59 -0
  338. package/out/eval-harness/mask-regression.d.ts +85 -0
  339. package/out/eval-harness/oa-resolver-eval.d.ts +132 -0
  340. package/out/eval-harness/oracle-k.d.ts +76 -0
  341. package/out/eval-harness/parity-corpus.d.ts +71 -0
  342. package/out/eval-harness/preset-compare.d.ts +19 -0
  343. package/out/eval-harness/promotion-gate-verdict.d.ts +65 -0
  344. package/out/eval-harness/promotion-gate.d.ts +91 -0
  345. package/out/eval-harness/v0-tree-adapter.d.ts +44 -0
  346. package/out/gazetteer-pipeline/admin/enrich.d.ts +30 -0
  347. package/out/gazetteer-pipeline/admin/fold-geonames.d.ts +37 -0
  348. package/out/gazetteer-pipeline/admin/fold-overture.d.ts +42 -0
  349. package/out/gazetteer-pipeline/admin/freeze.d.ts +29 -0
  350. package/out/gazetteer-pipeline/admin/index.d.ts +52 -0
  351. package/out/gazetteer-pipeline/admin/ingest-wof.d.ts +38 -0
  352. package/out/gazetteer-pipeline/anchor-lookup.d.ts +48 -0
  353. package/out/gazetteer-pipeline/defaults.d.ts +35 -0
  354. package/out/gazetteer-pipeline/fts.d.ts +23 -0
  355. package/out/gazetteer-pipeline/index.d.ts +139 -0
  356. package/out/gazetteer-pipeline/poi/build-poi.d.ts +159 -0
  357. package/out/gazetteer-pipeline/poi/build-poi.d.ts.map +1 -0
  358. package/out/gazetteer-pipeline/poi/build-poi.js +352 -0
  359. package/out/gazetteer-pipeline/poi/build-poi.js.map +1 -0
  360. package/out/gazetteer-pipeline/postcode/centroid-fills.d.ts +42 -0
  361. package/out/gazetteer-pipeline/postcode/index.d.ts +41 -0
  362. package/out/gazetteer-pipeline/postcode/nl-pc6.d.ts +42 -0
  363. package/out/gazetteer-pipeline/postcode/zcta-centroids.d.ts +73 -0
  364. package/out/gazetteer-pipeline/postcode-locality/base.d.ts +53 -0
  365. package/out/gazetteer-pipeline/postcode-locality/jp.d.ts +46 -0
  366. package/out/gazetteer-pipeline/postcode-locality/kr.d.ts +46 -0
  367. package/out/gazetteer-pipeline/postcode-locality/tw.d.ts +100 -0
  368. package/out/gazetteer-pipeline/verify-baseline.d.ts +17 -0
  369. package/out/gazetteer-pipeline/verify.d.ts +56 -0
  370. package/out/geocode-core.d.ts +282 -0
  371. package/out/geocode-stream.d.ts +63 -0
  372. package/out/geocode-worker.d.ts +12 -0
  373. package/out/index.d.ts +9 -0
  374. package/out/interp-calibration.d.ts +40 -0
  375. package/out/kbest-street-rerank.d.ts +73 -0
  376. package/out/poi-executor.d.ts +42 -0
  377. package/out/poi-executor.d.ts.map +1 -0
  378. package/out/poi-executor.js +95 -0
  379. package/out/poi-executor.js.map +1 -0
  380. package/out/poi-intent.d.ts +31 -0
  381. package/out/poi-intent.d.ts.map +1 -0
  382. package/out/poi-intent.js +39 -0
  383. package/out/poi-intent.js.map +1 -0
  384. package/out/poi-overpass.d.ts +11 -0
  385. package/out/poi-overpass.d.ts.map +1 -0
  386. package/out/poi-overpass.js +11 -0
  387. package/out/poi-overpass.js.map +1 -0
  388. package/out/region-recognition.d.ts +38 -0
  389. package/out/release-tools/publish-hf.d.ts +53 -0
  390. package/out/resolver-backend.d.ts +52 -0
  391. package/out/runtime-pipeline.d.ts +124 -0
  392. package/out/runtime-pipeline.d.ts.map +1 -1
  393. package/out/runtime-pipeline.js +55 -2
  394. package/out/runtime-pipeline.js.map +1 -1
  395. package/out/test-fixtures/fake-geocode-worker.d.ts +11 -0
  396. package/out/test-kit/index.d.ts +24 -0
  397. package/out/tools/generate-clients.d.ts +80 -0
  398. package/package.json +42 -25
  399. package/poi-executor.ts +133 -0
  400. package/poi-intent.ts +70 -0
  401. package/poi-overpass.ts +11 -0
  402. package/region-recognition.ts +234 -0
  403. package/release-tools/publish-hf.ts +328 -0
  404. package/resolver-backend.ts +95 -0
  405. package/runtime-pipeline.ts +331 -0
  406. package/test-kit/index.ts +78 -0
  407. package/tools/generate-clients.ts +909 -0
  408. package/types/node.d.ts +45 -0
package/api-engine.ts ADDED
@@ -0,0 +1,366 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The wired {@link MailwomanAPIEngine} for `mailwoman serve` (Phase 4b, the Hono cutover). Ports the
7
+ * former express `server/`'s handler bodies — `GeocodeRouter.getDeps` + its retired `/api/geocode`,
8
+ * `/api/batch`, `/api/resolve-tree`, `/api/reload` handlers, `AddressRouter`'s retired `/parse`
9
+ * handler, and `HealthRouter`'s `/health` data block — onto the engine-agnostic `@mailwoman/api`
10
+ * contract's live routes (`/v1/parse`, `/v1/geocode`, `/v1/batch`, `/v1/resolve`, `/v1/reload`).
11
+ * `mailwoman/server/` is deleted (Task 2) — this file is its sole successor, a fresh port rather
12
+ * than a thin wrapper.
13
+ *
14
+ * `createServeEngine` builds the shared stack ONCE, at boot, instead of express's lazy
15
+ * first-request memoized promise — the CLI's `serve` command awaits it before listening, so a
16
+ * misconfigured deployment fails FRIENDLY at boot (the #1009 pattern the drop-ins already use)
17
+ * instead of a runtime 503 on the first request. `parse` speaks native neural output (`ParseOutcome`
18
+ * = ordered components + the decoded `AddressTree`, the same language `/v1/resolve` speaks) — it
19
+ * needs only the model weights, loaded ONCE here and reused by the geocode stack below, so it is
20
+ * built independently of the WOF-data gate: a WOF-less boot still answers `/v1/parse`, while
21
+ * `geocode`/`batch`/`resolveTree`/`reload` are simply absent (`@mailwoman/api`'s routes answer 503
22
+ * for those on their own). When the weights themselves are unresolvable (`@mailwoman/neural`
23
+ * missing, or no weights package installed), `parse` is ALSO absent and the routes answer 501 — no
24
+ * rules fallback (the legacy-excision's point). `health` always answers, even when everything else
25
+ * is broken.
26
+ */
27
+
28
+ import { existsSync, readdirSync, readFileSync } from "node:fs"
29
+ import { createRequire } from "node:module"
30
+ import { join } from "node:path"
31
+
32
+ import type { BatchRow, GeocodeOutcome, HealthData, MailwomanAPIEngine, ResolveTreeOutcome } from "@mailwoman/api"
33
+ import { recordTimed } from "@mailwoman/api-kit"
34
+ import { decodeAsTuples, decodeAsXML } from "@mailwoman/core"
35
+ import type { AddressTree } from "@mailwoman/core/decoder"
36
+ import { $public } from "@mailwoman/core/env"
37
+ import { createWOFResolver, type Resolver, type ResolveOpts } from "@mailwoman/resolver"
38
+
39
+ import { readReleaseManifest } from "./data-release.ts"
40
+ import {
41
+ geocodeAddress,
42
+ type GeocodeClassifier,
43
+ type GeocodeResult,
44
+ regionSlugFromTree,
45
+ ShardProvider,
46
+ } from "./geocode-core.ts"
47
+ import { INTERP_RADIUS_CALIBRATION, interpCalibrationForRegion } from "./interp-calibration.ts"
48
+ import { createResolverBackend, mailwomanDataRoot, resolveCandidateDBPath, wofShardPaths } from "./resolver-backend.ts"
49
+
50
+ /** Default per-state shard root + interp calibration — mirrors the express server's defaults (`GeocodeRouter.ts`). */
51
+ const DATA_ROOT = mailwomanDataRoot()
52
+
53
+ /** The classifier/resolver/shard bundle `geocode`/`batch`/`resolveTree`/`reload` close over. */
54
+ interface GeocodeDepsBundle {
55
+ classifier: GeocodeClassifier
56
+ resolver: Resolver
57
+ shards: ShardProvider
58
+ defaultCountry?: string
59
+ }
60
+
61
+ /** Same WOF-path resolution as the express `GeocodeRouter`/`HealthRouter` (env override, else the conventional shards). */
62
+ function wofPaths(): string[] {
63
+ const env = $public.MAILWOMAN_WOF_DB
64
+
65
+ if (env)
66
+ return env
67
+ .split(",")
68
+ .map((p) => p.trim())
69
+ .filter(Boolean)
70
+
71
+ return wofShardPaths().filter((p) => existsSync(p))
72
+ }
73
+
74
+ /**
75
+ * #1009-style boot preflight message. Same shape as the drop-ins' (`photon/cli.ts`, `nominatim/cli.ts`) — a stranger's
76
+ * first `mailwoman serve` must say exactly what data is missing and the one command that fixes it — adapted to this
77
+ * package's own override (`MAILWOMAN_WOF_DB`, a comma-separated FTS shard list) alongside the shared
78
+ * candidate-gazetteer fast path (`MAILWOMAN_CANDIDATE_DB`).
79
+ */
80
+ function buildPreflightMessage(): string {
81
+ const conventionCandidate = join(DATA_ROOT, "wof", "candidate.db")
82
+
83
+ return [
84
+ "✗ no gazetteer data found — `mailwoman serve` needs a WOF SQLite distribution to geocode/resolve.",
85
+ "",
86
+ " Fastest path (worldwide resolution, ~1.4 GB, byte-range friendly):",
87
+ ` mkdir -p ${join(DATA_ROOT, "wof")}`,
88
+ ` curl -fSL https://public.sister.software/mailwoman/gazetteer/2026-07-07a/candidate.db \\`,
89
+ ` -o ${conventionCandidate}`,
90
+ "",
91
+ " Then re-run `serve` (the file is auto-detected at that path), or point at your own:",
92
+ " $MAILWOMAN_WOF_DB=<path1,path2,...> (admin WOF SQLite shard(s) — the FTS backend)",
93
+ " $MAILWOMAN_CANDIDATE_DB=<path> (candidate-table gazetteer — population-first, demo-parity)",
94
+ "",
95
+ " Docs: https://mailwoman.sister.software/docs",
96
+ ].join("\n")
97
+ }
98
+
99
+ /**
100
+ * Best-effort model-card read: env override → installed weights package → dev-tree fallback. Ported from
101
+ * `HealthRouter`.
102
+ */
103
+ function readModelCard(): Record<string, unknown> | null {
104
+ const candidates: string[] = []
105
+
106
+ if ($public.MAILWOMAN_MODEL_CARD) {
107
+ candidates.push($public.MAILWOMAN_MODEL_CARD)
108
+ }
109
+
110
+ try {
111
+ candidates.push(createRequire(import.meta.url).resolve("@mailwoman/neural-weights-en-us/model-card.json"))
112
+ } catch {
113
+ /* package not resolvable from here — fall through */
114
+ }
115
+ candidates.push("neural-weights-en-us/model-card.json")
116
+
117
+ for (const p of candidates) {
118
+ try {
119
+ if (existsSync(p)) return JSON.parse(readFileSync(p, "utf8")) as Record<string, unknown>
120
+ } catch {
121
+ /* unreadable / malformed — try the next candidate */
122
+ }
123
+ }
124
+
125
+ return null
126
+ }
127
+
128
+ /**
129
+ * Count canonical per-state shards (`<prefix>-us-<2-letter>.db`) in a data subdir; 0 if absent. Ported from
130
+ * `HealthRouter`.
131
+ */
132
+ function countShards(subdir: string, prefix: string): number {
133
+ try {
134
+ const re = new RegExp(`^${prefix}-us-[a-z]{2}\\.db$`)
135
+
136
+ return readdirSync(`${DATA_ROOT}/${subdir}`).filter((f) => re.test(f)).length
137
+ } catch {
138
+ return 0
139
+ }
140
+ }
141
+
142
+ /**
143
+ * The `/health` data block: model card + data-root inventory. Ported from `HealthRouter`'s `healthHandler`. Always
144
+ * available — reads files best-effort and never throws, regardless of preflight status.
145
+ */
146
+ function buildHealthData(): HealthData {
147
+ const card = readModelCard()
148
+
149
+ return {
150
+ model: card
151
+ ? {
152
+ name: card["name"],
153
+ version: card["version"],
154
+ locale: card["locale"],
155
+ labels: Array.isArray(card["labels"]) ? card["labels"].length : undefined,
156
+ format: card["format"],
157
+ }
158
+ : null,
159
+ data: {
160
+ data_root: DATA_ROOT,
161
+ // Versioned-switchover provenance (#485): the releases.json pin, or null in legacy mode.
162
+ versions: readReleaseManifest(DATA_ROOT),
163
+ // The express HealthRouter existsSync-filtered here where GeocodeRouter's wofPaths() didn't;
164
+ // this diagnostic field keeps the health-side behavior (no phantom env paths in "what's deployed").
165
+ wof_dbs: wofPaths().filter((p) => existsSync(p)),
166
+ situs_states: countShards("address-points", "address-points"),
167
+ interpolation_states: countShards("interpolation", "interpolation"),
168
+ },
169
+ }
170
+ }
171
+
172
+ /** One geocode call over the shared deps. Ported from `GeocodeRouter`'s `oneGeocode`. */
173
+ function oneGeocode(deps: GeocodeDepsBundle, address: string): Promise<GeocodeResult> {
174
+ return geocodeAddress(address, {
175
+ classifier: deps.classifier,
176
+ resolver: deps.resolver,
177
+ shards: deps.shards.for,
178
+ defaultCountry: deps.defaultCountry,
179
+ interpCalibration: INTERP_RADIUS_CALIBRATION,
180
+ })
181
+ }
182
+
183
+ /** Pull the street node's resolution tier (if any) for the metric. Ported verbatim from `GeocodeRouter`. */
184
+ function collectStreetTier(
185
+ node: AddressTree["roots"][number]
186
+ ): Array<"address_point" | "interpolated" | "street" | "admin"> {
187
+ const out: Array<"address_point" | "interpolated" | "street" | "admin"> = []
188
+
189
+ if (node.tag === "street") {
190
+ const tier = node.metadata?.["resolution_tier"]
191
+
192
+ if (tier === "address_point" || tier === "interpolated" || tier === "street") {
193
+ out.push(tier)
194
+ }
195
+ }
196
+
197
+ for (const child of node.children) {
198
+ out.push(...collectStreetTier(child))
199
+ }
200
+
201
+ return out
202
+ }
203
+
204
+ /** {@link createServeEngine}'s return value. */
205
+ export interface ServeEngine {
206
+ engine: MailwomanAPIEngine
207
+ preflight: { ok: true } | { ok: false; message: string }
208
+ }
209
+
210
+ /**
211
+ * Build the wired `mailwoman serve` engine. Awaited ONCE at boot (unlike express's lazy per-request `getDeps()`), so a
212
+ * misconfigured deployment reports its preflight failure before the process starts listening — the caller (the `serve`
213
+ * command) decides whether to boot degraded (parse+health only) or exit friendly.
214
+ */
215
+ export async function createServeEngine(): Promise<ServeEngine> {
216
+ // `health` reads files best-effort and never throws — wired unconditionally, matching `HealthRouter`'s "answers even
217
+ // when broken" contract.
218
+ const health: MailwomanAPIEngine["health"] = () => buildHealthData()
219
+
220
+ // Parse needs only the model weights — not the gazetteer. Load them independently of the WOF-data gate below so
221
+ // `/v1/parse` answers whenever weights resolve, even on a geocode-degraded boot. The classifier instance loaded
222
+ // here is reused by the geocode stack below — weights load ONCE per boot.
223
+ let parse: MailwomanAPIEngine["parse"]
224
+ let neuralMod: typeof import("@mailwoman/neural") | undefined
225
+ let classifier: GeocodeClassifier | undefined
226
+
227
+ try {
228
+ neuralMod = await import("@mailwoman/neural")
229
+ classifier = await neuralMod.NeuralAddressClassifier.loadFromWeights({ locale: "en-US" })
230
+
231
+ const parseClassifier = classifier
232
+ parse = async (address, opts) => {
233
+ const tree = await parseClassifier.parse(address, { postcodeRepair: true })
234
+
235
+ return {
236
+ input: address,
237
+ components: decodeAsTuples(tree).map(([tag, value]) => ({ tag, value })),
238
+ tree,
239
+ debug: opts.debug ? decodeAsXML(tree) : undefined,
240
+ }
241
+ }
242
+ } catch {
243
+ // Weights unresolvable — leave parse undefined; the route answers 501 with its existing guard.
244
+ console.error("createServeEngine: neural weights not found — /v1/parse disabled (501)")
245
+ }
246
+
247
+ if (!neuralMod || !classifier) {
248
+ console.error("createServeEngine: @mailwoman/neural + @mailwoman/resolver-wof-sqlite are required")
249
+
250
+ return { engine: { parse, health }, preflight: { ok: false, message: buildPreflightMessage() } }
251
+ }
252
+
253
+ let resolverMod: typeof import("@mailwoman/resolver-wof-sqlite")
254
+
255
+ try {
256
+ resolverMod = await import("@mailwoman/resolver-wof-sqlite")
257
+ } catch {
258
+ console.error("createServeEngine: @mailwoman/neural + @mailwoman/resolver-wof-sqlite are required")
259
+
260
+ return { engine: { parse, health }, preflight: { ok: false, message: buildPreflightMessage() } }
261
+ }
262
+
263
+ const paths = wofPaths()
264
+ // Candidate backend → country-agnostic default (demo's global, population-first behavior); a per-request `country`
265
+ // still scopes. FTS backend keeps the US default. (#170) A candidate DB alone (no WOF admin shard) is a valid boot
266
+ // configuration — `createResolverBackend` prefers it over `wofPaths` — so the preflight gate below checks BOTH,
267
+ // mirroring the drop-ins' `!candidateDb && wofPaths.length === 0` gate rather than `GeocodeRouter`'s WOF-only check.
268
+ // This gate governs geocode/batch/resolveTree/reload ONLY — `parse` is already wired above and unaffected.
269
+ const candidateDb = resolveCandidateDBPath()
270
+
271
+ if (paths.length === 0 && !candidateDb) {
272
+ console.error("createServeEngine: no WOF DBs found — set MAILWOMAN_WOF_DB or MAILWOMAN_CANDIDATE_DB")
273
+
274
+ return { engine: { parse, health }, preflight: { ok: false, message: buildPreflightMessage() } }
275
+ }
276
+
277
+ const backend = createResolverBackend(resolverMod, { wofPaths: paths })
278
+ const resolver = createWOFResolver(backend)
279
+ const shards = new ShardProvider(resolverMod, DATA_ROOT)
280
+ const deps: GeocodeDepsBundle = { classifier, resolver, shards, defaultCountry: candidateDb ? undefined : "US" }
281
+
282
+ // Route records the whole-call metric already (`@mailwoman/api`'s `routes.ts`) — the engine records nothing extra
283
+ // here. Ported from `GeocodeRouter`'s `singleHandler`. The cast mirrors `@mailwoman/api/routes.ts`'s established
284
+ // "documented wire shape looser than the domain type" idiom — `GeocodeOutcome` is a deliberately loose passthrough.
285
+ const geocode: MailwomanAPIEngine["geocode"] = async (address) =>
286
+ (await oneGeocode(deps, address)) as unknown as GeocodeOutcome
287
+
288
+ // Sequential loop — results land in input order; a thrown row is isolated to its own
289
+ // `{ input, error }` slot. Rows are trimmed here (the route passes the raw validated array through).
290
+ //
291
+ // This was a bounded-concurrency worker pool (`MAILWOMAN_BATCH_CONCURRENCY`, default 8) until
292
+ // 2026-07-16. The pool was measured at 1.00x — a geocode cannot overlap another in-process, because
293
+ // `onnxruntime-node`'s `session.run()` blocks the JS thread rather than releasing to the libuv pool,
294
+ // and `node:sqlite` reads are synchronous. The pool bought nothing but the appearance of tuning, so
295
+ // it's a plain loop now. To actually parallelize, cross a thread boundary — see
296
+ // `mailwoman/geocode-stream.ts`. Receipts: `docs/articles/plan/reference/performance.mdx`.
297
+ const batch: MailwomanAPIEngine["batch"] = async (addresses) => {
298
+ const inputs = addresses.map((a) => a.trim())
299
+ const results: BatchRow[] = new Array<BatchRow>(inputs.length)
300
+
301
+ for (let i = 0; i < inputs.length; i++) {
302
+ const input = inputs[i]!
303
+ const t0 = performance.now()
304
+
305
+ try {
306
+ const result = await oneGeocode(deps, input)
307
+ recordTimed(performance.now() - t0, result.resolution_tier)
308
+ results[i] = result as unknown as GeocodeOutcome
309
+ } catch (err) {
310
+ recordTimed(performance.now() - t0, "error")
311
+ results[i] = { input, error: err instanceof Error ? err.message : String(err) }
312
+ }
313
+ }
314
+
315
+ return { results }
316
+ }
317
+
318
+ // Metrics are the engine's own responsibility here — unlike `/v1/geocode`, the route wraps no try/catch around
319
+ // `resolveTree` (it lets a fault fall through to the app's 500 safety net), so the tier metric AND the rethrow both
320
+ // happen here. Ported from `GeocodeRouter`'s `resolveTreeHandler`.
321
+ const resolveTree: MailwomanAPIEngine["resolveTree"] = async (tree, rawOpts) => {
322
+ const incomingOpts = (rawOpts ?? {}) as ResolveOpts
323
+ const t0 = performance.now()
324
+
325
+ try {
326
+ const slug = regionSlugFromTree(tree)
327
+ const { addressPoints, interpolation } = deps.shards.for(slug)
328
+ const opts: ResolveOpts = {
329
+ ...incomingOpts,
330
+ defaultCountry: incomingOpts.defaultCountry ?? deps.defaultCountry,
331
+ ...(addressPoints ? { addressPoints } : {}),
332
+ ...(interpolation
333
+ ? {
334
+ interpolation,
335
+ interpolationRadiusCalibration:
336
+ incomingOpts.interpolationRadiusCalibration ??
337
+ interpCalibrationForRegion(INTERP_RADIUS_CALIBRATION, slug),
338
+ }
339
+ : {}),
340
+ }
341
+ const resolved = await deps.resolver.resolveTree(tree, opts)
342
+ // Best-effort tier metric: read the street node's stamped tier (matches the geocode path).
343
+ const street = resolved.roots.flatMap((r) => collectStreetTier(r)).find(Boolean)
344
+ recordTimed(performance.now() - t0, street ?? "admin")
345
+
346
+ const outcome: ResolveTreeOutcome = { tree: resolved }
347
+
348
+ return outcome
349
+ } catch (err) {
350
+ recordTimed(performance.now() - t0, "error")
351
+ throw err
352
+ }
353
+ }
354
+
355
+ // Ported from `GeocodeRouter`'s `reloadHandler`.
356
+ const reload: MailwomanAPIEngine["reload"] = async () => {
357
+ const versions = deps.shards.reload()
358
+
359
+ return { reloaded: true, versions }
360
+ }
361
+
362
+ return {
363
+ engine: { parse, geocode, batch, resolveTree, reload, health },
364
+ preflight: { ok: true },
365
+ }
366
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The command toolkit for `mailwoman/commands/*` — Pastel/Ink helper types, the one-shot
7
+ * {@linkcode useCommandTask} runner, and the {@linkcode CheckList} renderer. Lives OUTSIDE
8
+ * `commands/` (Pastel treats every file there as a command) and OUTSIDE `sdk/` (`sdk/` submodules
9
+ * mean data acquisition). Built with `createElement`, not JSX, so the module stays plain `.ts` —
10
+ * importable under node's type stripping (the dev `node →` exports condition).
11
+ */
12
+
13
+ import { Box, Text } from "ink"
14
+ import { createElement as h, useEffect, useState } from "react"
15
+ import type * as React from "react"
16
+ import type * as zod from "zod"
17
+
18
+ /**
19
+ * Type-helper to infer the positional arguments of a command.
20
+ */
21
+ export type PositionalArguments<T extends zod.ZodTypeAny> = {
22
+ args: zod.infer<T>
23
+ }
24
+
25
+ /**
26
+ * React component for a command with positional arguments.
27
+ */
28
+ export type PositionalCommandComponent<T extends zod.ZodTypeAny> = React.FC<PositionalArguments<T>>
29
+
30
+ /**
31
+ * Type-helper to infer the options of a command.
32
+ */
33
+ export type CommandProps<
34
+ OptionProps extends zod.ZodObject,
35
+ PositionalProps extends zod.ZodTypeAny | unknown = unknown,
36
+ > = {
37
+ options: zod.infer<OptionProps>
38
+ args: PositionalProps extends zod.ZodTypeAny ? zod.infer<PositionalProps> : unknown[]
39
+ }
40
+
41
+ /**
42
+ * React component for a command with options.
43
+ */
44
+ export type CommandComponent<
45
+ OptionProps extends zod.ZodObject,
46
+ PositionalProps extends zod.ZodTypeAny | unknown = unknown,
47
+ > = React.FC<CommandProps<OptionProps, PositionalProps>>
48
+
49
+ /** The lifecycle of a command's one-shot async task. */
50
+ export type CommandTaskState<T> =
51
+ | { status: "running" }
52
+ | { status: "done"; result: T }
53
+ | { status: "error"; message: string }
54
+
55
+ /**
56
+ * Run a command's one-shot async task and own the exit-code discipline: rejection renders the error state and exits 1;
57
+ * resolution exits with `exitCode(result)` (default 0) — always AFTER the final frame committed. Replaces the
58
+ * copy-pasted useEffect/useState/setImmediate dance in every command.
59
+ */
60
+ /* oxlint-disable react-hooks/exhaustive-deps -- One-shot by design: the task/exitCode closures
61
+ capture their options at mount; tracking them (fresh closure per render) would re-run the task
62
+ every render. The empty/[state] deps are the point. */
63
+ export function useCommandTask<T>(task: () => Promise<T>, exitCode?: (result: T) => number): CommandTaskState<T> {
64
+ const [state, setState] = useState<CommandTaskState<T>>({ status: "running" })
65
+
66
+ useEffect(() => {
67
+ void task().then(
68
+ (result) => setState({ status: "done", result }),
69
+ (error: unknown) =>
70
+ setState({ status: "error", message: error instanceof Error ? (error.stack ?? error.message) : String(error) })
71
+ )
72
+ }, [])
73
+
74
+ useEffect(() => {
75
+ if (state.status === "running") return
76
+ const code = state.status === "error" ? 1 : (exitCode?.(state.result) ?? 0)
77
+ setImmediate(() => process.exit(code))
78
+ }, [state])
79
+
80
+ return state
81
+ }
82
+ /* oxlint-enable react-hooks/exhaustive-deps */
83
+
84
+ /**
85
+ * Build a guidance-grade error whose rendered form is exactly `message` — no stack. {@linkcode useCommandTask} renders
86
+ * `error.stack ?? error.message`, which is right for unexpected failures but turns deliberate user-facing guidance
87
+ * ("Set $MAILWOMAN_WOF_DB…") into a stack dump. Throw `commandError(msg)` for those instead of `new Error(msg)`.
88
+ */
89
+ export function commandError(message: string): Error {
90
+ const error = new Error(message)
91
+ error.stack = message
92
+
93
+ return error
94
+ }
95
+
96
+ /** One ✓/✗ line in a {@linkcode CheckList}. */
97
+ export interface Check {
98
+ ok: boolean
99
+ check: string
100
+ detail?: string
101
+ }
102
+
103
+ /**
104
+ * The ✓/✗ check-list + PASS/FAIL renderer (extracted from `gazetteer verify`). Pass `verdict` to append the summary
105
+ * line.
106
+ */
107
+ export function CheckList({ checks, verdict }: { checks: readonly Check[]; verdict?: boolean }): React.ReactElement {
108
+ const lines = checks.map((c, i) =>
109
+ h(
110
+ Text,
111
+ { key: i, color: c.ok ? "green" : "red" },
112
+ `${c.ok ? "✓" : "✗"} ${c.check}${c.detail ? `: ${c.detail}` : ""}`
113
+ )
114
+ )
115
+ const summary =
116
+ verdict === undefined
117
+ ? null
118
+ : h(
119
+ Text,
120
+ { color: verdict ? "green" : "red" },
121
+ `${verdict ? "PASS" : "FAIL"} (${checks.filter((c) => c.ok).length}/${checks.length} checks)`
122
+ )
123
+
124
+ return h(Box, { flexDirection: "column" }, ...lines, summary)
125
+ }