@hawkeyexl/manni 4.11.0 → 4.12.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 (238) hide show
  1. package/README.md +1 -1
  2. package/dist/{batch-UAULUMGH.js → batch-KMGZMIAQ.js} +20 -19
  3. package/dist/build-NO4AWCYG.js +36 -0
  4. package/dist/calibrate-XQUG5XAK.js +41 -0
  5. package/dist/capture-5XE3ZMXK.js +38 -0
  6. package/dist/{check-YEOYBFST.js → check-HTNPLWOV.js} +19 -18
  7. package/dist/check-RJ45IBA6.js +34 -0
  8. package/dist/{check-VELUQAUW.js → check-W24BQNCT.js} +35 -34
  9. package/dist/{check-VELUQAUW.js.map → check-W24BQNCT.js.map} +1 -1
  10. package/dist/check-WFXSXICD.js +30 -0
  11. package/dist/{chunk-IGSHYP74.js → chunk-23P7KMJM.js} +4 -4
  12. package/dist/{chunk-HA6MQMOY.js → chunk-24J7TYPU.js} +6 -6
  13. package/dist/{chunk-PCS4VJBV.js → chunk-37QJLVS3.js} +9 -9
  14. package/dist/{chunk-4CK6ZSOK.js → chunk-3P6KCU36.js} +3 -3
  15. package/dist/{chunk-54F74XR3.js → chunk-5JBVFWVP.js} +19 -19
  16. package/dist/{chunk-B7XYNRJQ.js → chunk-6KIYVVKL.js} +2 -2
  17. package/dist/{chunk-J6IDQGJL.js → chunk-6TVH4FI7.js} +469 -85
  18. package/dist/chunk-6TVH4FI7.js.map +1 -0
  19. package/dist/{chunk-GDVZ5VII.js → chunk-7A452BPZ.js} +4 -4
  20. package/dist/{chunk-M42DZCOU.js → chunk-7FCLXEAQ.js} +2 -2
  21. package/dist/{chunk-2MJ62AYQ.js → chunk-7MCQHTQ5.js} +4 -3
  22. package/dist/chunk-7MCQHTQ5.js.map +1 -0
  23. package/dist/{chunk-LBSCW232.js → chunk-7NXPQKSB.js} +7 -7
  24. package/dist/{chunk-OPXWPJDA.js → chunk-A3TEDWBZ.js} +14 -14
  25. package/dist/{chunk-BBT6AVUG.js → chunk-BHHXU2D2.js} +2 -2
  26. package/dist/{chunk-D4N2V43Z.js → chunk-CEI2J42X.js} +13 -7
  27. package/dist/chunk-CEI2J42X.js.map +1 -0
  28. package/dist/{chunk-FQXWVS6J.js → chunk-CMMBWHT2.js} +9 -9
  29. package/dist/{chunk-EIFCP6CZ.js → chunk-CO47QJKZ.js} +2 -2
  30. package/dist/{chunk-2DRHT73U.js → chunk-CVZMKWZ5.js} +913 -1398
  31. package/dist/chunk-CVZMKWZ5.js.map +1 -0
  32. package/dist/{chunk-ADMEPUBT.js → chunk-CXWTNT2O.js} +3 -3
  33. package/dist/{chunk-LKP7QDFN.js → chunk-DAQTRAP5.js} +4 -4
  34. package/dist/chunk-DSWKBZWW.js +16 -0
  35. package/dist/chunk-DSWKBZWW.js.map +1 -0
  36. package/dist/{chunk-PWBH7DCQ.js → chunk-EESH6COU.js} +4 -4
  37. package/dist/{chunk-JPQDNYXH.js → chunk-EM67O2ET.js} +8 -2
  38. package/dist/chunk-EM67O2ET.js.map +1 -0
  39. package/dist/{chunk-HQR2ZBHW.js → chunk-I2XQRRP7.js} +36 -7
  40. package/dist/chunk-I2XQRRP7.js.map +1 -0
  41. package/dist/{chunk-NCPWXHDO.js → chunk-JEUFYSFQ.js} +4 -4
  42. package/dist/{chunk-2IR4T5PO.js → chunk-JJ4RSDSY.js} +2 -2
  43. package/dist/chunk-KCB5JCK4.js +2272 -0
  44. package/dist/chunk-KCB5JCK4.js.map +1 -0
  45. package/dist/{chunk-VSEBFHAY.js → chunk-KFKL4VTL.js} +11369 -10218
  46. package/dist/chunk-KFKL4VTL.js.map +1 -0
  47. package/dist/{chunk-PCQ7ELJH.js → chunk-L2TD7DZB.js} +109 -5
  48. package/dist/chunk-L2TD7DZB.js.map +1 -0
  49. package/dist/{chunk-WDWXAYVH.js → chunk-L3RLGGRK.js} +4 -4
  50. package/dist/{chunk-T7JAOYE5.js → chunk-L75CAZG4.js} +6 -6
  51. package/dist/{chunk-U7CTFN2J.js → chunk-MYRPMSOX.js} +7 -7
  52. package/dist/{chunk-Y6DOMTQD.js → chunk-NCF5NQV5.js} +27 -27
  53. package/dist/chunk-NCF5NQV5.js.map +1 -0
  54. package/dist/{chunk-WQWKYAAK.js → chunk-NUQF5F5J.js} +2 -2
  55. package/dist/{chunk-FUCPZVRY.js → chunk-OEN6FBUW.js} +2 -2
  56. package/dist/{chunk-FBMZJEB3.js → chunk-OKMTUAWO.js} +4 -4
  57. package/dist/{chunk-QLWC7MKD.js → chunk-OTJOBZJ5.js} +135 -135
  58. package/dist/chunk-OTJOBZJ5.js.map +1 -0
  59. package/dist/{chunk-R4WXQIOZ.js → chunk-PCEM5FOZ.js} +3 -3
  60. package/dist/{chunk-4D5IZSXM.js → chunk-PXNCRGUL.js} +3 -3
  61. package/dist/chunk-Q2QC5AZB.js +1868 -0
  62. package/dist/chunk-Q2QC5AZB.js.map +1 -0
  63. package/dist/{chunk-ODRPL3WB.js → chunk-Q3ODX5PF.js} +2 -2
  64. package/dist/{chunk-KGEQJ5CC.js → chunk-RRFFUWDL.js} +8 -8
  65. package/dist/{chunk-NSBXVPZL.js → chunk-S3XUYW5K.js} +17 -17
  66. package/dist/{chunk-LPIF5LIE.js → chunk-SAU2WQFC.js} +2 -2
  67. package/dist/{chunk-MSRU4D4P.js → chunk-SFLICIQV.js} +2 -2
  68. package/dist/{chunk-NGTVREIH.js → chunk-U6IHLEWT.js} +2 -2
  69. package/dist/{chunk-QCHVR4ZM.js → chunk-VM7VAFJW.js} +33 -7
  70. package/dist/chunk-VM7VAFJW.js.map +1 -0
  71. package/dist/{chunk-6HWBRZIN.js → chunk-VQYGLU4C.js} +252 -193
  72. package/dist/chunk-VQYGLU4C.js.map +1 -0
  73. package/dist/{chunk-2IAZEB45.js → chunk-WGMFDG6U.js} +229 -821
  74. package/dist/chunk-WGMFDG6U.js.map +1 -0
  75. package/dist/{chunk-GV56WGL2.js → chunk-WO4JYWZK.js} +1036 -883
  76. package/dist/chunk-WO4JYWZK.js.map +1 -0
  77. package/dist/chunk-XYRUDFKS.js +685 -0
  78. package/dist/chunk-XYRUDFKS.js.map +1 -0
  79. package/dist/{chunk-VQK4VHLF.js → chunk-YQV75FIL.js} +2 -2
  80. package/dist/chunk-YUTVAMWW.js +2 -0
  81. package/dist/{chunk-RLT5SEML.js → chunk-Z5MZCCTT.js} +6 -6
  82. package/dist/{chunk-BKVHX6FU.js → chunk-ZIRBFXDW.js} +5 -10
  83. package/dist/chunk-ZIRBFXDW.js.map +1 -0
  84. package/dist/{cli-O4ABTN4Z.js → cli-236TGVJG.js} +31 -30
  85. package/dist/{cli-O4ABTN4Z.js.map → cli-236TGVJG.js.map} +1 -1
  86. package/dist/{cli-3BTCK4AB.js → cli-3FAPJBEY.js} +27 -26
  87. package/dist/{cli-3BTCK4AB.js.map → cli-3FAPJBEY.js.map} +1 -1
  88. package/dist/{cli-JJMOVIKS.js → cli-BS43EVCD.js} +14 -14
  89. package/dist/{cli-RORDPAPE.js → cli-CMR4FYF6.js} +59 -38
  90. package/dist/cli-CMR4FYF6.js.map +1 -0
  91. package/dist/{cli-ONTC5PKT.js → cli-FX53PBQ3.js} +9 -9
  92. package/dist/{cli-6KZTHWGP.js → cli-IQNJLKSN.js} +37 -36
  93. package/dist/{cli-6KZTHWGP.js.map → cli-IQNJLKSN.js.map} +1 -1
  94. package/dist/{cli-4MF37HAQ.js → cli-KOMXVPXX.js} +26 -25
  95. package/dist/{cli-4MF37HAQ.js.map → cli-KOMXVPXX.js.map} +1 -1
  96. package/dist/{cli-I62SRH3R.js → cli-NGL63E53.js} +14 -13
  97. package/dist/{cli-DRMZBZLB.js → cli-QPGS2MR5.js} +43 -42
  98. package/dist/{cli-DRMZBZLB.js.map → cli-QPGS2MR5.js.map} +1 -1
  99. package/dist/{cli-VID7PJS3.js → cli-RKQX4TI3.js} +23 -22
  100. package/dist/{cli-VID7PJS3.js.map → cli-RKQX4TI3.js.map} +1 -1
  101. package/dist/{cli-VUKMWSSF.js → cli-WNEI5L7P.js} +4 -4
  102. package/dist/cli.js +19 -13
  103. package/dist/cli.js.map +1 -1
  104. package/dist/{config-XDA42RJL.js → config-JJW75SBX.js} +5 -5
  105. package/dist/docmeta.js +13 -12
  106. package/dist/docmeta.js.map +1 -1
  107. package/dist/github-4UR67MEN.js +35 -0
  108. package/dist/github-UPGKMVVF.js +30 -0
  109. package/dist/graph/runtime.d.ts +4 -1
  110. package/dist/graph/runtime.js +20 -9
  111. package/dist/graph/runtime.js.map +1 -1
  112. package/dist/{host-6GURH5HE.js → host-YSTYO5DX.js} +3 -3
  113. package/dist/index.d.ts +192 -26
  114. package/dist/index.js +129 -124
  115. package/dist/index.js.map +1 -1
  116. package/dist/{json-BXY44ED7.js → json-DQIELR36.js} +18 -17
  117. package/dist/labels-XSTJJMSG.js +73 -0
  118. package/dist/labels-XSTJJMSG.js.map +1 -0
  119. package/dist/lint-BLBGGOYM.js +34 -0
  120. package/dist/list-B3RRRWFQ.js +32 -0
  121. package/dist/list-ZFRT3JQH.js +35 -0
  122. package/dist/{page-EV7OUBAU.js → page-5ISEVJR5.js} +18 -16
  123. package/dist/{prepare-JHJDJB6I.js → prepare-KFXXG2CJ.js} +22 -21
  124. package/dist/{prepare-JHJDJB6I.js.map → prepare-KFXXG2CJ.js.map} +1 -1
  125. package/dist/{pretty-Q5Y2PA64.js → pretty-Y7CGQLAF.js} +18 -17
  126. package/dist/{pretty-SCRK66M5.js → pretty-ZISH3Q4F.js} +18 -17
  127. package/dist/{provider-527CO4DM.js → provider-LBX4IXGX.js} +3 -3
  128. package/dist/{release-3D5PK6M7.js → release-VV3UTKE5.js} +3 -3
  129. package/dist/{reporters-M6LJ2IYU.js → reporters-26H3N2KW.js} +3 -3
  130. package/dist/{reporters-A5ADKDYR.js → reporters-4STXJJE2.js} +16 -15
  131. package/dist/{reporters-RBYTASMI.js → reporters-74B4U2GZ.js} +7 -7
  132. package/dist/run-DVGTKM52.js +37 -0
  133. package/dist/{term-CQAGNG3Q.js → term-PLJEYJQV.js} +981 -141
  134. package/dist/term-PLJEYJQV.js.map +1 -0
  135. package/dist/validate-CJNFQHMD.js +19 -0
  136. package/package.json +3 -2
  137. package/plugin/manni/.claude-plugin/plugin.json +1 -1
  138. package/shapes/graph/shapes-1.1.0.ttl +785 -0
  139. package/dist/build-QSPNK5AL.js +0 -35
  140. package/dist/calibrate-KXN4URPM.js +0 -40
  141. package/dist/capture-JHTSUPUC.js +0 -37
  142. package/dist/check-MDGXEUWL.js +0 -20
  143. package/dist/check-WOQRKNZK.js +0 -29
  144. package/dist/chunk-2DRHT73U.js.map +0 -1
  145. package/dist/chunk-2IAZEB45.js.map +0 -1
  146. package/dist/chunk-2MJ62AYQ.js.map +0 -1
  147. package/dist/chunk-4VBSTXN6.js +0 -392
  148. package/dist/chunk-4VBSTXN6.js.map +0 -1
  149. package/dist/chunk-6HWBRZIN.js.map +0 -1
  150. package/dist/chunk-BKVHX6FU.js.map +0 -1
  151. package/dist/chunk-D4N2V43Z.js.map +0 -1
  152. package/dist/chunk-EFW7CAG4.js +0 -387
  153. package/dist/chunk-EFW7CAG4.js.map +0 -1
  154. package/dist/chunk-GV56WGL2.js.map +0 -1
  155. package/dist/chunk-HQR2ZBHW.js.map +0 -1
  156. package/dist/chunk-J6IDQGJL.js.map +0 -1
  157. package/dist/chunk-JPQDNYXH.js.map +0 -1
  158. package/dist/chunk-JROBZ7JA.js +0 -818
  159. package/dist/chunk-JROBZ7JA.js.map +0 -1
  160. package/dist/chunk-PCQ7ELJH.js.map +0 -1
  161. package/dist/chunk-QCHVR4ZM.js.map +0 -1
  162. package/dist/chunk-QLWC7MKD.js.map +0 -1
  163. package/dist/chunk-TU2WUBVZ.js +0 -2
  164. package/dist/chunk-VSEBFHAY.js.map +0 -1
  165. package/dist/chunk-Y6DOMTQD.js.map +0 -1
  166. package/dist/cli-RORDPAPE.js.map +0 -1
  167. package/dist/github-RY3FRA6P.js +0 -29
  168. package/dist/github-YKQSH7FS.js +0 -34
  169. package/dist/lint-YJ2L22BW.js +0 -33
  170. package/dist/list-NSG24VVX.js +0 -31
  171. package/dist/list-V6EGCSHX.js +0 -34
  172. package/dist/run-BUE2DDXN.js +0 -36
  173. package/dist/term-CQAGNG3Q.js.map +0 -1
  174. package/dist/validate-ZBW44RHT.js +0 -18
  175. /package/dist/{batch-UAULUMGH.js.map → batch-KMGZMIAQ.js.map} +0 -0
  176. /package/dist/{build-QSPNK5AL.js.map → build-NO4AWCYG.js.map} +0 -0
  177. /package/dist/{calibrate-KXN4URPM.js.map → calibrate-XQUG5XAK.js.map} +0 -0
  178. /package/dist/{capture-JHTSUPUC.js.map → capture-5XE3ZMXK.js.map} +0 -0
  179. /package/dist/{check-MDGXEUWL.js.map → check-HTNPLWOV.js.map} +0 -0
  180. /package/dist/{check-WOQRKNZK.js.map → check-RJ45IBA6.js.map} +0 -0
  181. /package/dist/{check-YEOYBFST.js.map → check-WFXSXICD.js.map} +0 -0
  182. /package/dist/{chunk-IGSHYP74.js.map → chunk-23P7KMJM.js.map} +0 -0
  183. /package/dist/{chunk-HA6MQMOY.js.map → chunk-24J7TYPU.js.map} +0 -0
  184. /package/dist/{chunk-PCS4VJBV.js.map → chunk-37QJLVS3.js.map} +0 -0
  185. /package/dist/{chunk-4CK6ZSOK.js.map → chunk-3P6KCU36.js.map} +0 -0
  186. /package/dist/{chunk-54F74XR3.js.map → chunk-5JBVFWVP.js.map} +0 -0
  187. /package/dist/{chunk-B7XYNRJQ.js.map → chunk-6KIYVVKL.js.map} +0 -0
  188. /package/dist/{chunk-GDVZ5VII.js.map → chunk-7A452BPZ.js.map} +0 -0
  189. /package/dist/{chunk-M42DZCOU.js.map → chunk-7FCLXEAQ.js.map} +0 -0
  190. /package/dist/{chunk-LBSCW232.js.map → chunk-7NXPQKSB.js.map} +0 -0
  191. /package/dist/{chunk-OPXWPJDA.js.map → chunk-A3TEDWBZ.js.map} +0 -0
  192. /package/dist/{chunk-BBT6AVUG.js.map → chunk-BHHXU2D2.js.map} +0 -0
  193. /package/dist/{chunk-FQXWVS6J.js.map → chunk-CMMBWHT2.js.map} +0 -0
  194. /package/dist/{chunk-EIFCP6CZ.js.map → chunk-CO47QJKZ.js.map} +0 -0
  195. /package/dist/{chunk-ADMEPUBT.js.map → chunk-CXWTNT2O.js.map} +0 -0
  196. /package/dist/{chunk-LKP7QDFN.js.map → chunk-DAQTRAP5.js.map} +0 -0
  197. /package/dist/{chunk-PWBH7DCQ.js.map → chunk-EESH6COU.js.map} +0 -0
  198. /package/dist/{chunk-NCPWXHDO.js.map → chunk-JEUFYSFQ.js.map} +0 -0
  199. /package/dist/{chunk-2IR4T5PO.js.map → chunk-JJ4RSDSY.js.map} +0 -0
  200. /package/dist/{chunk-WDWXAYVH.js.map → chunk-L3RLGGRK.js.map} +0 -0
  201. /package/dist/{chunk-T7JAOYE5.js.map → chunk-L75CAZG4.js.map} +0 -0
  202. /package/dist/{chunk-U7CTFN2J.js.map → chunk-MYRPMSOX.js.map} +0 -0
  203. /package/dist/{chunk-WQWKYAAK.js.map → chunk-NUQF5F5J.js.map} +0 -0
  204. /package/dist/{chunk-FUCPZVRY.js.map → chunk-OEN6FBUW.js.map} +0 -0
  205. /package/dist/{chunk-FBMZJEB3.js.map → chunk-OKMTUAWO.js.map} +0 -0
  206. /package/dist/{chunk-R4WXQIOZ.js.map → chunk-PCEM5FOZ.js.map} +0 -0
  207. /package/dist/{chunk-4D5IZSXM.js.map → chunk-PXNCRGUL.js.map} +0 -0
  208. /package/dist/{chunk-ODRPL3WB.js.map → chunk-Q3ODX5PF.js.map} +0 -0
  209. /package/dist/{chunk-KGEQJ5CC.js.map → chunk-RRFFUWDL.js.map} +0 -0
  210. /package/dist/{chunk-NSBXVPZL.js.map → chunk-S3XUYW5K.js.map} +0 -0
  211. /package/dist/{chunk-LPIF5LIE.js.map → chunk-SAU2WQFC.js.map} +0 -0
  212. /package/dist/{chunk-MSRU4D4P.js.map → chunk-SFLICIQV.js.map} +0 -0
  213. /package/dist/{chunk-NGTVREIH.js.map → chunk-U6IHLEWT.js.map} +0 -0
  214. /package/dist/{chunk-VQK4VHLF.js.map → chunk-YQV75FIL.js.map} +0 -0
  215. /package/dist/{chunk-TU2WUBVZ.js.map → chunk-YUTVAMWW.js.map} +0 -0
  216. /package/dist/{chunk-RLT5SEML.js.map → chunk-Z5MZCCTT.js.map} +0 -0
  217. /package/dist/{cli-JJMOVIKS.js.map → cli-BS43EVCD.js.map} +0 -0
  218. /package/dist/{cli-ONTC5PKT.js.map → cli-FX53PBQ3.js.map} +0 -0
  219. /package/dist/{cli-I62SRH3R.js.map → cli-NGL63E53.js.map} +0 -0
  220. /package/dist/{cli-VUKMWSSF.js.map → cli-WNEI5L7P.js.map} +0 -0
  221. /package/dist/{config-XDA42RJL.js.map → config-JJW75SBX.js.map} +0 -0
  222. /package/dist/{github-RY3FRA6P.js.map → github-4UR67MEN.js.map} +0 -0
  223. /package/dist/{github-YKQSH7FS.js.map → github-UPGKMVVF.js.map} +0 -0
  224. /package/dist/{host-6GURH5HE.js.map → host-YSTYO5DX.js.map} +0 -0
  225. /package/dist/{json-BXY44ED7.js.map → json-DQIELR36.js.map} +0 -0
  226. /package/dist/{lint-YJ2L22BW.js.map → lint-BLBGGOYM.js.map} +0 -0
  227. /package/dist/{list-NSG24VVX.js.map → list-B3RRRWFQ.js.map} +0 -0
  228. /package/dist/{list-V6EGCSHX.js.map → list-ZFRT3JQH.js.map} +0 -0
  229. /package/dist/{page-EV7OUBAU.js.map → page-5ISEVJR5.js.map} +0 -0
  230. /package/dist/{pretty-Q5Y2PA64.js.map → pretty-Y7CGQLAF.js.map} +0 -0
  231. /package/dist/{pretty-SCRK66M5.js.map → pretty-ZISH3Q4F.js.map} +0 -0
  232. /package/dist/{provider-527CO4DM.js.map → provider-LBX4IXGX.js.map} +0 -0
  233. /package/dist/{release-3D5PK6M7.js.map → release-VV3UTKE5.js.map} +0 -0
  234. /package/dist/{reporters-A5ADKDYR.js.map → reporters-26H3N2KW.js.map} +0 -0
  235. /package/dist/{reporters-M6LJ2IYU.js.map → reporters-4STXJJE2.js.map} +0 -0
  236. /package/dist/{reporters-RBYTASMI.js.map → reporters-74B4U2GZ.js.map} +0 -0
  237. /package/dist/{run-BUE2DDXN.js.map → run-DVGTKM52.js.map} +0 -0
  238. /package/dist/{validate-ZBW44RHT.js.map → validate-CJNFQHMD.js.map} +0 -0
@@ -0,0 +1,2272 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ COPIED_SHARE,
4
+ DEFAULT_MODEL,
5
+ GraphError,
6
+ LOCALIZATIONS_FILENAME,
7
+ NS,
8
+ PREFIXES,
9
+ RDF_TYPE,
10
+ STDIN_PATH,
11
+ UNDETERMINED,
12
+ byCodeUnit,
13
+ canonicalLanguageTag,
14
+ copiedPairs,
15
+ formatOf,
16
+ isTreeFormat,
17
+ loadRunConfig,
18
+ mergePage,
19
+ metadataOf,
20
+ mintTreeSections,
21
+ openMetaView,
22
+ parseLocalizations,
23
+ parseTree,
24
+ profileFor,
25
+ questionKey,
26
+ readQuestionFile,
27
+ readSampleQuestions,
28
+ withPrefix
29
+ } from "./chunk-Q2QC5AZB.js";
30
+ import {
31
+ packageRoot
32
+ } from "./chunk-TIUTIOFB.js";
33
+ import {
34
+ bodyLineOf,
35
+ findWindows,
36
+ pinOfLines,
37
+ spellLines,
38
+ splitLines,
39
+ toFileLines
40
+ } from "./chunk-WO4JYWZK.js";
41
+ import {
42
+ locateFrontmatter
43
+ } from "./chunk-KFKL4VTL.js";
44
+ import {
45
+ warn
46
+ } from "./chunk-HX44GB4Z.js";
47
+ import {
48
+ errorMessage
49
+ } from "./chunk-G3T47CFV.js";
50
+
51
+ // src/graph/core/load.ts
52
+ import { existsSync, readFileSync } from "node:fs";
53
+ import { Parser, Store } from "n3";
54
+ var XSD_STRING = `${NS.xsd}string`;
55
+ function loadGraph(ttlPath) {
56
+ if (!existsSync(ttlPath)) {
57
+ throw new GraphError(
58
+ `Graph not found: ${ttlPath} \u2014 run \`manni graph build\` first.`
59
+ );
60
+ }
61
+ return parseGraph(readFileSync(ttlPath, "utf8"), ttlPath);
62
+ }
63
+ function parseGraph(turtle, label) {
64
+ const parser = new Parser({ format: "text/turtle" });
65
+ let quads;
66
+ try {
67
+ quads = parser.parse(turtle);
68
+ } catch (e) {
69
+ throw new GraphError(
70
+ `Failed to parse ${label}: ${errorMessage(e)}`
71
+ );
72
+ }
73
+ return new Store(quads);
74
+ }
75
+ function storeToQuads(store) {
76
+ return store.getQuads(null, null, null, null).map((q) => {
77
+ const o = q.object;
78
+ let term;
79
+ if (o.termType === "Literal" && o.language !== "") {
80
+ term = { kind: "literal", value: o.value, language: o.language };
81
+ } else if (o.termType === "Literal") {
82
+ const dt = o.datatype.value;
83
+ term = dt && dt !== XSD_STRING ? { kind: "literal", value: o.value, datatype: dt } : { kind: "literal", value: o.value };
84
+ } else {
85
+ term = { kind: "iri", value: o.value };
86
+ }
87
+ return { s: q.subject.value, p: q.predicate.value, o: term };
88
+ });
89
+ }
90
+ function expandTerm(input) {
91
+ const colon = input.indexOf(":");
92
+ if (colon > 0) {
93
+ const prefix = input.slice(0, colon);
94
+ const ns = NS[prefix];
95
+ if (ns) return `${ns}${input.slice(colon + 1)}`;
96
+ }
97
+ return input;
98
+ }
99
+ function compactIri(iri) {
100
+ for (const [prefix, ns] of PREFIXES) {
101
+ if (iri.startsWith(ns) && iri.length > ns.length) {
102
+ return `${prefix}:${iri.slice(ns.length)}`;
103
+ }
104
+ }
105
+ return iri;
106
+ }
107
+
108
+ // src/graph/core/pkg.ts
109
+ import { join } from "node:path";
110
+ function bundledShapesPath(moduleUrl) {
111
+ return join(packageRoot(moduleUrl), "shapes", "graph", "shapes-1.1.0.ttl");
112
+ }
113
+
114
+ // src/graph/core/shacl.ts
115
+ import { readFileSync as readFileSync2 } from "node:fs";
116
+ import { DataFactory, Parser as Parser2, Store as Store2 } from "n3";
117
+ import SHACLValidator from "rdf-validate-shacl";
118
+ var FAMILY_SEVERITY = {
119
+ violation: "error",
120
+ warning: "warning",
121
+ info: "notice"
122
+ };
123
+ function finding(shaclSeverity, rest) {
124
+ return { severity: FAMILY_SEVERITY[shaclSeverity], shaclSeverity, ...rest };
125
+ }
126
+ var SEVERITY_RANK = {
127
+ error: 0,
128
+ warning: 1,
129
+ notice: 2
130
+ };
131
+ function compareFindings(a, b) {
132
+ return SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] || byCodeUnit(a.focusNode, b.focusNode) || byCodeUnit(a.path ?? "", b.path ?? "") || byCodeUnit(a.message, b.message);
133
+ }
134
+ function loadShapes(paths) {
135
+ const store = new Store2();
136
+ for (const path of paths) {
137
+ let text;
138
+ try {
139
+ text = readFileSync2(path, "utf8");
140
+ } catch {
141
+ throw new GraphError(`Shapes file not found: ${path}`);
142
+ }
143
+ try {
144
+ store.addQuads(new Parser2({ format: "text/turtle" }).parse(text));
145
+ } catch (e) {
146
+ throw new GraphError(
147
+ `Failed to parse shapes ${path}: ${errorMessage(e)}`
148
+ );
149
+ }
150
+ }
151
+ return store;
152
+ }
153
+ function blameDocs(store, focus) {
154
+ const pathPred = DataFactory.namedNode(`${NS.graph}path`);
155
+ const pathOf = (iri) => store.getQuads(DataFactory.namedNode(iri), pathPred, null, null)[0]?.object.value;
156
+ const found = /* @__PURE__ */ new Set();
157
+ const visited = /* @__PURE__ */ new Set();
158
+ const walk = (iri, depth) => {
159
+ if (visited.has(iri)) return;
160
+ visited.add(iri);
161
+ const own = pathOf(iri);
162
+ if (own !== void 0) {
163
+ found.add(own);
164
+ return;
165
+ }
166
+ const hash = iri.indexOf("#");
167
+ if (hash !== -1) {
168
+ const base = pathOf(iri.slice(0, hash));
169
+ if (base !== void 0) {
170
+ found.add(base);
171
+ return;
172
+ }
173
+ }
174
+ if (depth === 0) return;
175
+ for (const quad of store.getQuads(null, null, DataFactory.namedNode(iri), null)) {
176
+ if (quad.subject.termType === "NamedNode") {
177
+ walk(quad.subject.value, depth - 1);
178
+ }
179
+ }
180
+ };
181
+ walk(focus, 2);
182
+ return [...found].sort();
183
+ }
184
+ function broaderEdges(store) {
185
+ const edges2 = [];
186
+ for (const q of store.getQuads(
187
+ null,
188
+ DataFactory.namedNode(`${NS.skos}broader`),
189
+ null,
190
+ null
191
+ )) {
192
+ if (q.object.termType === "NamedNode") {
193
+ edges2.push({ s: q.subject.value, o: q.object.value });
194
+ }
195
+ }
196
+ for (const q of store.getQuads(
197
+ null,
198
+ DataFactory.namedNode(`${NS.skos}narrower`),
199
+ null,
200
+ null
201
+ )) {
202
+ if (q.object.termType === "NamedNode") {
203
+ edges2.push({ s: q.object.value, o: q.subject.value });
204
+ }
205
+ }
206
+ return edges2;
207
+ }
208
+ function broaderCycles(store) {
209
+ const adjacency = /* @__PURE__ */ new Map();
210
+ const selfLoops = /* @__PURE__ */ new Set();
211
+ for (const { s, o } of broaderEdges(store)) {
212
+ if (s === o) selfLoops.add(s);
213
+ const list = adjacency.get(s);
214
+ if (list) list.push(o);
215
+ else adjacency.set(s, [o]);
216
+ if (!adjacency.has(o)) adjacency.set(o, []);
217
+ }
218
+ for (const list of adjacency.values()) list.sort();
219
+ const index = /* @__PURE__ */ new Map();
220
+ const lowlink = /* @__PURE__ */ new Map();
221
+ const onStack = /* @__PURE__ */ new Set();
222
+ const stack = [];
223
+ const components = [];
224
+ let counter = 0;
225
+ const numberOf = (map, node) => {
226
+ const value = map.get(node);
227
+ if (value === void 0) {
228
+ throw new GraphError(
229
+ `internal: ${node} was visited before it was numbered`
230
+ );
231
+ }
232
+ return value;
233
+ };
234
+ const nodes = [...adjacency.keys()].sort();
235
+ for (const root of nodes) {
236
+ if (index.has(root)) continue;
237
+ const frames = [[root, 0]];
238
+ for (let frame = frames.at(-1); frame !== void 0; frame = frames.at(-1)) {
239
+ const [node, childIdx] = frame;
240
+ if (childIdx === 0) {
241
+ index.set(node, counter);
242
+ lowlink.set(node, counter);
243
+ counter += 1;
244
+ stack.push(node);
245
+ onStack.add(node);
246
+ }
247
+ const children = adjacency.get(node) ?? [];
248
+ const child = children[childIdx];
249
+ if (child !== void 0) {
250
+ frame[1] += 1;
251
+ if (!index.has(child)) {
252
+ frames.push([child, 0]);
253
+ } else if (onStack.has(child)) {
254
+ lowlink.set(
255
+ node,
256
+ Math.min(numberOf(lowlink, node), numberOf(index, child))
257
+ );
258
+ }
259
+ } else {
260
+ if (lowlink.get(node) === index.get(node)) {
261
+ const component = [];
262
+ let member;
263
+ do {
264
+ member = stack.pop();
265
+ if (member === void 0) break;
266
+ onStack.delete(member);
267
+ component.push(member);
268
+ } while (member !== node);
269
+ if (component.length > 1 || selfLoops.has(node)) {
270
+ components.push(component.sort());
271
+ }
272
+ }
273
+ frames.pop();
274
+ const parent = frames.at(-1);
275
+ if (parent) {
276
+ lowlink.set(
277
+ parent[0],
278
+ Math.min(numberOf(lowlink, parent[0]), numberOf(lowlink, node))
279
+ );
280
+ }
281
+ }
282
+ }
283
+ }
284
+ return components.sort((a, b) => (a[0] ?? "") < (b[0] ?? "") ? -1 : 1);
285
+ }
286
+ function broaderClosure(edges2, start) {
287
+ const adjacency = /* @__PURE__ */ new Map();
288
+ for (const { s, o } of edges2) {
289
+ const list = adjacency.get(s);
290
+ if (list) list.push(o);
291
+ else adjacency.set(s, [o]);
292
+ }
293
+ const seen = /* @__PURE__ */ new Set();
294
+ const queue = [...adjacency.get(start) ?? []];
295
+ for (let node = queue.shift(); node !== void 0; node = queue.shift()) {
296
+ if (seen.has(node)) continue;
297
+ seen.add(node);
298
+ queue.push(...adjacency.get(node) ?? []);
299
+ }
300
+ return seen;
301
+ }
302
+ function relatedConflicts(store) {
303
+ const edges2 = broaderEdges(store);
304
+ const direct = /* @__PURE__ */ new Map();
305
+ for (const e of edges2) {
306
+ const targets = direct.get(e.s);
307
+ if (targets) targets.add(e.o);
308
+ else direct.set(e.s, /* @__PURE__ */ new Set([e.o]));
309
+ }
310
+ const findings = [];
311
+ for (const q of store.getQuads(
312
+ null,
313
+ DataFactory.namedNode(`${NS.skos}related`),
314
+ null,
315
+ null
316
+ )) {
317
+ if (q.object.termType !== "NamedNode") continue;
318
+ const s = q.subject.value;
319
+ const o = q.object.value;
320
+ if (direct.get(s)?.has(o)) continue;
321
+ const conflict = broaderClosure(edges2, s).has(o) || broaderClosure(edges2, o).has(s);
322
+ if (!conflict) continue;
323
+ findings.push(
324
+ finding("violation", {
325
+ message: `skos:related conflicts with skos:broaderTransitive between ${compactIri(s)} and ${compactIri(o)} \u2014 a concept cannot be both related to and an ancestor/descendant of another`,
326
+ focusNode: s,
327
+ path: `${NS.skos}related`,
328
+ docs: blameDocs(store, s)
329
+ })
330
+ );
331
+ }
332
+ return findings;
333
+ }
334
+ function cycleFindings(store) {
335
+ return broaderCycles(store).map(
336
+ (members) => finding("violation", {
337
+ message: `skos:broader cycle through ${members.map((m) => compactIri(m)).join(
338
+ ", "
339
+ )} (${members.map((m) => m).join(" \u2192 ")}) \u2014 a concept cannot be its own ancestor`,
340
+ // `broaderCycles` returns only components of at least one member, so
341
+ // the first is there; `?? ""` says that without asserting it.
342
+ focusNode: members[0] ?? "",
343
+ path: `${NS.skos}broader`,
344
+ docs: [...new Set(members.flatMap((m) => blameDocs(store, m)))].sort()
345
+ })
346
+ );
347
+ }
348
+ var PREF_LABEL_COLLISION = "concept carries multiple prefLabels (same slug, different spellings) \u2014 settle on one spelling";
349
+ function untaggedPrefLabelFindings(store) {
350
+ const prefLabel = `${NS.skos}prefLabel`;
351
+ const untagged = /* @__PURE__ */ new Map();
352
+ for (const q of store.getQuads(null, DataFactory.namedNode(prefLabel), null, null)) {
353
+ if (q.object.termType !== "Literal" || q.object.language !== "") continue;
354
+ untagged.set(q.subject.value, (untagged.get(q.subject.value) ?? 0) + 1);
355
+ }
356
+ return [...untagged].filter(([, count]) => count > 1).map(
357
+ ([focusNode]) => finding("warning", {
358
+ message: PREF_LABEL_COLLISION,
359
+ focusNode,
360
+ path: prefLabel,
361
+ docs: blameDocs(store, focusNode)
362
+ })
363
+ );
364
+ }
365
+ var MAPPINGS = ["exactMatch", "closeMatch", "broadMatch", "narrowMatch", "relatedMatch"];
366
+ function inSchemeMappingFindings(store) {
367
+ const inScheme = DataFactory.namedNode(`${NS.skos}inScheme`);
368
+ const findings = [];
369
+ for (const mapping of MAPPINGS) {
370
+ const path = `${NS.skos}${mapping}`;
371
+ for (const q of store.getQuads(null, DataFactory.namedNode(path), null, null)) {
372
+ if (q.object.termType !== "NamedNode") continue;
373
+ const schemes = new Set(store.getObjects(q.subject, inScheme, null).map((o) => o.value));
374
+ const shared = store.getObjects(q.object, inScheme, null).some((o) => schemes.has(o.value));
375
+ if (!shared) continue;
376
+ findings.push(
377
+ finding("violation", {
378
+ message: `skos:${mapping} points into this scheme (${compactIri(q.object.value)}), so use broader, narrower or related instead.`,
379
+ focusNode: q.subject.value,
380
+ path,
381
+ docs: blameDocs(store, q.subject.value)
382
+ })
383
+ );
384
+ }
385
+ }
386
+ return findings;
387
+ }
388
+ var CURATED_FIELDS = /* @__PURE__ */ new Set(["sections", "revision-of", "derived-from"]);
389
+ var GRAPH_FILL_FRAGMENT = "#prov.graph-fill.";
390
+ function curatedFieldFindings(store) {
391
+ const filledField = `${NS.graph}filledField`;
392
+ const pathPred = DataFactory.namedNode(`${NS.graph}path`);
393
+ const findings = [];
394
+ for (const q of store.getQuads(null, DataFactory.namedNode(filledField), null, null)) {
395
+ if (q.object.termType !== "Literal") continue;
396
+ const field = q.object.value;
397
+ if (!CURATED_FIELDS.has(field)) continue;
398
+ const node = q.subject.value;
399
+ const at = node.indexOf(GRAPH_FILL_FRAGMENT);
400
+ const fieldAt = node.lastIndexOf(".field.");
401
+ if (at === -1 || fieldAt <= at) continue;
402
+ const activity = node.slice(0, fieldAt);
403
+ const model = node.slice(at + GRAPH_FILL_FRAGMENT.length, fieldAt);
404
+ const docIri = activity.slice(0, activity.indexOf("#"));
405
+ const path = store.getQuads(DataFactory.namedNode(docIri), pathPred, null, null)[0]?.object.value;
406
+ findings.push(
407
+ finding("violation", {
408
+ message: `meta-provenance attributes /graph/${field} to ${model} \u2014 ${field} is curated by hand, never filled by a machine`,
409
+ focusNode: activity,
410
+ path: filledField,
411
+ docs: path === void 0 ? blameDocs(store, activity) : [path]
412
+ })
413
+ );
414
+ }
415
+ return findings;
416
+ }
417
+ function severityOf(iri) {
418
+ if (iri === "http://www.w3.org/ns/shacl#Warning") return "warning";
419
+ if (iri === "http://www.w3.org/ns/shacl#Info") return "info";
420
+ return "violation";
421
+ }
422
+ async function validateGraph(store, shapesPaths) {
423
+ const shapes = loadShapes(shapesPaths);
424
+ const validator = new SHACLValidator(shapes);
425
+ let report;
426
+ try {
427
+ report = await validator.validate(store);
428
+ } catch (e) {
429
+ throw new GraphError(
430
+ `SHACL validation failed: ${errorMessage(e)}`
431
+ );
432
+ }
433
+ const findings = [];
434
+ for (const result of report.results) {
435
+ const focus = result.focusNode?.value ?? "";
436
+ const path = result.path?.value;
437
+ const messages = result.message.map((m) => m.value).filter((m) => m.length > 0);
438
+ const message = messages.length > 0 ? messages.join("; ") : `constraint violated${path ? ` on ${compactIri(path)}` : ""}`;
439
+ findings.push(
440
+ finding(severityOf(result.severity?.value), {
441
+ message,
442
+ focusNode: focus,
443
+ ...path !== void 0 ? { path } : {},
444
+ docs: blameDocs(store, focus)
445
+ })
446
+ );
447
+ }
448
+ const said = new Set(
449
+ findings.filter((f) => f.message === PREF_LABEL_COLLISION).map((f) => f.focusNode)
450
+ );
451
+ findings.push(
452
+ ...cycleFindings(store),
453
+ ...relatedConflicts(store),
454
+ ...curatedFieldFindings(store),
455
+ ...inSchemeMappingFindings(store),
456
+ ...untaggedPrefLabelFindings(store).filter((f) => !said.has(f.focusNode))
457
+ );
458
+ findings.sort(compareFindings);
459
+ return findings;
460
+ }
461
+
462
+ // src/graph/commands/search.ts
463
+ import { createHash } from "node:crypto";
464
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
465
+ import { dirname, join as join2, resolve } from "node:path";
466
+
467
+ // src/graph/core/vector-index.ts
468
+ var MAGIC = new Uint8Array([77, 71, 82, 86]);
469
+ var FORMAT_VERSION = 2;
470
+ var HEADER_OFFSET = 12;
471
+ var VectorIndexError = class extends Error {
472
+ constructor(message) {
473
+ super(message);
474
+ this.name = "VectorIndexError";
475
+ }
476
+ };
477
+ function normalize(vector) {
478
+ let sum = 0;
479
+ for (const value of vector) sum += value * value;
480
+ if (sum === 0) return vector;
481
+ const inv = 1 / Math.sqrt(sum);
482
+ vector.set(vector.map((value) => value * inv));
483
+ return vector;
484
+ }
485
+ function encodeVectorIndex(entries, meta) {
486
+ const sorted = [...entries].sort((a, b) => byCodeUnit(a.id, b.id));
487
+ const dims = sorted[0]?.vector.length ?? 0;
488
+ for (const entry2 of sorted) {
489
+ if (entry2.vector.length !== dims) {
490
+ throw new VectorIndexError(
491
+ `Inconsistent vector dimensions: ${entry2.id} has ${entry2.vector.length}, expected ${dims}.`
492
+ );
493
+ }
494
+ }
495
+ const header = {
496
+ model: meta.model,
497
+ dtype: meta.dtype,
498
+ dims,
499
+ count: sorted.length,
500
+ ids: sorted.map((e) => e.id),
501
+ source: meta.source,
502
+ language: meta.language
503
+ };
504
+ const headerBytes = new TextEncoder().encode(JSON.stringify(header));
505
+ const payloadBytes = sorted.length * dims * 4;
506
+ const out = new Uint8Array(HEADER_OFFSET + headerBytes.length + payloadBytes);
507
+ const view = new DataView(out.buffer);
508
+ out.set(MAGIC, 0);
509
+ view.setUint32(4, FORMAT_VERSION, true);
510
+ view.setUint32(8, headerBytes.length, true);
511
+ out.set(headerBytes, HEADER_OFFSET);
512
+ let offset = HEADER_OFFSET + headerBytes.length;
513
+ for (const entry2 of sorted) {
514
+ const vector = normalize(Float32Array.from(entry2.vector));
515
+ for (const value of vector) {
516
+ view.setFloat32(offset, value, true);
517
+ offset += 4;
518
+ }
519
+ }
520
+ return out;
521
+ }
522
+ function decodeVectorIndex(bytes) {
523
+ if (bytes.length < HEADER_OFFSET) {
524
+ throw new VectorIndexError("Not a manni graph vector index: file is too short.");
525
+ }
526
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
527
+ if (!MAGIC.every((byte, i) => bytes[i] === byte)) {
528
+ throw new VectorIndexError(
529
+ "Not a manni graph vector index: bad magic \u2014 is this the right file?"
530
+ );
531
+ }
532
+ const version = view.getUint32(4, true);
533
+ if (version !== FORMAT_VERSION) {
534
+ throw new VectorIndexError(
535
+ `Unsupported vector index version ${version} (this build reads ${FORMAT_VERSION}) \u2014 re-run \`manni graph embed\`.`
536
+ );
537
+ }
538
+ const headerLength = view.getUint32(8, true);
539
+ if (HEADER_OFFSET + headerLength > bytes.length) {
540
+ throw new VectorIndexError("Vector index is truncated: header overruns.");
541
+ }
542
+ let header;
543
+ try {
544
+ header = JSON.parse(
545
+ new TextDecoder().decode(
546
+ bytes.subarray(HEADER_OFFSET, HEADER_OFFSET + headerLength)
547
+ )
548
+ );
549
+ } catch {
550
+ throw new VectorIndexError("Vector index header is not valid JSON.");
551
+ }
552
+ if (!Array.isArray(header.ids) || typeof header.dims !== "number" || header.ids.length !== header.count) {
553
+ throw new VectorIndexError("Vector index header is malformed.");
554
+ }
555
+ const start = HEADER_OFFSET + headerLength;
556
+ const expected = header.count * header.dims * 4;
557
+ if (bytes.length - start !== expected) {
558
+ throw new VectorIndexError(
559
+ `Vector index is truncated: expected ${expected} payload bytes, found ${bytes.length - start}.`
560
+ );
561
+ }
562
+ const vectors = new Float32Array(header.count * header.dims);
563
+ for (let i = 0; i < vectors.length; i++) {
564
+ vectors[i] = view.getFloat32(start + i * 4, true);
565
+ }
566
+ return { header, vectors };
567
+ }
568
+
569
+ // src/graph/runtime/vector.ts
570
+ var VectorMismatchError = class extends Error {
571
+ reason;
572
+ constructor(mismatch) {
573
+ super(mismatch.detail);
574
+ this.name = "VectorMismatchError";
575
+ this.reason = mismatch.reason;
576
+ }
577
+ };
578
+ async function searchIndexDigest(raw) {
579
+ const webCrypto = globalThis.crypto;
580
+ const subtle = webCrypto?.subtle;
581
+ if (!subtle) {
582
+ throw new Error(
583
+ "searchIndexDigest needs Web Crypto (globalThis.crypto.subtle), which is unavailable here \u2014 a page served over plain HTTP does not get it."
584
+ );
585
+ }
586
+ const digest = await subtle.digest("SHA-256", new TextEncoder().encode(raw));
587
+ const hex = [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
588
+ return `sha256:${hex}`;
589
+ }
590
+ function createVectorIndex(bytes) {
591
+ const { header, vectors } = decodeVectorIndex(bytes);
592
+ const { dims, ids } = header;
593
+ return {
594
+ model: header.model,
595
+ dtype: header.dtype,
596
+ dims,
597
+ source: header.source,
598
+ size: () => ids.length,
599
+ idAt: (row) => ids[row],
600
+ check(expected) {
601
+ if (expected.model !== void 0 && expected.model !== header.model) {
602
+ return {
603
+ reason: "model",
604
+ detail: `Vector index was built with ${header.model}, but the embedder is ${expected.model}. Re-run \`manni graph embed\`.`
605
+ };
606
+ }
607
+ if (expected.dtype !== void 0 && expected.dtype !== header.dtype) {
608
+ return {
609
+ reason: "dtype",
610
+ detail: `Vector index was built at dtype ${header.dtype}, but the embedder is at ${expected.dtype} \u2014 different weights are a different function. Re-run \`manni graph embed\`.`
611
+ };
612
+ }
613
+ if (expected.dims !== void 0 && expected.dims !== dims) {
614
+ return {
615
+ reason: "dims",
616
+ detail: `Vector index has ${dims} dimensions, but the embedder produces ${expected.dims}.`
617
+ };
618
+ }
619
+ if (expected.source !== void 0 && expected.source !== header.source) {
620
+ return {
621
+ reason: "stale-source",
622
+ detail: "Vector index was built from a different search index \u2014 the corpus changed. Re-run `manni graph embed`."
623
+ };
624
+ }
625
+ return void 0;
626
+ },
627
+ search(query, options = {}) {
628
+ if (dims === 0 || ids.length === 0) return [];
629
+ if (query.length !== dims) {
630
+ throw new VectorMismatchError({
631
+ reason: "dims",
632
+ detail: `Query vector has ${query.length} dimensions, but the index has ${dims}.`
633
+ });
634
+ }
635
+ const limit = normalizeLimit(options.limit);
636
+ if (limit === 0) return [];
637
+ const minScore = options.minScore ?? 0;
638
+ const q = normalize(Float32Array.from(query));
639
+ const scored = [];
640
+ for (const [row, iri] of ids.entries()) {
641
+ const stored = vectors.subarray(row * dims, (row + 1) * dims);
642
+ let score = 0;
643
+ for (const [i, value] of q.entries()) score += value * (stored[i] ?? 0);
644
+ if (score >= minScore) {
645
+ scored.push({ iri, score, via: "vector" });
646
+ }
647
+ }
648
+ scored.sort(
649
+ (a, b) => a.score === b.score ? byCodeUnit(a.iri, b.iri) : b.score - a.score
650
+ );
651
+ return scored.slice(0, limit);
652
+ }
653
+ };
654
+ }
655
+ function normalizeLimit(limit) {
656
+ return typeof limit === "number" && Number.isInteger(limit) && limit >= 0 ? limit : 10;
657
+ }
658
+
659
+ // src/graph/runtime/trace.ts
660
+ function createTrace() {
661
+ return { entry: [], hops: [], exclusions: [], resolutions: [] };
662
+ }
663
+ function reachedNodes(trace) {
664
+ const reached = /* @__PURE__ */ new Set();
665
+ for (const e of trace.entry) reached.add(e.iri);
666
+ for (const h of trace.hops) reached.add(h.to);
667
+ return reached;
668
+ }
669
+
670
+ // src/graph/runtime/entry.ts
671
+ function rrfMerge(rankings, k = 60) {
672
+ const fused = /* @__PURE__ */ new Map();
673
+ for (const ranking of rankings) {
674
+ ranking.forEach((candidate, i) => {
675
+ const current = fused.get(candidate.iri) ?? {
676
+ score: 0,
677
+ vias: /* @__PURE__ */ new Set()
678
+ };
679
+ current.score += 1 / (k + i + 1);
680
+ current.vias.add(candidate.via);
681
+ fused.set(candidate.iri, current);
682
+ });
683
+ }
684
+ return [...fused.entries()].map(([iri, { score, vias }]) => {
685
+ const [only] = vias;
686
+ return {
687
+ iri,
688
+ score,
689
+ via: vias.size === 1 && only !== void 0 ? only : "hybrid"
690
+ };
691
+ }).sort(
692
+ (a, b) => a.score === b.score ? byCodeUnit(a.iri, b.iri) : b.score - a.score
693
+ );
694
+ }
695
+ async function findEntry(query, options) {
696
+ const trace = options.trace ?? createTrace();
697
+ const limit = typeof options.limit === "number" && Number.isInteger(options.limit) && options.limit >= 0 ? options.limit : 10;
698
+ const lexical = options.lexical.search(query, { limit });
699
+ const embedder = options.embedder;
700
+ const embed = embedder ? (q) => embedder.embed(q) : options.embedQuery;
701
+ let vector = [];
702
+ if (options.vectors && embed && query.trim() !== "") {
703
+ const mismatch = options.vectors.check({
704
+ ...options.embedder ? { model: options.embedder.model, dtype: options.embedder.dtype } : {},
705
+ // Staleness is checked whichever way the query is embedded — it is a
706
+ // property of the corpus, not of the embedder, so even the unverified
707
+ // `embedQuery` path gets it when the caller supplies a digest.
708
+ ...options.source === void 0 ? {} : { source: options.source }
709
+ });
710
+ if (mismatch) throw new VectorMismatchError(mismatch);
711
+ const queryVector = await embed(query);
712
+ vector = options.vectors.search(queryVector, { limit });
713
+ }
714
+ const rankings = vector.length > 0 ? [lexical, vector] : [lexical];
715
+ const candidates = rrfMerge(rankings).slice(0, limit);
716
+ for (const candidate of candidates) trace.entry.push(candidate);
717
+ return { candidates, lexical, vector, trace };
718
+ }
719
+
720
+ // src/graph/runtime/lexical.ts
721
+ import MiniSearch from "minisearch";
722
+ function createLexicalIndex(input) {
723
+ const doc = typeof input === "string" ? JSON.parse(input) : input;
724
+ const entries = Array.isArray(doc?.entries) ? doc.entries : [];
725
+ const mini = new MiniSearch({
726
+ fields: ["title", "labels", "description", "text"],
727
+ storeFields: ["id"],
728
+ idField: "id",
729
+ // Titles and labels are the strongest signal that a node *is* the subject;
730
+ // body text is evidence it merely mentions it.
731
+ searchOptions: {
732
+ boost: { title: 3, labels: 2, description: 1.5 },
733
+ prefix: true,
734
+ fuzzy: 0.2
735
+ }
736
+ });
737
+ mini.addAll(entries);
738
+ const byIri = new Map(entries.map((e) => [e.id, e]));
739
+ return {
740
+ size: () => entries.length,
741
+ entry: (iri) => byIri.get(iri),
742
+ search(query, options = {}) {
743
+ const trimmed = query.trim();
744
+ if (trimmed === "") return [];
745
+ const hits = mini.search(trimmed);
746
+ hits.sort(
747
+ (a, b) => a.score === b.score ? byCodeUnit(String(a.id), String(b.id)) : b.score - a.score
748
+ );
749
+ return hits.slice(0, options.limit ?? 10).map((h) => ({ iri: String(h.id), score: h.score, via: "lexical" }));
750
+ }
751
+ };
752
+ }
753
+
754
+ // src/graph/embed/local.ts
755
+ var EmbedderUnavailableError = class extends Error {
756
+ constructor(detail) {
757
+ super(
758
+ `Local embeddings need @huggingface/transformers, which is an optional peer dependency.
759
+ Install it: npm install @huggingface/transformers
760
+ (${detail})`
761
+ );
762
+ this.name = "EmbedderUnavailableError";
763
+ }
764
+ };
765
+ async function loadTransformers(injected) {
766
+ if (injected) return injected;
767
+ try {
768
+ return await import("@huggingface/transformers");
769
+ } catch (e) {
770
+ throw new EmbedderUnavailableError(
771
+ errorMessage(e)
772
+ );
773
+ }
774
+ }
775
+ async function createLocalEmbedder(options = {}) {
776
+ const model = options.model ?? DEFAULT_MODEL;
777
+ const dtype = options.dtype ?? "q8";
778
+ const role = options.role ?? "passage";
779
+ const profile = profileFor(model);
780
+ const transformers = await loadTransformers(options.transformers);
781
+ if (transformers.env?.backends?.onnx?.wasm) {
782
+ transformers.env.backends.onnx.wasm.numThreads = 1;
783
+ }
784
+ const extractor = await transformers.pipeline("feature-extraction", model, {
785
+ // No `device`: its accepted values are disjoint across platforms, so any
786
+ // hardcoded choice throws on one of them (ADR 01025).
787
+ ...options.device === void 0 ? {} : { device: options.device },
788
+ dtype
789
+ });
790
+ let dims = 0;
791
+ return {
792
+ model,
793
+ dtype,
794
+ get dims() {
795
+ return dims;
796
+ },
797
+ async embed(text) {
798
+ const output = await extractor(withPrefix(profile, role, text), {
799
+ pooling: "mean",
800
+ normalize: true
801
+ });
802
+ const data = Float32Array.from(output.data);
803
+ dims = data.length;
804
+ return data;
805
+ }
806
+ };
807
+ }
808
+
809
+ // src/graph/embed/mock.ts
810
+ function fnv1a(text, seed) {
811
+ let hash = (2166136261 ^ seed) >>> 0;
812
+ for (let i = 0; i < text.length; i++) {
813
+ hash = (hash ^ text.charCodeAt(i)) >>> 0;
814
+ hash = hash + ((hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24)) >>> 0;
815
+ }
816
+ return hash >>> 0;
817
+ }
818
+ function createMockEmbedder(options = {}) {
819
+ const dims = options.dims ?? 8;
820
+ const model = options.model ?? "mock";
821
+ const dtype = options.dtype ?? "mock";
822
+ return {
823
+ model,
824
+ dtype,
825
+ dims,
826
+ embed(text) {
827
+ const vector = new Float32Array(dims);
828
+ for (let i = 0; i < dims; i++) {
829
+ vector[i] = fnv1a(text, i) / 4294967296 * 2 - 1;
830
+ }
831
+ return Promise.resolve(vector);
832
+ }
833
+ };
834
+ }
835
+
836
+ // src/graph/commands/search.ts
837
+ var SEARCH_MODES = ["lexical", "hybrid", "vector"];
838
+ function loadSearchIndex(indexPath) {
839
+ const raw = readFileSync3(indexPath, "utf8");
840
+ let parsed;
841
+ try {
842
+ parsed = JSON.parse(raw);
843
+ } catch (e) {
844
+ throw new GraphError(
845
+ `Failed to parse ${indexPath}: ${errorMessage(e)} \u2014 re-run \`manni graph export search\`.`
846
+ );
847
+ }
848
+ const entries = parsed?.entries;
849
+ if (!Array.isArray(entries)) {
850
+ throw new GraphError(
851
+ `Not a manni graph search index: ${indexPath} \u2014 expected an \`entries\` array; re-run \`manni graph export search\`.`
852
+ );
853
+ }
854
+ return {
855
+ doc: parsed,
856
+ // Same recipe `manni graph embed` uses, so the two digests are comparable.
857
+ source: `sha256:${createHash("sha256").update(raw, "utf8").digest("hex")}`
858
+ };
859
+ }
860
+ function resolveLocalization(indexDir, lang) {
861
+ const manifestPath = join2(indexDir, LOCALIZATIONS_FILENAME);
862
+ if (!existsSync2(manifestPath)) {
863
+ throw new GraphError(
864
+ `Localization manifest not found: ${manifestPath} \u2014 run \`manni graph export search\` first.`
865
+ );
866
+ }
867
+ const manifest = parseLocalizations(readFileSync3(manifestPath, "utf8"));
868
+ if (!manifest) {
869
+ throw new GraphError(
870
+ `Not a manni graph localization manifest: ${manifestPath} \u2014 re-run \`manni graph export search\`.`
871
+ );
872
+ }
873
+ const available = manifest.languages.map((l) => l.language);
874
+ if (lang !== void 0) {
875
+ const wanted = canonicalLanguageTag(lang);
876
+ const hit = manifest.languages.find(
877
+ (l) => canonicalLanguageTag(l.language) === wanted
878
+ );
879
+ if (!hit) {
880
+ throw new GraphError(
881
+ `No index for language "${lang}" \u2014 this corpus has ${available.join(", ") || "none"}.`
882
+ );
883
+ }
884
+ return hit;
885
+ }
886
+ const only = manifest.languages[0];
887
+ if (!only) {
888
+ throw new GraphError(
889
+ `The localization manifest lists no languages: ${manifestPath} \u2014 re-run \`manni graph export search\`.`
890
+ );
891
+ }
892
+ if (manifest.languages.length > 1) {
893
+ throw new GraphError(
894
+ `This corpus has more than one localization (${available.join(", ")}) \u2014 pass --lang to choose one.`
895
+ );
896
+ }
897
+ return only;
898
+ }
899
+ async function runSearch(opts) {
900
+ const cwd = opts.cwd ?? process.cwd();
901
+ const config = loadRunConfig(
902
+ {
903
+ ...opts.config === void 0 ? {} : { configPath: opts.config },
904
+ ...opts.noConfig === void 0 ? {} : { noConfig: opts.noConfig }
905
+ },
906
+ cwd
907
+ );
908
+ const graphPath = resolve(cwd, opts.graph ?? config.out);
909
+ const indexDir = opts.index ? resolve(cwd, opts.index) : dirname(graphPath);
910
+ const localization = resolveLocalization(indexDir, opts.lang);
911
+ const indexPath = join2(indexDir, localization.search.path);
912
+ if (!existsSync2(indexPath)) {
913
+ throw new GraphError(
914
+ `Search index not found: ${indexPath} \u2014 the manifest names it; re-run \`manni graph export search\`.`
915
+ );
916
+ }
917
+ const { doc, source } = loadSearchIndex(indexPath);
918
+ if (localization.search.digest !== source) {
919
+ throw new GraphError(
920
+ `Stale manifest: ${localization.search.path} does not match the digest recorded for "${localization.language}". Re-run \`manni graph export search\`.`
921
+ );
922
+ }
923
+ const lexical = createLexicalIndex(doc);
924
+ const explicitVectors = opts.vectors !== void 0 && opts.vectors !== "" ? opts.vectors : void 0;
925
+ const wantsVector = opts.mode === "vector" || opts.mode === "hybrid" || explicitVectors !== void 0 && opts.mode !== "lexical";
926
+ const vectorsPath = explicitVectors ? resolve(cwd, explicitVectors) : localization.vectors ? resolve(indexDir, localization.vectors.path) : void 0;
927
+ let vectors;
928
+ if (opts.mode !== "lexical" && vectorsPath !== void 0 && existsSync2(vectorsPath)) {
929
+ try {
930
+ vectors = createVectorIndex(readFileSync3(vectorsPath));
931
+ } catch (e) {
932
+ if (e instanceof VectorIndexError) {
933
+ throw new GraphError(
934
+ `${e.message} (${vectorsPath}) \u2014 re-run \`manni graph embed\`, or use \`--mode lexical\`.`
935
+ );
936
+ }
937
+ throw e;
938
+ }
939
+ const stale = vectors.check({ source });
940
+ if (stale && wantsVector) throw new GraphError(stale.detail);
941
+ if (stale) {
942
+ warn(`${stale.detail} Falling back to lexical.`);
943
+ vectors = void 0;
944
+ }
945
+ } else if (wantsVector) {
946
+ const named = vectorsPath ?? `no sidecar recorded for "${localization.language}"`;
947
+ throw new GraphError(
948
+ `Vector index not found: ${named} \u2014 run \`manni graph embed\` first, or use \`--mode lexical\`.`
949
+ );
950
+ }
951
+ let embedder;
952
+ if (vectors) {
953
+ const requireEmbedder = opts.mode === "vector" || opts.mode === "hybrid";
954
+ embedder = await resolveEmbedder(opts.embedder, vectors, requireEmbedder);
955
+ }
956
+ const entry2 = await findEntry(opts.query, {
957
+ lexical,
958
+ // Pass the embedder, not a bare function: findEntry then verifies it
959
+ // against the index itself rather than trusting the CLI to have done so.
960
+ ...embedder && vectors ? { vectors, embedder } : {},
961
+ limit: opts.limit
962
+ }).catch((e) => {
963
+ if (e instanceof VectorMismatchError) {
964
+ throw new GraphError(`${e.message} Re-run \`manni graph embed\`.`);
965
+ }
966
+ throw e;
967
+ });
968
+ const decorate = (c) => {
969
+ const found = lexical.entry(c.iri);
970
+ return {
971
+ iri: c.iri,
972
+ score: c.score,
973
+ via: c.via,
974
+ ...found?.type === void 0 ? {} : { type: found.type },
975
+ ...found?.title === void 0 ? {} : { title: found.title }
976
+ };
977
+ };
978
+ const mode = opts.mode ?? (embedder && vectors ? "hybrid" : "lexical");
979
+ const results = opts.mode === "vector" ? entry2.vector : entry2.candidates;
980
+ if (opts.mode === "vector") {
981
+ entry2.trace.entry.length = 0;
982
+ entry2.trace.entry.push(...entry2.vector);
983
+ }
984
+ return {
985
+ query: opts.query,
986
+ mode,
987
+ results: results.map(decorate),
988
+ lexical: entry2.lexical.map(decorate),
989
+ vector: entry2.vector.map(decorate),
990
+ trace: entry2.trace
991
+ };
992
+ }
993
+ async function resolveEmbedder(override, vectors, required) {
994
+ const { model, dtype } = vectors;
995
+ try {
996
+ const embedder = override ?? (model === "mock" ? createMockEmbedder({ dtype }) : await createLocalEmbedder({ model, dtype, role: "query" }));
997
+ const mismatch = vectors.check({
998
+ model: embedder.model,
999
+ dtype: embedder.dtype,
1000
+ // Defense in depth: a future model that changes output size without
1001
+ // changing its id would otherwise only surface deep inside search().
1002
+ ...embedder.dims > 0 ? { dims: embedder.dims } : {}
1003
+ });
1004
+ if (mismatch) throw new GraphError(mismatch.detail);
1005
+ return embedder;
1006
+ } catch (e) {
1007
+ if (e instanceof GraphError) throw e;
1008
+ if (e instanceof EmbedderUnavailableError) {
1009
+ if (required) throw new GraphError(e.message);
1010
+ return void 0;
1011
+ }
1012
+ throw new GraphError(
1013
+ `Failed to load embedding model ${model}: ${errorMessage(e)}`
1014
+ );
1015
+ }
1016
+ }
1017
+ function renderSearch(report, format) {
1018
+ if (format === "json") return JSON.stringify(report, null, 2);
1019
+ const lines = [];
1020
+ for (const r of report.results) {
1021
+ const label = r.title ? ` \u2014 ${r.title}` : "";
1022
+ const type = r.type ? ` [${r.type}]` : "";
1023
+ lines.push(`${r.score.toFixed(4)} ${compactIri(r.iri)}${type}${label}`);
1024
+ }
1025
+ if (report.results.length === 0) {
1026
+ lines.push("(no matches)");
1027
+ }
1028
+ lines.push("");
1029
+ lines.push(
1030
+ `${report.results.length} result${report.results.length === 1 ? "" : "s"} (${report.mode})`
1031
+ );
1032
+ return lines.join("\n");
1033
+ }
1034
+
1035
+ // src/graph/commands/questions.ts
1036
+ import { createHash as createHash2 } from "node:crypto";
1037
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
1038
+ import { join as join3, resolve as resolve3 } from "node:path";
1039
+ import GithubSlugger from "github-slugger";
1040
+
1041
+ // src/graph/core/search-index.ts
1042
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
1043
+ import { resolve as resolve2 } from "node:path";
1044
+
1045
+ // src/graph/runtime/resolve.ts
1046
+ var GRAPH_PATH = `${NS.graph}path`;
1047
+ var DCTERMS_TITLE = `${NS.dcterms}title`;
1048
+ var DCTERMS_HAS_PART = `${NS.dcterms}hasPart`;
1049
+ var GRAPH_LEVEL = `${NS.graph}level`;
1050
+ var GRAPH_ORDER = `${NS.graph}order`;
1051
+ function splitFragment(iri) {
1052
+ const hash = iri.indexOf("#");
1053
+ return hash < 0 ? { doc: iri } : { doc: iri.slice(0, hash), fragment: iri.slice(hash + 1) };
1054
+ }
1055
+ function fencedLines(lines) {
1056
+ const mask = new Array(lines.length).fill(false);
1057
+ let open;
1058
+ for (const [i, line] of lines.entries()) {
1059
+ const m = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
1060
+ const delimiter = m?.[1];
1061
+ const info = m?.[2];
1062
+ const isFence = delimiter !== void 0 && info !== void 0;
1063
+ if (open === void 0) {
1064
+ if (isFence && !(delimiter.startsWith("`") && info.includes("`"))) {
1065
+ open = delimiter;
1066
+ mask[i] = true;
1067
+ }
1068
+ continue;
1069
+ }
1070
+ mask[i] = true;
1071
+ if (isFence && delimiter[0] === open[0] && delimiter.length >= open.length && info.trim() === "") {
1072
+ open = void 0;
1073
+ }
1074
+ }
1075
+ return mask;
1076
+ }
1077
+ var ATX = /^ {0,3}(#{1,6})(?:[ \t]+(.*?))?[ \t]*$/;
1078
+ var SETEXT_UNDERLINE = /^ {0,3}(=+|-+)[ \t]*$/;
1079
+ function isParagraphLine(line) {
1080
+ if (line.trim() === "") return false;
1081
+ if (ATX.test(line)) return false;
1082
+ if (/^ {0,3}([-+*]|\d{1,9}[.)])([ \t]|$)/.test(line)) return false;
1083
+ if (/^ {0,3}>/.test(line)) return false;
1084
+ if (/^ {0,3}([-*_])([ \t]*\1){2,}[ \t]*$/.test(line)) return false;
1085
+ return !/^ {4}/.test(line);
1086
+ }
1087
+ function frontmatterLength(lines) {
1088
+ if (lines[0]?.trim() !== "---") return 0;
1089
+ for (let i = 1; i < lines.length; i++) {
1090
+ const line = lines[i]?.trim();
1091
+ if (line === "---" || line === "...") return i + 1;
1092
+ }
1093
+ return 0;
1094
+ }
1095
+ function sourceHeadings(lines) {
1096
+ const fenced = fencedLines(lines);
1097
+ const skip = frontmatterLength(lines);
1098
+ const headings = [];
1099
+ let paragraph = -1;
1100
+ for (const [i, line] of lines.entries()) {
1101
+ if (i < skip || fenced[i]) {
1102
+ paragraph = -1;
1103
+ continue;
1104
+ }
1105
+ const atx = ATX.exec(line);
1106
+ const hashes = atx?.[1];
1107
+ if (hashes !== void 0) {
1108
+ const text = (atx?.[2] ?? "").replace(/(^|[ \t]+)#+$/, "");
1109
+ headings.push({ start: i, level: hashes.length, text });
1110
+ paragraph = -1;
1111
+ continue;
1112
+ }
1113
+ const underline = SETEXT_UNDERLINE.exec(line)?.[1];
1114
+ if (underline !== void 0 && paragraph >= 0) {
1115
+ headings.push({
1116
+ start: paragraph,
1117
+ level: underline.startsWith("=") ? 1 : 2,
1118
+ text: lines.slice(paragraph, i).join("\n")
1119
+ });
1120
+ paragraph = -1;
1121
+ continue;
1122
+ }
1123
+ if (isParagraphLine(line)) {
1124
+ if (paragraph < 0) paragraph = i;
1125
+ } else {
1126
+ paragraph = -1;
1127
+ }
1128
+ }
1129
+ return headings;
1130
+ }
1131
+ var ENTITIES = {
1132
+ amp: "&",
1133
+ lt: "<",
1134
+ gt: ">",
1135
+ quot: '"',
1136
+ apos: "'",
1137
+ nbsp: "\xA0"
1138
+ };
1139
+ function decodeEntities(text) {
1140
+ return text.replace(
1141
+ /&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/gi,
1142
+ (whole, name) => {
1143
+ if (name.startsWith("#")) {
1144
+ const hex = name[1] === "x" || name[1] === "X";
1145
+ const code = Number.parseInt(name.slice(hex ? 2 : 1), hex ? 16 : 10);
1146
+ return Number.isNaN(code) || code > 1114111 ? whole : String.fromCodePoint(code);
1147
+ }
1148
+ return ENTITIES[name.toLowerCase()] ?? whole;
1149
+ }
1150
+ );
1151
+ }
1152
+ function headingKey(text) {
1153
+ return text.replace(/[`*_~\\[\]]/g, "").replace(/\s+/g, " ").trim().toLowerCase();
1154
+ }
1155
+ function stripInline(raw) {
1156
+ return decodeEntities(
1157
+ raw.replace(/!?\[([^\]]*)\]\((?:[^()]|\([^()]*\))*\)/g, "$1").replace(/!?\[([^\]]*)\]\[[^\]]*\]/g, "$1").replace(/<((?:https?|mailto):[^>\s]+)>/gi, "$1")
1158
+ );
1159
+ }
1160
+ function documentSectionOrder(graph, docIri) {
1161
+ const ordered = [];
1162
+ const seen = /* @__PURE__ */ new Set();
1163
+ const childrenOf = (iri) => graph.out(iri, DCTERMS_HAS_PART).map((e) => e.target).sort((a, b) => {
1164
+ const oa = Number.parseInt(graph.literal(a, GRAPH_ORDER) ?? "", 10);
1165
+ const ob = Number.parseInt(graph.literal(b, GRAPH_ORDER) ?? "", 10);
1166
+ if (Number.isNaN(oa) || Number.isNaN(ob) || oa === ob) {
1167
+ return a < b ? -1 : a > b ? 1 : 0;
1168
+ }
1169
+ return oa - ob;
1170
+ });
1171
+ const walk = (iri) => {
1172
+ for (const child of childrenOf(iri)) {
1173
+ if (seen.has(child)) continue;
1174
+ seen.add(child);
1175
+ ordered.push(child);
1176
+ walk(child);
1177
+ }
1178
+ };
1179
+ walk(docIri);
1180
+ return ordered;
1181
+ }
1182
+ function sectionOccurrences(graph, docIri) {
1183
+ const occurrences = /* @__PURE__ */ new Map();
1184
+ const counts = /* @__PURE__ */ new Map();
1185
+ for (const section of documentSectionOrder(graph, docIri)) {
1186
+ const rawTitle = graph.literal(section, DCTERMS_TITLE);
1187
+ if (rawTitle === void 0) {
1188
+ occurrences.set(section, 0);
1189
+ continue;
1190
+ }
1191
+ const title = headingKey(rawTitle);
1192
+ const level = graph.literal(section, GRAPH_LEVEL) ?? "";
1193
+ let byLevel = counts.get(title);
1194
+ if (!byLevel) {
1195
+ byLevel = /* @__PURE__ */ new Map();
1196
+ counts.set(title, byLevel);
1197
+ }
1198
+ const seen = byLevel.get(level) ?? 0;
1199
+ occurrences.set(section, seen);
1200
+ byLevel.set(level, seen + 1);
1201
+ }
1202
+ return occurrences;
1203
+ }
1204
+ function sectionOccurrence(graph, sectionIri) {
1205
+ const { doc } = splitFragment(sectionIri);
1206
+ return sectionOccurrences(graph, doc).get(sectionIri) ?? 0;
1207
+ }
1208
+ function documentPreamble(markdown) {
1209
+ const lines = markdown.split(/\r?\n/);
1210
+ const end = sourceHeadings(lines)[0]?.start ?? lines.length;
1211
+ const text = lines.slice(0, end).join("\n").trim();
1212
+ return text === "" ? void 0 : text;
1213
+ }
1214
+ function sliceSection(markdown, title, level, occurrence = 0) {
1215
+ return slice(markdown, title, level, occurrence, false);
1216
+ }
1217
+ function sectionOwnText(markdown, title, level, occurrence = 0) {
1218
+ return slice(markdown, title, level, occurrence, true);
1219
+ }
1220
+ function sectionOwnLines(markdown, title, level, occurrence = 0) {
1221
+ const span = locate(markdown.split(/\r?\n/), title, level, occurrence, true);
1222
+ return span === void 0 ? void 0 : { start: span.start + 1, end: span.end };
1223
+ }
1224
+ function slice(markdown, title, level, occurrence, ownTextOnly) {
1225
+ const lines = markdown.split(/\r?\n/);
1226
+ const span = locate(lines, title, level, occurrence, ownTextOnly);
1227
+ if (span === void 0) return void 0;
1228
+ return lines.slice(span.start, span.end).join("\n").trimEnd();
1229
+ }
1230
+ function locate(lines, title, level, occurrence, ownTextOnly) {
1231
+ const headings = sourceHeadings(lines);
1232
+ const wanted = headingKey(title);
1233
+ let remaining = occurrence;
1234
+ let found = -1;
1235
+ for (const [n, heading] of headings.entries()) {
1236
+ if (level !== void 0 && heading.level !== level) continue;
1237
+ if (headingKey(stripInline(heading.text)) !== wanted) continue;
1238
+ if (remaining > 0) {
1239
+ remaining -= 1;
1240
+ continue;
1241
+ }
1242
+ found = n;
1243
+ break;
1244
+ }
1245
+ const match = headings[found];
1246
+ if (match === void 0) return void 0;
1247
+ const next = headings.slice(found + 1).find((h) => ownTextOnly || h.level <= match.level);
1248
+ return { start: match.start, end: next?.start ?? lines.length };
1249
+ }
1250
+ function createFetchResolver(graph, options = {}) {
1251
+ const doFetch = options.fetch ?? ((url) => globalThis.fetch(url));
1252
+ const toUrl = options.pathToUrl ?? ((path) => `${options.baseUrl ?? ""}${path}`);
1253
+ const cache = /* @__PURE__ */ new Map();
1254
+ const occurrenceCache = /* @__PURE__ */ new Map();
1255
+ const occurrenceOf = (docIri, sectionIri) => {
1256
+ let map = occurrenceCache.get(docIri);
1257
+ if (!map) {
1258
+ map = sectionOccurrences(graph, docIri);
1259
+ occurrenceCache.set(docIri, map);
1260
+ }
1261
+ return map.get(sectionIri) ?? 0;
1262
+ };
1263
+ const fetchDoc = (url) => {
1264
+ let pending = cache.get(url);
1265
+ if (!pending) {
1266
+ pending = doFetch(url).then((r) => r.ok ? r.text() : void 0).catch(() => void 0);
1267
+ cache.set(url, pending);
1268
+ }
1269
+ return pending;
1270
+ };
1271
+ return {
1272
+ async resolve(iri) {
1273
+ const { doc, fragment } = splitFragment(iri);
1274
+ const path = graph.literal(doc, GRAPH_PATH);
1275
+ if (!path) return void 0;
1276
+ const sourceUrl = toUrl(path);
1277
+ const body = await fetchDoc(sourceUrl);
1278
+ const title = graph.literal(iri, DCTERMS_TITLE);
1279
+ if (body === void 0) {
1280
+ options.trace?.resolutions.push({
1281
+ iri,
1282
+ sourceUrl,
1283
+ ok: false,
1284
+ error: "fetch failed"
1285
+ });
1286
+ return void 0;
1287
+ }
1288
+ let text = body;
1289
+ if (fragment) {
1290
+ const levelText = graph.literal(iri, GRAPH_LEVEL);
1291
+ const level = levelText ? Number.parseInt(levelText, 10) : void 0;
1292
+ const slice2 = title === void 0 ? void 0 : sliceSection(
1293
+ body,
1294
+ title,
1295
+ Number.isNaN(level) ? void 0 : level,
1296
+ occurrenceOf(doc, iri)
1297
+ );
1298
+ if (slice2 === void 0) {
1299
+ options.trace?.resolutions.push({
1300
+ iri,
1301
+ sourceUrl,
1302
+ ok: false,
1303
+ error: "section heading not found"
1304
+ });
1305
+ return void 0;
1306
+ }
1307
+ text = slice2;
1308
+ }
1309
+ options.trace?.resolutions.push({ iri, sourceUrl, ok: true });
1310
+ return { iri, text, sourceUrl, title };
1311
+ }
1312
+ };
1313
+ }
1314
+
1315
+ // src/graph/core/search-index.ts
1316
+ var GRAPH_DOCUMENT = `${NS.graph}Document`;
1317
+ var GRAPH_SECTION = `${NS.graph}Section`;
1318
+ var SKOS_CONCEPT = `${NS.skos}Concept`;
1319
+ var GRAPH_PATH2 = `${NS.graph}path`;
1320
+ var GRAPH_LEVEL2 = `${NS.graph}level`;
1321
+ var DCTERMS_TITLE2 = `${NS.dcterms}title`;
1322
+ var DCTERMS_DESCRIPTION = `${NS.dcterms}description`;
1323
+ var SKOS_PREF_LABEL = `${NS.skos}prefLabel`;
1324
+ var SKOS_ALT_LABEL = `${NS.skos}altLabel`;
1325
+ var DCTERMS_LANGUAGE = `${NS.dcterms}language`;
1326
+ function joinLabels(values) {
1327
+ const seen = /* @__PURE__ */ new Map();
1328
+ for (const v of values) {
1329
+ const key = v.trim().toLowerCase();
1330
+ if (key && !seen.has(key)) seen.set(key, v.trim());
1331
+ }
1332
+ const out = [...seen.values()].sort(byCodeUnit);
1333
+ return out.length > 0 ? out.join(" ") : void 0;
1334
+ }
1335
+ var FRONTMATTER = /^---[ \t]*\r?\n(?:[\s\S]*?\r?\n)?(?:---|\.\.\.)[ \t]*(?:\r?\n|$)(?:\r?\n)*/;
1336
+ function stripFrontmatter(markdown) {
1337
+ return markdown.replace(FRONTMATTER, "");
1338
+ }
1339
+ function plainText(nodes) {
1340
+ return nodes.map((n) => n.text.replace(/\r\n/g, "\n").trim()).filter((t) => t !== "");
1341
+ }
1342
+ function treeText(source, path) {
1343
+ const format = formatOf(path);
1344
+ if (format === "markdown" || format === "mdx") return void 0;
1345
+ const minted = mintTreeSections(parseTree(source, path, format).sections);
1346
+ const sections = /* @__PURE__ */ new Map();
1347
+ for (const [n, section] of minted.sections.entries()) {
1348
+ const own = plainText(minted.owned[n] ?? []);
1349
+ sections.set(section.slug, [section.title, ...own].join("\n\n"));
1350
+ }
1351
+ const lead = plainText(minted.unowned);
1352
+ return { doc: lead.length > 0 ? lead.join("\n\n") : void 0, sections };
1353
+ }
1354
+ function entry(id, type, fields) {
1355
+ const out = { id, type };
1356
+ if (fields.title !== void 0) out.title = fields.title;
1357
+ if (fields.labels !== void 0) out.labels = fields.labels;
1358
+ if (fields.description !== void 0) out.description = fields.description;
1359
+ if (fields.text !== void 0) out.text = fields.text;
1360
+ return out;
1361
+ }
1362
+ function buildSearchIndex(graph, cwd, options = {}) {
1363
+ const warnings = options.warnings ?? [];
1364
+ const readDoc = options.readDoc ?? ((path) => {
1365
+ const abs = resolve2(cwd, path);
1366
+ return existsSync3(abs) ? readFileSync4(abs, "utf8") : void 0;
1367
+ });
1368
+ const entries = [];
1369
+ const sourceOf = /* @__PURE__ */ new Map();
1370
+ const treeTextOf = /* @__PURE__ */ new Map();
1371
+ const allSections = graph.instancesOf(GRAPH_SECTION);
1372
+ const sectionsOf = /* @__PURE__ */ new Map();
1373
+ for (const section of allSections) {
1374
+ const hash = section.indexOf("#");
1375
+ if (hash < 0) continue;
1376
+ const doc = section.slice(0, hash);
1377
+ const siblings = sectionsOf.get(doc);
1378
+ if (siblings) siblings.push(section);
1379
+ else sectionsOf.set(doc, [section]);
1380
+ }
1381
+ for (const doc of graph.instancesOf(GRAPH_DOCUMENT)) {
1382
+ const path = graph.literal(doc, GRAPH_PATH2);
1383
+ const source = path === void 0 ? void 0 : readDoc(path);
1384
+ sourceOf.set(doc, source);
1385
+ if (path !== void 0 && source === void 0) {
1386
+ warnings.push(
1387
+ `Source not found for ${path} \u2014 indexed without body text.`
1388
+ );
1389
+ }
1390
+ const tree = source === void 0 || path === void 0 ? void 0 : treeText(source, path);
1391
+ if (tree) treeTextOf.set(doc, tree);
1392
+ const body = source === void 0 ? void 0 : stripFrontmatter(source).replace(/\r\n/g, "\n");
1393
+ const text = tree ? tree.doc : body === void 0 ? void 0 : (sectionsOf.get(doc)?.length ?? 0) > 0 ? documentPreamble(body) : body;
1394
+ entries.push(
1395
+ entry(doc, compactIri(GRAPH_DOCUMENT), {
1396
+ title: graph.literal(doc, DCTERMS_TITLE2),
1397
+ description: graph.literal(doc, DCTERMS_DESCRIPTION),
1398
+ text
1399
+ })
1400
+ );
1401
+ }
1402
+ const occurrencesOf = /* @__PURE__ */ new Map();
1403
+ for (const section of allSections) {
1404
+ const hash = section.indexOf("#");
1405
+ const doc = hash < 0 ? section : section.slice(0, hash);
1406
+ const title = graph.literal(section, DCTERMS_TITLE2);
1407
+ const source = sourceOf.get(doc);
1408
+ const levelText = graph.literal(section, GRAPH_LEVEL2);
1409
+ const parsed = levelText === void 0 ? NaN : Number.parseInt(levelText, 10);
1410
+ const level = Number.isNaN(parsed) ? void 0 : parsed;
1411
+ let occurrences = occurrencesOf.get(doc);
1412
+ if (!occurrences) {
1413
+ occurrences = sectionOccurrences(graph, doc);
1414
+ occurrencesOf.set(doc, occurrences);
1415
+ }
1416
+ const tree = treeTextOf.get(doc);
1417
+ const text = tree ? tree.sections.get(section.slice(hash + 1)) : source !== void 0 && title !== void 0 ? sectionOwnText(source, title, level, occurrences.get(section) ?? 0) : void 0;
1418
+ entries.push(entry(section, compactIri(GRAPH_SECTION), { title, text }));
1419
+ }
1420
+ for (const concept of graph.instancesOf(SKOS_CONCEPT)) {
1421
+ const pref = graph.literals(concept, SKOS_PREF_LABEL);
1422
+ const alt = graph.literals(concept, SKOS_ALT_LABEL);
1423
+ entries.push(
1424
+ entry(concept, compactIri(SKOS_CONCEPT), {
1425
+ title: pref[0],
1426
+ labels: joinLabels([...pref, ...alt])
1427
+ })
1428
+ );
1429
+ }
1430
+ entries.sort((a, b) => byCodeUnit(a.id, b.id));
1431
+ return { version: 1, entries };
1432
+ }
1433
+ function emitSearchIndex(doc) {
1434
+ return `${JSON.stringify(doc, null, 2)}
1435
+ `;
1436
+ }
1437
+ function partitionByLanguage(graph, index) {
1438
+ const documentType = compactIri(GRAPH_DOCUMENT);
1439
+ const sectionType = compactIri(GRAPH_SECTION);
1440
+ const languageOf = (entry2) => {
1441
+ if (entry2.type !== documentType && entry2.type !== sectionType) {
1442
+ return void 0;
1443
+ }
1444
+ const hash = entry2.id.indexOf("#");
1445
+ const doc = hash === -1 ? entry2.id : entry2.id.slice(0, hash);
1446
+ return languageOfDocument(graph, doc);
1447
+ };
1448
+ const out = /* @__PURE__ */ new Map();
1449
+ const shared = [];
1450
+ for (const entry2 of index.entries) {
1451
+ const language = languageOf(entry2);
1452
+ if (language === void 0) {
1453
+ shared.push(entry2);
1454
+ continue;
1455
+ }
1456
+ const bucket = out.get(language);
1457
+ if (bucket) bucket.entries.push(entry2);
1458
+ else out.set(language, { version: 1, entries: [entry2] });
1459
+ }
1460
+ if (out.size === 0 && shared.length > 0) {
1461
+ out.set(UNDETERMINED, { version: 1, entries: [] });
1462
+ }
1463
+ for (const bucket of out.values()) {
1464
+ bucket.entries.push(...shared);
1465
+ bucket.entries.sort((a, b) => byCodeUnit(a.id, b.id));
1466
+ }
1467
+ return out;
1468
+ }
1469
+ function languageOfDocument(graph, doc) {
1470
+ const tag = graph.literal(doc, DCTERMS_LANGUAGE);
1471
+ return tag === void 0 ? UNDETERMINED : canonicalLanguageTag(tag);
1472
+ }
1473
+ function documentsByLanguage(graph) {
1474
+ const out = /* @__PURE__ */ new Map();
1475
+ for (const doc of graph.instancesOf(GRAPH_DOCUMENT)) {
1476
+ const language = languageOfDocument(graph, doc);
1477
+ out.set(language, (out.get(language) ?? 0) + 1);
1478
+ }
1479
+ return out;
1480
+ }
1481
+
1482
+ // src/graph/runtime/graph.ts
1483
+ var XSD_STRING2 = "http://www.w3.org/2001/XMLSchema#string";
1484
+ function compareValues(a, b) {
1485
+ if (a.kind !== b.kind) return a.kind === "iri" ? -1 : 1;
1486
+ const v = byCodeUnit(a.value, b.value);
1487
+ if (v !== 0) return v;
1488
+ const da = a.kind === "literal" ? a.datatype ?? "" : "";
1489
+ const db = b.kind === "literal" ? b.datatype ?? "" : "";
1490
+ const la = a.kind === "literal" ? a.language ?? "" : "";
1491
+ const lb = b.kind === "literal" ? b.language ?? "" : "";
1492
+ return byCodeUnit(da, db) || byCodeUnit(la, lb);
1493
+ }
1494
+ var GraphIndex = class _GraphIndex {
1495
+ nodes = /* @__PURE__ */ new Map();
1496
+ /** subject → predicate → object IRIs (sorted). */
1497
+ outbound = /* @__PURE__ */ new Map();
1498
+ /** object → predicate → subject IRIs (sorted). */
1499
+ inbound = /* @__PURE__ */ new Map();
1500
+ /** class IRI → instance IRIs (sorted). */
1501
+ byType = /* @__PURE__ */ new Map();
1502
+ /** prefix → namespace, from the artifact's own `@context`. */
1503
+ context;
1504
+ constructor(context) {
1505
+ this.context = context;
1506
+ }
1507
+ /**
1508
+ * Build from a `graph.jsonld` document — a JSON string or the parsed object.
1509
+ * CURIE keys and `@type` values are expanded using the document's own
1510
+ * `@context`, so the index is self-describing.
1511
+ */
1512
+ static fromJsonLd(input) {
1513
+ const doc = typeof input === "string" ? JSON.parse(input) : input;
1514
+ const context = doc["@context"] ?? {};
1515
+ const index = new _GraphIndex({ ...context });
1516
+ for (const raw of doc["@graph"] ?? []) {
1517
+ index.addJsonLdNode(raw);
1518
+ }
1519
+ index.finalize();
1520
+ return index;
1521
+ }
1522
+ /** Build from dockg's internal quad shape (the Node/CLI path). */
1523
+ static fromQuads(quads, context = {}) {
1524
+ const index = new _GraphIndex({ ...context });
1525
+ for (const q of quads) {
1526
+ const o = q.o;
1527
+ const value = o.kind === "iri" ? { kind: "iri", value: o.value } : o.language ? { kind: "literal", value: o.value, language: o.language } : o.datatype && o.datatype !== XSD_STRING2 ? { kind: "literal", value: o.value, datatype: o.datatype } : { kind: "literal", value: o.value };
1528
+ index.addTriple(q.s, q.p, value);
1529
+ }
1530
+ index.finalize();
1531
+ return index;
1532
+ }
1533
+ /** Expand a CURIE (`dcterms:title`) to a full IRI using `@context`. */
1534
+ expand(term) {
1535
+ const colon = term.indexOf(":");
1536
+ if (colon <= 0) return term;
1537
+ const ns = this.context[term.slice(0, colon)];
1538
+ return ns ? `${ns}${term.slice(colon + 1)}` : term;
1539
+ }
1540
+ /** Compact a full IRI to a CURIE when a context namespace matches. */
1541
+ compact(iri) {
1542
+ let best = iri;
1543
+ let bestNs = "";
1544
+ for (const [prefix, ns] of Object.entries(this.context)) {
1545
+ if (iri.startsWith(ns) && iri.length > ns.length && ns.length > bestNs.length) {
1546
+ best = `${prefix}:${iri.slice(ns.length)}`;
1547
+ bestNs = ns;
1548
+ }
1549
+ }
1550
+ return best;
1551
+ }
1552
+ addJsonLdNode(raw) {
1553
+ const id = typeof raw["@id"] === "string" ? this.expand(raw["@id"]) : "";
1554
+ if (!id) return;
1555
+ this.ensureNode(id);
1556
+ const rawTypes = raw["@type"];
1557
+ for (const t of toArray(rawTypes)) {
1558
+ if (typeof t === "string") {
1559
+ this.addTriple(id, RDF_TYPE, { kind: "iri", value: this.expand(t) });
1560
+ }
1561
+ }
1562
+ for (const [key, rawValue] of Object.entries(raw)) {
1563
+ if (key === "@id" || key === "@type") continue;
1564
+ const predicate = this.expand(key);
1565
+ for (const v of toArray(rawValue)) {
1566
+ const value = this.toValue(v);
1567
+ if (value) this.addTriple(id, predicate, value);
1568
+ }
1569
+ }
1570
+ }
1571
+ toValue(v) {
1572
+ if (typeof v === "string") return { kind: "literal", value: v };
1573
+ if (typeof v === "number" || typeof v === "boolean") {
1574
+ return { kind: "literal", value: String(v) };
1575
+ }
1576
+ if (v && typeof v === "object") {
1577
+ const o = v;
1578
+ if (typeof o["@id"] === "string") {
1579
+ return { kind: "iri", value: this.expand(o["@id"]) };
1580
+ }
1581
+ if (o["@value"] !== void 0) {
1582
+ const raw = o["@value"];
1583
+ const lexical = typeof raw === "string" ? raw : JSON.stringify(raw);
1584
+ if (typeof o["@language"] === "string") {
1585
+ return { kind: "literal", value: lexical, language: o["@language"] };
1586
+ }
1587
+ const dt = typeof o["@type"] === "string" ? this.expand(o["@type"]) : void 0;
1588
+ return dt && dt !== XSD_STRING2 ? { kind: "literal", value: lexical, datatype: dt } : { kind: "literal", value: lexical };
1589
+ }
1590
+ }
1591
+ return void 0;
1592
+ }
1593
+ ensureNode(id) {
1594
+ let node = this.nodes.get(id);
1595
+ if (!node) {
1596
+ node = { id, types: [], props: /* @__PURE__ */ new Map() };
1597
+ this.nodes.set(id, node);
1598
+ }
1599
+ return node;
1600
+ }
1601
+ addTriple(s, p, o) {
1602
+ const node = this.ensureNode(s);
1603
+ let values = node.props.get(p);
1604
+ if (!values) {
1605
+ values = [];
1606
+ node.props.set(p, values);
1607
+ }
1608
+ values.push(o);
1609
+ if (p === RDF_TYPE && o.kind === "iri") {
1610
+ node.types.push(o.value);
1611
+ const instances = this.byType.get(o.value);
1612
+ if (instances) instances.push(s);
1613
+ else this.byType.set(o.value, [s]);
1614
+ }
1615
+ if (o.kind === "iri") {
1616
+ addEdge(this.outbound, s, p, o.value);
1617
+ addEdge(this.inbound, o.value, p, s);
1618
+ this.ensureNode(o.value);
1619
+ }
1620
+ }
1621
+ /** Sort every collection so traversal order is deterministic. */
1622
+ finalize() {
1623
+ for (const node of this.nodes.values()) {
1624
+ node.types.sort(byCodeUnit);
1625
+ for (const values of node.props.values()) values.sort(compareValues);
1626
+ }
1627
+ for (const instances of this.byType.values()) instances.sort(byCodeUnit);
1628
+ for (const map of [this.outbound, this.inbound]) {
1629
+ for (const preds of map.values()) {
1630
+ for (const targets of preds.values()) targets.sort(byCodeUnit);
1631
+ }
1632
+ }
1633
+ }
1634
+ /** Every node IRI, sorted. */
1635
+ ids() {
1636
+ return [...this.nodes.keys()].sort(byCodeUnit);
1637
+ }
1638
+ size() {
1639
+ return this.nodes.size;
1640
+ }
1641
+ node(iri) {
1642
+ return this.nodes.get(iri);
1643
+ }
1644
+ has(iri) {
1645
+ return this.nodes.has(iri);
1646
+ }
1647
+ /** Class IRIs of a node, sorted. */
1648
+ types(iri) {
1649
+ return this.nodes.get(iri)?.types ?? [];
1650
+ }
1651
+ /** Instances of a class, sorted. */
1652
+ instancesOf(classIri) {
1653
+ return this.byType.get(classIri) ?? [];
1654
+ }
1655
+ /** All values for a predicate, sorted. */
1656
+ values(iri, predicate) {
1657
+ return this.nodes.get(iri)?.props.get(predicate) ?? [];
1658
+ }
1659
+ /** Literal lexical forms for a predicate, sorted. */
1660
+ literals(iri, predicate) {
1661
+ return this.values(iri, predicate).filter((v) => v.kind === "literal").map((v) => v.value);
1662
+ }
1663
+ /** The first literal for a predicate, if any. */
1664
+ literal(iri, predicate) {
1665
+ return this.literals(iri, predicate)[0];
1666
+ }
1667
+ /** Outbound IRI targets: all predicates, or one. Sorted. */
1668
+ out(iri, predicate) {
1669
+ return edges(this.outbound, iri, predicate);
1670
+ }
1671
+ /** Inbound IRI sources (who points at this node). Sorted. */
1672
+ in(iri, predicate) {
1673
+ return edges(this.inbound, iri, predicate);
1674
+ }
1675
+ };
1676
+ function addEdge(map, key, predicate, target) {
1677
+ let preds = map.get(key);
1678
+ if (!preds) {
1679
+ preds = /* @__PURE__ */ new Map();
1680
+ map.set(key, preds);
1681
+ }
1682
+ const targets = preds.get(predicate);
1683
+ if (targets) targets.push(target);
1684
+ else preds.set(predicate, [target]);
1685
+ }
1686
+ function edges(map, iri, predicate) {
1687
+ const preds = map.get(iri);
1688
+ if (!preds) return [];
1689
+ const out = [];
1690
+ const keys = predicate ? [predicate] : [...preds.keys()].sort(byCodeUnit);
1691
+ for (const p of keys) {
1692
+ for (const target of preds.get(p) ?? []) out.push({ predicate: p, target });
1693
+ }
1694
+ return out;
1695
+ }
1696
+ function toArray(v) {
1697
+ if (v === void 0 || v === null) return [];
1698
+ return Array.isArray(v) ? v : [v];
1699
+ }
1700
+
1701
+ // src/graph/commands/questions.ts
1702
+ var DOCUMENT = `${NS.graph}Document`;
1703
+ var SECTION = `${NS.graph}Section`;
1704
+ var PATH = `${NS.graph}path`;
1705
+ var LEVEL = `${NS.graph}level`;
1706
+ var TITLE = `${NS.dcterms}title`;
1707
+ var LANGUAGE = `${NS.dcterms}language`;
1708
+ var SAMPLE_QUESTION = `${NS.graph}sampleQuestion`;
1709
+ var DOCUMENT_TYPE = compactIri(DOCUMENT);
1710
+ var SECTION_TYPE = compactIri(SECTION);
1711
+ function readManifest(indexDir) {
1712
+ const path = join3(indexDir, LOCALIZATIONS_FILENAME);
1713
+ if (!existsSync4(path)) return [];
1714
+ const manifest = parseLocalizations(readFileSync5(path, "utf8"));
1715
+ if (!manifest) {
1716
+ throw new GraphError(
1717
+ `Not a manni graph localization manifest: ${path} \u2014 re-run \`manni graph export search\`.`
1718
+ );
1719
+ }
1720
+ return manifest.languages;
1721
+ }
1722
+ async function openRetriever(doc, entry2, indexDir) {
1723
+ const lexical = createLexicalIndex(doc);
1724
+ if (!entry2?.vectors) return { lexical };
1725
+ const path = resolve3(indexDir, entry2.vectors.path);
1726
+ if (!existsSync4(path)) return { lexical };
1727
+ let vectors;
1728
+ try {
1729
+ vectors = createVectorIndex(readFileSync5(path));
1730
+ } catch (e) {
1731
+ if (e instanceof VectorIndexError) {
1732
+ throw new GraphError(`${e.message} (${path}) \u2014 re-run \`manni graph embed\`.`);
1733
+ }
1734
+ throw e;
1735
+ }
1736
+ const source = `sha256:${createHash2("sha256").update(emitSearchIndex(doc), "utf8").digest("hex")}`;
1737
+ const stale = vectors.check({ source });
1738
+ if (stale) {
1739
+ warn(`${stale.detail} Falling back to lexical.`);
1740
+ return { lexical };
1741
+ }
1742
+ const embedder = await resolveEmbedder(void 0, vectors, false);
1743
+ return embedder ? { lexical, vectors, embedder } : { lexical };
1744
+ }
1745
+ async function rankNodes(question, r) {
1746
+ const entry2 = await findEntry(question, {
1747
+ lexical: r.lexical,
1748
+ ...r.vectors && r.embedder ? { vectors: r.vectors, embedder: r.embedder } : {},
1749
+ limit: r.lexical.size()
1750
+ }).catch((e) => {
1751
+ if (e instanceof VectorMismatchError) {
1752
+ throw new GraphError(`${e.message} Re-run \`manni graph embed\`.`);
1753
+ }
1754
+ throw e;
1755
+ });
1756
+ return entry2.candidates.flatMap((c) => {
1757
+ const type = r.lexical.entry(c.iri)?.type;
1758
+ return type === DOCUMENT_TYPE || type === SECTION_TYPE ? [c.iri] : [];
1759
+ });
1760
+ }
1761
+ function pageOf(iri) {
1762
+ const hash = iri.indexOf("#");
1763
+ return hash < 0 ? iri : iri.slice(0, hash);
1764
+ }
1765
+ function lastLine(position) {
1766
+ const { start, end } = position;
1767
+ return end.column <= 1 && end.line > start.line ? end.line - 1 : end.line;
1768
+ }
1769
+ function sectionSpans(graph, doc, path, content, sections) {
1770
+ const format = formatOf(path);
1771
+ const out = [];
1772
+ if (isTreeFormat(format)) {
1773
+ const known = new Set(sections);
1774
+ const minted = mintTreeSections(parseTree(content, path, format).sections);
1775
+ for (const [n, section] of minted.sections.entries()) {
1776
+ const iri = `${doc}#${section.slug}`;
1777
+ const node = minted.nodes[n];
1778
+ if (!known.has(iri) || node === void 0) continue;
1779
+ const head = node.titlePosition ?? node.position;
1780
+ const end = Math.max(
1781
+ lastLine(head),
1782
+ ...(minted.owned[n] ?? []).map((c) => lastLine(c.position))
1783
+ );
1784
+ out.push({ iri, span: { start: head.start.line, end } });
1785
+ }
1786
+ return out;
1787
+ }
1788
+ const occurrences = sectionOccurrences(graph, doc);
1789
+ for (const iri of sections) {
1790
+ const title = graph.literal(iri, TITLE);
1791
+ if (title === void 0) continue;
1792
+ const level = Number.parseInt(graph.literal(iri, LEVEL) ?? "", 10);
1793
+ const span = sectionOwnLines(
1794
+ content,
1795
+ title,
1796
+ Number.isNaN(level) ? void 0 : level,
1797
+ occurrences.get(iri) ?? 0
1798
+ );
1799
+ if (span) out.push({ iri, span });
1800
+ }
1801
+ return out;
1802
+ }
1803
+ function bodyOf(content, format) {
1804
+ const bodyLine = format === "html" || format === "xml" ? 1 : bodyLineOf(content, locateFrontmatter(content)?.closeEnd ?? 0);
1805
+ return { bodyLine, body: splitLines(content).slice(bodyLine - 1) };
1806
+ }
1807
+ function freshness(body, pin) {
1808
+ const { lines, integrity } = pin;
1809
+ if (integrity === void 0) {
1810
+ return lines.end <= body.length ? { state: "current", ranges: [lines] } : { state: "changed" };
1811
+ }
1812
+ if (pinOfLines(body, lines) === integrity) return { state: "current", ranges: [lines] };
1813
+ const length = lines.end - lines.start + 1;
1814
+ const { starts } = findWindows(body, length, integrity, void 0, { around: lines.start });
1815
+ const ranges = starts.map((start) => ({ start, end: start + length - 1 }));
1816
+ if (ranges.length === 0) return { state: "changed" };
1817
+ return { state: ranges.length === 1 ? "moved" : "ambiguous", ranges };
1818
+ }
1819
+ function matchList(ranges) {
1820
+ const spelled = ranges.map(spellLines);
1821
+ const head = spelled.slice(0, -1);
1822
+ const last = spelled[spelled.length - 1] ?? "";
1823
+ if (head.length === 0) return `lines ${last} match`;
1824
+ const both = head.length === 1 ? "both" : "all";
1825
+ return `lines ${head.join(", ")} and ${last} ${both} match`;
1826
+ }
1827
+ async function readPages(graph, sources, view, verb) {
1828
+ const contents = /* @__PURE__ */ new Map();
1829
+ const onDisk = [];
1830
+ const byAbs = /* @__PURE__ */ new Map();
1831
+ let missing = 0;
1832
+ for (const doc of graph.instancesOf(DOCUMENT)) {
1833
+ const path = graph.literal(doc, PATH);
1834
+ if (path === void 0 || path === STDIN_PATH) continue;
1835
+ const abs = resolve3(sources, path);
1836
+ if (!existsSync4(abs)) {
1837
+ missing += 1;
1838
+ continue;
1839
+ }
1840
+ contents.set(path, readFileSync5(abs, "utf8"));
1841
+ onDisk.push({ doc, path });
1842
+ byAbs.set(abs, { doc, path });
1843
+ }
1844
+ if (missing > 0) {
1845
+ warn(
1846
+ missing === 1 ? `1 page in the graph is not on disk, so its questions were not ${verb}; run manni graph build.` : `${String(missing)} pages in the graph are not on disk, so their questions were not ${verb}; run manni graph build.`
1847
+ );
1848
+ }
1849
+ const sectionsOf = /* @__PURE__ */ new Map();
1850
+ for (const section of graph.instancesOf(SECTION)) {
1851
+ const doc = pageOf(section);
1852
+ const siblings = sectionsOf.get(doc);
1853
+ if (siblings) siblings.push(section);
1854
+ else sectionsOf.set(doc, [section]);
1855
+ }
1856
+ const records = /* @__PURE__ */ new Map();
1857
+ const record = (doc, path, questions) => {
1858
+ const known = records.get(doc);
1859
+ if (known) return known;
1860
+ const content = contents.get(path) ?? "";
1861
+ const { bodyLine, body } = bodyOf(content, formatOf(path));
1862
+ const tag = graph.literal(doc, LANGUAGE);
1863
+ const page = {
1864
+ doc,
1865
+ path,
1866
+ language: tag === void 0 ? UNDETERMINED : canonicalLanguageTag(tag),
1867
+ bodyLine,
1868
+ body,
1869
+ sections: sectionSpans(graph, doc, path, content, sectionsOf.get(doc) ?? []),
1870
+ questions
1871
+ };
1872
+ records.set(doc, page);
1873
+ return page;
1874
+ };
1875
+ const meta = await openMetaView(view.source, view.cwd, onDisk.map((p) => p.path), sources);
1876
+ const pages = [];
1877
+ for (const { doc, path } of onDisk) {
1878
+ const content = contents.get(path) ?? "";
1879
+ const format = formatOf(path);
1880
+ const own = metadataOf(content, path, format);
1881
+ const { data } = await mergePage(meta, path, own.data, own.present, format);
1882
+ const questions = readSampleQuestions(data["sample-questions"]);
1883
+ if (questions.length > 0) pages.push(record(doc, path, questions));
1884
+ }
1885
+ pages.sort((a, b) => byCodeUnit(a.path, b.path));
1886
+ return {
1887
+ pages,
1888
+ contents,
1889
+ pageAt: (abs) => {
1890
+ const hit = byAbs.get(abs);
1891
+ return hit && record(hit.doc, hit.path, []);
1892
+ }
1893
+ };
1894
+ }
1895
+ function holdersOf(page, lines) {
1896
+ const out = page.sections.filter(({ span }) => span.start <= lines.end && lines.start <= span.end).map(({ iri }) => iri);
1897
+ for (let line = lines.start; line <= lines.end; line++) {
1898
+ if (!page.sections.some(({ span }) => span.start <= line && line <= span.end)) {
1899
+ return [page.doc, ...out];
1900
+ }
1901
+ }
1902
+ return out;
1903
+ }
1904
+ function percent(part, of) {
1905
+ return of === 0 ? 0 : Math.floor(part / of * 100);
1906
+ }
1907
+ function plural(n, word) {
1908
+ return `${String(n)} ${word}${n === 1 ? "" : "s"}`;
1909
+ }
1910
+ function listOf(items) {
1911
+ const head = items.slice(0, -1);
1912
+ const last = items[items.length - 1] ?? "";
1913
+ return head.length === 0 ? last : `${head.join(", ")} and ${last}`;
1914
+ }
1915
+ function renderQuestionSummary(s) {
1916
+ const homes = [
1917
+ ...s.pages > 0 ? [`on ${plural(s.pages, "page")}`] : [],
1918
+ ...s.files.length > 0 ? [`in ${listOf(s.files)}`] : []
1919
+ ];
1920
+ const { reviewed, drafted } = s.found;
1921
+ const a = s.answers;
1922
+ return [
1923
+ `questions ${String(s.total)}: ${String(s.reviewed)} reviewed, ${String(s.drafted)} drafted${homes.length > 0 ? `, ${homes.join(" and ")}` : ""}`,
1924
+ ` found ${String(reviewed.inTop)} of ${String(reviewed.of)} reviewed in the top ${String(s.top)} (${String(percent(reviewed.inTop, reviewed.of))}%), ${String(reviewed.first)} first (${String(percent(reviewed.first, reviewed.of))}%)`,
1925
+ ...drafted.of > 0 ? [` drafted ${String(drafted.inTop)} of ${String(drafted.of)} in the top ${String(s.top)}, not counted until reviewed`] : [],
1926
+ ` answers ${String(a.pinned)} pinned, ${String(a.unreviewed)} not reviewed: ${String(a.current)} current, ${String(a.moved)} moved, ${String(a.changed)} changed; ${String(a.unpinned)} not pinned`,
1927
+ ...s.gaps > 0 ? [` gaps ${plural(s.gaps, "question")} the docs don't answer`] : []
1928
+ ];
1929
+ }
1930
+ async function askSampleQuestions(store, settings, sources, indexDir, view, questionFiles, configDir) {
1931
+ const knob = settings.severity;
1932
+ const top = settings.top;
1933
+ const sets = questionFiles.map((label) => ({
1934
+ label,
1935
+ questions: readQuestionFile(resolve3(configDir, label), label)
1936
+ }));
1937
+ const graph = GraphIndex.fromQuads(storeToQuads(store), Object.fromEntries(PREFIXES));
1938
+ const { pages, contents, pageAt } = await readPages(graph, sources, view, "checked");
1939
+ const total = pages.reduce((n, p) => n + p.questions.length, 0) + sets.reduce((n, f) => n + f.questions.length, 0);
1940
+ if (total === 0) return { findings: [] };
1941
+ let byLanguage;
1942
+ let manifest = [];
1943
+ const retrievers = /* @__PURE__ */ new Map();
1944
+ const ranked = /* @__PURE__ */ new Map();
1945
+ const rank = async (language, question) => {
1946
+ const key = JSON.stringify([language, question]);
1947
+ const cached = ranked.get(key);
1948
+ if (cached) return cached;
1949
+ if (byLanguage === void 0) {
1950
+ byLanguage = partitionByLanguage(
1951
+ graph,
1952
+ buildSearchIndex(graph, sources, { readDoc: (p) => contents.get(p) })
1953
+ );
1954
+ manifest = readManifest(indexDir);
1955
+ }
1956
+ let retriever = retrievers.get(language);
1957
+ if (!retriever) {
1958
+ retriever = await openRetriever(
1959
+ byLanguage.get(language) ?? { version: 1, entries: [] },
1960
+ manifest.find((l) => canonicalLanguageTag(l.language) === language),
1961
+ indexDir
1962
+ );
1963
+ retrievers.set(language, retriever);
1964
+ }
1965
+ const nodes = await rankNodes(question, retriever);
1966
+ ranked.set(key, nodes);
1967
+ return nodes;
1968
+ };
1969
+ const describe = (iri) => {
1970
+ const path = graph.literal(pageOf(iri), PATH) ?? pageOf(iri);
1971
+ const title = iri.includes("#") ? graph.literal(iri, TITLE) : void 0;
1972
+ return title === void 0 ? path : `${path} \xA7 ${title}`;
1973
+ };
1974
+ const where = (position, first) => [
1975
+ position === 0 ? "it is not found" : `it ranks ${String(position)}`,
1976
+ ...first === void 0 ? [] : [`first is ${describe(first)}`]
1977
+ ].join("; ");
1978
+ const findings = [];
1979
+ const reporter = (target) => (kind, message, draft) => {
1980
+ const level = kind === "knob" ? knob : kind;
1981
+ if (level === "off") return;
1982
+ const suffix = draft === "question" ? " (drafted, not reviewed)" : draft === "pin" ? " (answer not reviewed)" : "";
1983
+ findings.push({
1984
+ severity: draft ? "notice" : level,
1985
+ message: `${message}${suffix}`,
1986
+ focusNode: target.focusNode,
1987
+ ...target.path === void 0 ? {} : { path: target.path },
1988
+ docs: target.docs
1989
+ });
1990
+ };
1991
+ const empty = () => ({ inTop: 0, first: 0, of: 0 });
1992
+ const summary = {
1993
+ total,
1994
+ reviewed: 0,
1995
+ drafted: 0,
1996
+ pages: pages.length,
1997
+ files: [...questionFiles],
1998
+ top,
1999
+ found: { reviewed: empty(), drafted: empty() },
2000
+ answers: { pinned: 0, unreviewed: 0, current: 0, moved: 0, changed: 0, unpinned: 0 },
2001
+ gaps: 0
2002
+ };
2003
+ const tally = (reviewed, tests) => {
2004
+ if (tests.length === 0) return;
2005
+ const count = reviewed ? summary.found.reviewed : summary.found.drafted;
2006
+ count.of += 1;
2007
+ if (tests.every((p) => p >= 1 && p <= top)) count.inTop += 1;
2008
+ if (tests.every((p) => p === 1)) count.first += 1;
2009
+ };
2010
+ const askPin = async (page, pin, question, language, report, draft, file) => {
2011
+ const fresh = freshness(page.body, pin);
2012
+ if (fresh.state === "changed") {
2013
+ summary.answers.changed += 1;
2014
+ const of2 = file === void 0 ? "" : ` of ${file}`;
2015
+ const fix = file === void 0 ? "re-pair it with manni graph fill --fields sample-questions and review it" : "re-pin it and review it";
2016
+ report(
2017
+ "knob",
2018
+ `lines ${spellLines(pin.lines)}${of2} no longer hold the answer to "${question}" as pinned, so ${fix}.`,
2019
+ draft
2020
+ );
2021
+ return { position: 0 };
2022
+ }
2023
+ const [range] = fresh.ranges;
2024
+ const inFile = file === void 0 ? "" : ` in ${file}`;
2025
+ if (fresh.state === "current") summary.answers.current += 1;
2026
+ else summary.answers.moved += 1;
2027
+ if (fresh.state === "moved" && range) {
2028
+ report(
2029
+ "notice",
2030
+ `the answer to "${question}"${inFile} moved from lines ${spellLines(pin.lines)} to ${spellLines(range)}, so manni graph fill --fields sample-questions re-pins it.`,
2031
+ draft
2032
+ );
2033
+ } else if (fresh.state === "ambiguous") {
2034
+ report(
2035
+ "warning",
2036
+ `the answer to "${question}"${inFile} moved, and ${matchList(fresh.ranges)}, so pin one of them by hand.`,
2037
+ draft
2038
+ );
2039
+ }
2040
+ const text = range ? page.body.slice(range.start - 1, range.end).join("\n") : void 0;
2041
+ const nodes = await rank(language, question);
2042
+ const answering = fresh.ranges.flatMap((r) => holdersOf(page, toFileLines(r, page.bodyLine)));
2043
+ const positions = answering.map((iri) => nodes.indexOf(iri) + 1);
2044
+ const best = positions.reduce(
2045
+ (b, p, i) => p >= 1 && (b.p === 0 || p < b.p) ? { p, i } : b,
2046
+ { p: 0, i: 0 }
2047
+ );
2048
+ if (best.p >= 1 && best.p <= top) return { position: best.p, ...text === void 0 ? {} : { text } };
2049
+ const holder = answering[best.i];
2050
+ if (holder !== void 0) {
2051
+ const title = holder.includes("#") ? graph.literal(holder, TITLE) : void 0;
2052
+ const target = file !== void 0 ? `does not find ${describe(holder)}` : title === void 0 ? "does not find this page, whose own text answers it," : `does not find the section that answers it, \xA7 ${title},`;
2053
+ report("knob", `"${question}" ${target} in the top ${String(top)} (${where(best.p, nodes[0])})`, draft);
2054
+ }
2055
+ return { position: best.p, ...text === void 0 ? {} : { text } };
2056
+ };
2057
+ const copies = (question, said, report, draft) => {
2058
+ const m = said.length === 0 ? void 0 : copiedPairs(question, ...said);
2059
+ if (m && m.copied / m.of > COPIED_SHARE) {
2060
+ report(
2061
+ "notice",
2062
+ `"${question}" repeats ${String(m.copied)} of its ${String(m.of)} word pairs from its answer, so it tests matching words more than finding.`,
2063
+ draft
2064
+ );
2065
+ }
2066
+ };
2067
+ const asked = [];
2068
+ for (const page of pages) {
2069
+ const report = reporter({ focusNode: page.doc, path: SAMPLE_QUESTION, docs: [page.path] });
2070
+ for (const [n, q] of page.questions.entries()) {
2071
+ const drafted = q.reviewed ? void 0 : "question";
2072
+ if (q.reviewed) summary.reviewed += 1;
2073
+ else summary.drafted += 1;
2074
+ asked.push({ key: questionKey(q.question), where: page.path, order: n, text: q.question, report, ...drafted ? { draft: drafted } : {} });
2075
+ if (q.answers.length === 0) summary.answers.unpinned += 1;
2076
+ const tests = [];
2077
+ if (q.answers.length === 0 || q.reviewed && q.answers.every((a) => !a.reviewed)) {
2078
+ const nodes = await rank(page.language, q.question);
2079
+ const pagesFound = [...new Set(nodes.map(pageOf))];
2080
+ const position = pagesFound.indexOf(page.doc) + 1;
2081
+ tests.push(position);
2082
+ if (position < 1 || position > top) {
2083
+ report(
2084
+ "knob",
2085
+ `"${q.question}" does not find this page in the top ${String(top)} (${where(position, pagesFound[0])})`,
2086
+ drafted
2087
+ );
2088
+ }
2089
+ }
2090
+ const said = [];
2091
+ for (const pin of q.answers) {
2092
+ const draft = drafted ?? (pin.reviewed ? void 0 : "pin");
2093
+ summary.answers.pinned += 1;
2094
+ if (draft) summary.answers.unreviewed += 1;
2095
+ const { position, text } = await askPin(page, pin, q.question, page.language, report, draft);
2096
+ if (text !== void 0) said.push(text);
2097
+ if (draft !== "pin") tests.push(position);
2098
+ }
2099
+ copies(q.question, said, report, drafted);
2100
+ tally(q.reviewed, tests);
2101
+ }
2102
+ }
2103
+ for (const set of sets) {
2104
+ for (const [n, q] of set.questions.entries()) {
2105
+ const report = reporter({ focusNode: `${set.label}:${String(q.line)}`, docs: [set.label] });
2106
+ const drafted = q.reviewed ? void 0 : "question";
2107
+ if (q.reviewed) summary.reviewed += 1;
2108
+ else summary.drafted += 1;
2109
+ asked.push({ key: questionKey(q.question), where: set.label, order: n, text: q.question, report, ...drafted ? { draft: drafted } : {} });
2110
+ if (q.answers.length === 0) {
2111
+ summary.gaps += 1;
2112
+ continue;
2113
+ }
2114
+ const [firstAnswer] = q.answers;
2115
+ const firstPage = firstAnswer && pageAt(resolve3(configDir, firstAnswer.file));
2116
+ const language = q.language !== void 0 ? canonicalLanguageTag(q.language) : firstPage?.language ?? UNDETERMINED;
2117
+ const tests = [];
2118
+ const said = [];
2119
+ for (const pin of q.answers) {
2120
+ const draft = drafted ?? (pin.reviewed ? void 0 : "pin");
2121
+ summary.answers.pinned += 1;
2122
+ if (draft) summary.answers.unreviewed += 1;
2123
+ const abs = resolve3(configDir, pin.file);
2124
+ const page = pageAt(abs);
2125
+ if (page === void 0) {
2126
+ if (existsSync4(abs)) {
2127
+ const nodes = await rank(language, q.question);
2128
+ report("knob", `"${q.question}" does not find ${pin.file} in the top ${String(top)} (${where(0, nodes[0])})`, draft);
2129
+ } else {
2130
+ summary.answers.changed += 1;
2131
+ report(
2132
+ "knob",
2133
+ `${pin.file}, which holds the answer to "${q.question}", does not exist, so re-pin or remove the question.`,
2134
+ draft
2135
+ );
2136
+ }
2137
+ if (draft !== "pin") tests.push(0);
2138
+ continue;
2139
+ }
2140
+ const { position, text } = await askPin(page, pin, q.question, language, report, draft, pin.file);
2141
+ if (text !== void 0) said.push(text);
2142
+ if (draft !== "pin") tests.push(position);
2143
+ }
2144
+ copies(q.question, said, report, drafted);
2145
+ tally(q.reviewed, tests);
2146
+ }
2147
+ }
2148
+ asked.sort((a, b) => byCodeUnit(a.where, b.where) || a.order - b.order);
2149
+ const firstAt = /* @__PURE__ */ new Map();
2150
+ for (const copy of asked) {
2151
+ if (copy.key === "") continue;
2152
+ const first = firstAt.get(copy.key);
2153
+ if (first === void 0) firstAt.set(copy.key, copy.where);
2154
+ else copy.report("knob", `"${copy.text}" is also asked on ${first}, so keep it where it is answered.`, copy.draft);
2155
+ }
2156
+ const threshold = settings.foundThreshold;
2157
+ const { inTop, of } = summary.found.reviewed;
2158
+ if (threshold !== null && of > 0 && inTop / of * 100 < threshold) {
2159
+ findings.push({
2160
+ severity: "error",
2161
+ message: `${String(inTop)} of ${String(of)} reviewed questions are found in the top ${String(top)} (${String(percent(inTop, of))}%), under the ${String(threshold)}% that graph.check.questions.foundThreshold asks for.`,
2162
+ focusNode: "questions",
2163
+ docs: []
2164
+ });
2165
+ }
2166
+ return { findings, summary };
2167
+ }
2168
+ async function exportSampleQuestions(store, sources, view, questionFiles, configDir) {
2169
+ const sets = questionFiles.map((label) => ({
2170
+ label,
2171
+ questions: readQuestionFile(resolve3(configDir, label), label)
2172
+ }));
2173
+ const graph = GraphIndex.fromQuads(storeToQuads(store), Object.fromEntries(PREFIXES));
2174
+ const { pages, pageAt } = await readPages(graph, sources, view, "exported");
2175
+ let stale = 0;
2176
+ const pinned = (page, file, pin, reviewed) => {
2177
+ const fresh = freshness(page.body, pin);
2178
+ const range = fresh.state === "changed" ? pin.lines : fresh.ranges[0] ?? pin.lines;
2179
+ if (fresh.state === "changed") stale += 1;
2180
+ const first = toFileLines({ start: range.start, end: range.start }, page.bodyLine);
2181
+ const text = page.body.slice(range.start - 1, range.end).join("\n");
2182
+ return {
2183
+ id: holdersOf(page, first)[0] ?? page.doc,
2184
+ file,
2185
+ lines: spellLines(range),
2186
+ reviewed,
2187
+ ...fresh.state === "changed" ? { stale: true } : {},
2188
+ ...text === "" ? {} : { text }
2189
+ };
2190
+ };
2191
+ const lines = [];
2192
+ const write = (id, question, language, reviewed, expected) => {
2193
+ lines.push(
2194
+ JSON.stringify({ id, question, language, reviewed, answerable: expected.length > 0, expected })
2195
+ );
2196
+ };
2197
+ for (const page of pages) {
2198
+ const slugger = new GithubSlugger();
2199
+ for (const q of page.questions) {
2200
+ const expected = q.answers.length === 0 ? [{ id: page.doc, file: page.path, reviewed: q.reviewed }] : q.answers.map((pin) => pinned(page, page.path, pin, q.reviewed && pin.reviewed));
2201
+ write(`${page.path}#${slugger.slug(q.question)}`, q.question, page.language, q.reviewed, expected);
2202
+ }
2203
+ }
2204
+ for (const set of sets) {
2205
+ const slugger = new GithubSlugger();
2206
+ for (const q of set.questions) {
2207
+ const expected = q.answers.map((pin) => {
2208
+ const reviewed = q.reviewed && pin.reviewed;
2209
+ const abs = resolve3(configDir, pin.file);
2210
+ const page = pageAt(abs);
2211
+ if (page) return pinned(page, pin.file, pin, reviewed);
2212
+ const gone = !existsSync4(abs);
2213
+ if (gone) stale += 1;
2214
+ return { file: pin.file, lines: spellLines(pin.lines), reviewed, ...gone ? { stale: true } : {} };
2215
+ });
2216
+ const [firstAnswer] = q.answers;
2217
+ const firstPage = firstAnswer && pageAt(resolve3(configDir, firstAnswer.file));
2218
+ const language = q.language !== void 0 ? canonicalLanguageTag(q.language) : firstPage?.language ?? UNDETERMINED;
2219
+ write(`${set.label}#${slugger.slug(q.question)}`, q.question, language, q.reviewed, expected);
2220
+ }
2221
+ }
2222
+ return { lines, stale };
2223
+ }
2224
+
2225
+ export {
2226
+ loadGraph,
2227
+ parseGraph,
2228
+ storeToQuads,
2229
+ expandTerm,
2230
+ compactIri,
2231
+ bundledShapesPath,
2232
+ compareFindings,
2233
+ validateGraph,
2234
+ splitFragment,
2235
+ documentSectionOrder,
2236
+ sectionOccurrences,
2237
+ sectionOccurrence,
2238
+ documentPreamble,
2239
+ sliceSection,
2240
+ sectionOwnText,
2241
+ createFetchResolver,
2242
+ buildSearchIndex,
2243
+ emitSearchIndex,
2244
+ partitionByLanguage,
2245
+ documentsByLanguage,
2246
+ VectorIndexError,
2247
+ normalize,
2248
+ encodeVectorIndex,
2249
+ decodeVectorIndex,
2250
+ VectorMismatchError,
2251
+ searchIndexDigest,
2252
+ createVectorIndex,
2253
+ createTrace,
2254
+ reachedNodes,
2255
+ rrfMerge,
2256
+ findEntry,
2257
+ GraphIndex,
2258
+ createLexicalIndex,
2259
+ EmbedderUnavailableError,
2260
+ createLocalEmbedder,
2261
+ createMockEmbedder,
2262
+ SEARCH_MODES,
2263
+ runSearch,
2264
+ renderSearch,
2265
+ bodyOf,
2266
+ freshness,
2267
+ matchList,
2268
+ renderQuestionSummary,
2269
+ askSampleQuestions,
2270
+ exportSampleQuestions
2271
+ };
2272
+ //# sourceMappingURL=chunk-KCB5JCK4.js.map