@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
@@ -1,41 +1,34 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- LOCALIZATIONS_FILENAME,
4
- STDIN,
5
- UNDETERMINED,
6
- analyzeDoc,
7
- assertInputFormat,
8
- canonicalLanguageTag,
3
+ canonicalLanguage,
9
4
  deriveGraph,
10
- documentBase,
11
- documentSetPatterns,
12
- emitLocalizations,
13
- extractorFor,
14
- formatOf,
15
- isLanguageTag,
16
- isSafeRelativePath,
17
- mergePage,
18
- metadataOf,
19
- mintTreeSections,
20
- openMetaView,
21
- parseLocalizations,
22
- parseTree,
23
- resolveDocumentSet,
24
- routesFrom,
25
- searchIndexFilename,
26
- vectorIndexFilename
27
- } from "./chunk-2IAZEB45.js";
5
+ termIndex
6
+ } from "./chunk-WGMFDG6U.js";
28
7
  import {
8
+ EmbedderUnavailableError,
9
+ GraphIndex,
10
+ bodyOf,
11
+ buildSearchIndex,
29
12
  bundledShapesPath,
30
13
  compactIri,
14
+ createLocalEmbedder,
15
+ createMockEmbedder,
16
+ createTrace,
17
+ documentsByLanguage,
18
+ emitSearchIndex,
19
+ encodeVectorIndex,
31
20
  expandTerm,
21
+ exportSampleQuestions,
22
+ freshness,
32
23
  loadGraph,
24
+ matchList,
25
+ partitionByLanguage,
33
26
  storeToQuads,
34
27
  validateGraph
35
- } from "./chunk-EFW7CAG4.js";
28
+ } from "./chunk-KCB5JCK4.js";
36
29
  import {
30
+ COPIED_SHARE,
37
31
  COVERAGE_FIELDS,
38
- DEFAULT_MODEL,
39
32
  GRAPH_NOT_APPLICABLE_TO_VARIANT,
40
33
  GRAPH_NOT_SOFTWARE_SUBJECT,
41
34
  GraphError,
@@ -59,32 +52,65 @@ import {
59
52
  IIRDS_SOURCE,
60
53
  IIRDS_TITLE,
61
54
  IIRDS_TOPIC,
55
+ LOCALIZATIONS_FILENAME,
62
56
  NS,
63
57
  PREFIXES,
64
58
  RDF_TYPE,
65
59
  SECTION_COVERAGE_FIELDS,
66
60
  SOFTWARE_SUBJECT_IRIS,
61
+ STDIN,
67
62
  STDIN_PATH,
63
+ UNIFORM_COVERAGE_FIELD_NAMES,
68
64
  VCARD_NS,
69
65
  VCARD_ORGANIZATION,
70
66
  VCARD_ORGANIZATION_NAME,
67
+ analyzeDoc,
68
+ assertInputFormat,
71
69
  byCodeUnit,
70
+ canonicalLanguageTag,
71
+ copiedPairs,
72
+ documentBase,
73
+ documentSetPatterns,
74
+ emitLocalizations,
75
+ extractorFor,
76
+ formatOf,
77
+ isLanguageTag,
78
+ isSafeRelativePath,
72
79
  loadRunConfig,
73
- profileFor,
74
- withPrefix
75
- } from "./chunk-JROBZ7JA.js";
80
+ mergePage,
81
+ metadataOf,
82
+ openMetaView,
83
+ parseLocalizations,
84
+ questionKey,
85
+ readQuestionFile,
86
+ resolveDocumentSet,
87
+ routesFrom,
88
+ searchIndexFilename,
89
+ vectorIndexFilename
90
+ } from "./chunk-Q2QC5AZB.js";
91
+ import {
92
+ splitBody
93
+ } from "./chunk-VQYGLU4C.js";
76
94
  import {
77
95
  mergeMetaProvenance
78
- } from "./chunk-M42DZCOU.js";
96
+ } from "./chunk-7FCLXEAQ.js";
79
97
  import {
98
+ findWindows,
80
99
  isMissing,
100
+ lineSpec,
101
+ parseLines,
102
+ pinOfLines,
103
+ spellLines,
81
104
  spliceManifestValue
82
- } from "./chunk-GV56WGL2.js";
105
+ } from "./chunk-WO4JYWZK.js";
106
+ import {
107
+ termLabelSource
108
+ } from "./chunk-DSWKBZWW.js";
83
109
  import {
84
110
  DocmetaError,
85
111
  urlManifestMessage,
86
112
  writeFileAtomic
87
- } from "./chunk-VSEBFHAY.js";
113
+ } from "./chunk-KFKL4VTL.js";
88
114
  import {
89
115
  noticeOnce,
90
116
  warn
@@ -94,18 +120,20 @@ import {
94
120
  assertLocalFlag,
95
121
  assertModelHasProvider,
96
122
  localNotice,
123
+ noOfflineModel,
124
+ offlineIdentity,
97
125
  providerSpecFor,
98
126
  resolveIdentity,
99
127
  selectProvider
100
- } from "./chunk-QCHVR4ZM.js";
128
+ } from "./chunk-VM7VAFJW.js";
101
129
  import {
102
130
  errorMessage
103
131
  } from "./chunk-G3T47CFV.js";
104
132
 
105
133
  // src/graph/commands/export.ts
106
134
  import { createHash } from "node:crypto";
107
- import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
108
- import { dirname, join, resolve as resolve3 } from "node:path";
135
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
136
+ import { dirname, join, resolve as resolve2 } from "node:path";
109
137
 
110
138
  // src/graph/core/emit-jsonld.ts
111
139
  var XSD_STRING = `${NS.xsd}string`;
@@ -115,7 +143,17 @@ function renderValue(term) {
115
143
  value: { "@id": term.value },
116
144
  kind: 0,
117
145
  primary: term.value,
118
- datatype: ""
146
+ datatype: "",
147
+ language: ""
148
+ };
149
+ }
150
+ if (term.language) {
151
+ return {
152
+ value: { "@value": term.value, "@language": term.language },
153
+ kind: 1,
154
+ primary: term.value,
155
+ datatype: "",
156
+ language: term.language
119
157
  };
120
158
  }
121
159
  if (term.datatype && term.datatype !== XSD_STRING) {
@@ -123,16 +161,17 @@ function renderValue(term) {
123
161
  value: { "@value": term.value, "@type": compactIri(term.datatype) },
124
162
  kind: 1,
125
163
  primary: term.value,
126
- datatype: term.datatype
164
+ datatype: term.datatype,
165
+ language: ""
127
166
  };
128
167
  }
129
- return { value: term.value, kind: 1, primary: term.value, datatype: "" };
168
+ return { value: term.value, kind: 1, primary: term.value, datatype: "", language: "" };
130
169
  }
131
170
  function compareRendered(a, b) {
132
171
  if (a.kind !== b.kind) return a.kind - b.kind;
133
172
  const p = byCodeUnit(a.primary, b.primary);
134
173
  if (p !== 0) return p;
135
- return byCodeUnit(a.datatype, b.datatype);
174
+ return byCodeUnit(a.datatype, b.datatype) || byCodeUnit(a.language, b.language);
136
175
  }
137
176
  function collapse(values) {
138
177
  const rendered = [...values].sort(compareRendered).map((v) => v.value);
@@ -200,7 +239,9 @@ function compareTerms(a, b) {
200
239
  if (v !== 0) return v;
201
240
  const da = a.kind === "literal" ? a.datatype ?? "" : "";
202
241
  const db = b.kind === "literal" ? b.datatype ?? "" : "";
203
- return byCodeUnit(da, db);
242
+ const la = a.kind === "literal" ? a.language ?? "" : "";
243
+ const lb = b.kind === "literal" ? b.language ?? "" : "";
244
+ return byCodeUnit(da, db) || byCodeUnit(la, lb);
204
245
  }
205
246
  function emitRdfXml(quads, prefixes) {
206
247
  const sortedPrefixes = Object.entries(prefixes).sort(
@@ -241,6 +282,10 @@ function emitRdfXml(quads, prefixes) {
241
282
  lines.push(
242
283
  ` <${qname} rdf:resource="${escapeAttr(term.value)}"/>`
243
284
  );
285
+ } else if (term.language) {
286
+ lines.push(
287
+ ` <${qname} xml:lang="${escapeAttr(term.language)}">${escapeText(term.value)}</${qname}>`
288
+ );
244
289
  } else if (term.datatype && term.datatype !== XSD_STRING2) {
245
290
  lines.push(
246
291
  ` <${qname} rdf:datatype="${escapeAttr(term.datatype)}">${escapeText(term.value)}</${qname}>`
@@ -275,17 +320,17 @@ function writeZip(entries) {
275
320
  const locals = [];
276
321
  const centrals = [];
277
322
  let offset = 0;
278
- for (const entry2 of entries) {
279
- if (escapesArchiveRoot(entry2.name)) {
323
+ for (const entry of entries) {
324
+ if (escapesArchiveRoot(entry.name)) {
280
325
  throw new GraphError(
281
- `Refusing ZIP entry "${entry2.name}": an archive entry may not be absolute or climb above the archive root.`
326
+ `Refusing ZIP entry "${entry.name}": an archive entry may not be absolute or climb above the archive root.`
282
327
  );
283
328
  }
284
- const name = Buffer.from(entry2.name, "utf8");
329
+ const name = Buffer.from(entry.name, "utf8");
285
330
  const flags = name.some((b) => b > 127) ? 2048 : 0;
286
- const method = entry2.store ? 0 : 8;
287
- const stored = entry2.store ? entry2.data : deflateRawSync(entry2.data);
288
- const crc = crc32(entry2.data) >>> 0;
331
+ const method = entry.store ? 0 : 8;
332
+ const stored = entry.store ? entry.data : deflateRawSync(entry.data);
333
+ const crc = crc32(entry.data) >>> 0;
289
334
  const local = Buffer.alloc(30);
290
335
  local.writeUInt32LE(67324752, 0);
291
336
  local.writeUInt16LE(VERSION, 4);
@@ -295,7 +340,7 @@ function writeZip(entries) {
295
340
  local.writeUInt16LE(DOS_DATE, 12);
296
341
  local.writeUInt32LE(crc, 14);
297
342
  local.writeUInt32LE(stored.length, 18);
298
- local.writeUInt32LE(entry2.data.length, 22);
343
+ local.writeUInt32LE(entry.data.length, 22);
299
344
  local.writeUInt16LE(name.length, 26);
300
345
  local.writeUInt16LE(0, 28);
301
346
  locals.push(local, name, stored);
@@ -309,7 +354,7 @@ function writeZip(entries) {
309
354
  central.writeUInt16LE(DOS_DATE, 14);
310
355
  central.writeUInt32LE(crc, 16);
311
356
  central.writeUInt32LE(stored.length, 20);
312
- central.writeUInt32LE(entry2.data.length, 24);
357
+ central.writeUInt32LE(entry.data.length, 24);
313
358
  central.writeUInt16LE(name.length, 28);
314
359
  central.writeUInt16LE(0, 30);
315
360
  central.writeUInt16LE(0, 32);
@@ -424,7 +469,9 @@ function projectPackage(store, opts, cwd) {
424
469
  null
425
470
  )) {
426
471
  const o = q.object;
427
- if (o.termType === "Literal") {
472
+ if (o.termType === "Literal" && o.language !== "") {
473
+ add(doc, pred, { kind: "literal", value: o.value, language: o.language });
474
+ } else if (o.termType === "Literal") {
428
475
  const dt = o.datatype.value;
429
476
  add(doc, pred, lit(o.value, dt !== XSD_STRING3 ? dt : void 0));
430
477
  } else {
@@ -449,655 +496,8 @@ function projectPackage(store, opts, cwd) {
449
496
  return { quads, prefixes, contentFiles, warnings };
450
497
  }
451
498
 
452
- // src/graph/core/search-index.ts
453
- import { existsSync as existsSync2, readFileSync } from "node:fs";
454
- import { resolve as resolve2 } from "node:path";
455
-
456
- // src/graph/runtime/resolve.ts
457
- var GRAPH_PATH = `${NS.graph}path`;
458
- var DCTERMS_TITLE = `${NS.dcterms}title`;
459
- var DCTERMS_HAS_PART = `${NS.dcterms}hasPart`;
460
- var GRAPH_LEVEL = `${NS.graph}level`;
461
- var GRAPH_ORDER = `${NS.graph}order`;
462
- function splitFragment(iri2) {
463
- const hash = iri2.indexOf("#");
464
- return hash < 0 ? { doc: iri2 } : { doc: iri2.slice(0, hash), fragment: iri2.slice(hash + 1) };
465
- }
466
- function fencedLines(lines) {
467
- const mask = new Array(lines.length).fill(false);
468
- let open;
469
- for (const [i, line] of lines.entries()) {
470
- const m = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
471
- const delimiter = m?.[1];
472
- const info = m?.[2];
473
- const isFence = delimiter !== void 0 && info !== void 0;
474
- if (open === void 0) {
475
- if (isFence && !(delimiter.startsWith("`") && info.includes("`"))) {
476
- open = delimiter;
477
- mask[i] = true;
478
- }
479
- continue;
480
- }
481
- mask[i] = true;
482
- if (isFence && delimiter[0] === open[0] && delimiter.length >= open.length && info.trim() === "") {
483
- open = void 0;
484
- }
485
- }
486
- return mask;
487
- }
488
- var ATX = /^ {0,3}(#{1,6})(?:[ \t]+(.*?))?[ \t]*$/;
489
- var SETEXT_UNDERLINE = /^ {0,3}(=+|-+)[ \t]*$/;
490
- function isParagraphLine(line) {
491
- if (line.trim() === "") return false;
492
- if (ATX.test(line)) return false;
493
- if (/^ {0,3}([-+*]|\d{1,9}[.)])([ \t]|$)/.test(line)) return false;
494
- if (/^ {0,3}>/.test(line)) return false;
495
- if (/^ {0,3}([-*_])([ \t]*\1){2,}[ \t]*$/.test(line)) return false;
496
- return !/^ {4}/.test(line);
497
- }
498
- function frontmatterLength(lines) {
499
- if (lines[0]?.trim() !== "---") return 0;
500
- for (let i = 1; i < lines.length; i++) {
501
- const line = lines[i]?.trim();
502
- if (line === "---" || line === "...") return i + 1;
503
- }
504
- return 0;
505
- }
506
- function sourceHeadings(lines) {
507
- const fenced = fencedLines(lines);
508
- const skip = frontmatterLength(lines);
509
- const headings = [];
510
- let paragraph = -1;
511
- for (const [i, line] of lines.entries()) {
512
- if (i < skip || fenced[i]) {
513
- paragraph = -1;
514
- continue;
515
- }
516
- const atx = ATX.exec(line);
517
- const hashes = atx?.[1];
518
- if (hashes !== void 0) {
519
- const text = (atx?.[2] ?? "").replace(/(^|[ \t]+)#+$/, "");
520
- headings.push({ start: i, level: hashes.length, text });
521
- paragraph = -1;
522
- continue;
523
- }
524
- const underline = SETEXT_UNDERLINE.exec(line)?.[1];
525
- if (underline !== void 0 && paragraph >= 0) {
526
- headings.push({
527
- start: paragraph,
528
- level: underline.startsWith("=") ? 1 : 2,
529
- text: lines.slice(paragraph, i).join("\n")
530
- });
531
- paragraph = -1;
532
- continue;
533
- }
534
- if (isParagraphLine(line)) {
535
- if (paragraph < 0) paragraph = i;
536
- } else {
537
- paragraph = -1;
538
- }
539
- }
540
- return headings;
541
- }
542
- var ENTITIES = {
543
- amp: "&",
544
- lt: "<",
545
- gt: ">",
546
- quot: '"',
547
- apos: "'",
548
- nbsp: "\xA0"
549
- };
550
- function decodeEntities(text) {
551
- return text.replace(
552
- /&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/gi,
553
- (whole, name) => {
554
- if (name.startsWith("#")) {
555
- const hex = name[1] === "x" || name[1] === "X";
556
- const code = Number.parseInt(name.slice(hex ? 2 : 1), hex ? 16 : 10);
557
- return Number.isNaN(code) || code > 1114111 ? whole : String.fromCodePoint(code);
558
- }
559
- return ENTITIES[name.toLowerCase()] ?? whole;
560
- }
561
- );
562
- }
563
- function headingKey(text) {
564
- return text.replace(/[`*_~\\[\]]/g, "").replace(/\s+/g, " ").trim().toLowerCase();
565
- }
566
- function stripInline(raw) {
567
- return decodeEntities(
568
- raw.replace(/!?\[([^\]]*)\]\((?:[^()]|\([^()]*\))*\)/g, "$1").replace(/!?\[([^\]]*)\]\[[^\]]*\]/g, "$1").replace(/<((?:https?|mailto):[^>\s]+)>/gi, "$1")
569
- );
570
- }
571
- function documentSectionOrder(graph, docIri) {
572
- const ordered = [];
573
- const seen = /* @__PURE__ */ new Set();
574
- const childrenOf = (iri2) => graph.out(iri2, DCTERMS_HAS_PART).map((e) => e.target).sort((a, b) => {
575
- const oa = Number.parseInt(graph.literal(a, GRAPH_ORDER) ?? "", 10);
576
- const ob = Number.parseInt(graph.literal(b, GRAPH_ORDER) ?? "", 10);
577
- if (Number.isNaN(oa) || Number.isNaN(ob) || oa === ob) {
578
- return a < b ? -1 : a > b ? 1 : 0;
579
- }
580
- return oa - ob;
581
- });
582
- const walk = (iri2) => {
583
- for (const child of childrenOf(iri2)) {
584
- if (seen.has(child)) continue;
585
- seen.add(child);
586
- ordered.push(child);
587
- walk(child);
588
- }
589
- };
590
- walk(docIri);
591
- return ordered;
592
- }
593
- function sectionOccurrences(graph, docIri) {
594
- const occurrences = /* @__PURE__ */ new Map();
595
- const counts = /* @__PURE__ */ new Map();
596
- for (const section of documentSectionOrder(graph, docIri)) {
597
- const rawTitle = graph.literal(section, DCTERMS_TITLE);
598
- if (rawTitle === void 0) {
599
- occurrences.set(section, 0);
600
- continue;
601
- }
602
- const title = headingKey(rawTitle);
603
- const level = graph.literal(section, GRAPH_LEVEL) ?? "";
604
- let byLevel = counts.get(title);
605
- if (!byLevel) {
606
- byLevel = /* @__PURE__ */ new Map();
607
- counts.set(title, byLevel);
608
- }
609
- const seen = byLevel.get(level) ?? 0;
610
- occurrences.set(section, seen);
611
- byLevel.set(level, seen + 1);
612
- }
613
- return occurrences;
614
- }
615
- function sectionOccurrence(graph, sectionIri) {
616
- const { doc } = splitFragment(sectionIri);
617
- return sectionOccurrences(graph, doc).get(sectionIri) ?? 0;
618
- }
619
- function documentPreamble(markdown) {
620
- const lines = markdown.split(/\r?\n/);
621
- const end = sourceHeadings(lines)[0]?.start ?? lines.length;
622
- const text = lines.slice(0, end).join("\n").trim();
623
- return text === "" ? void 0 : text;
624
- }
625
- function sliceSection(markdown, title, level, occurrence = 0) {
626
- return slice(markdown, title, level, occurrence, false);
627
- }
628
- function sectionOwnText(markdown, title, level, occurrence = 0) {
629
- return slice(markdown, title, level, occurrence, true);
630
- }
631
- function slice(markdown, title, level, occurrence, ownTextOnly) {
632
- const lines = markdown.split(/\r?\n/);
633
- const headings = sourceHeadings(lines);
634
- const wanted = headingKey(title);
635
- let remaining = occurrence;
636
- let found = -1;
637
- for (const [n, heading] of headings.entries()) {
638
- if (level !== void 0 && heading.level !== level) continue;
639
- if (headingKey(stripInline(heading.text)) !== wanted) continue;
640
- if (remaining > 0) {
641
- remaining -= 1;
642
- continue;
643
- }
644
- found = n;
645
- break;
646
- }
647
- const match = headings[found];
648
- if (match === void 0) return void 0;
649
- const next = headings.slice(found + 1).find((h) => ownTextOnly || h.level <= match.level);
650
- const end = next?.start ?? lines.length;
651
- return lines.slice(match.start, end).join("\n").trimEnd();
652
- }
653
- function createFetchResolver(graph, options = {}) {
654
- const doFetch = options.fetch ?? ((url) => globalThis.fetch(url));
655
- const toUrl = options.pathToUrl ?? ((path2) => `${options.baseUrl ?? ""}${path2}`);
656
- const cache = /* @__PURE__ */ new Map();
657
- const occurrenceCache = /* @__PURE__ */ new Map();
658
- const occurrenceOf = (docIri, sectionIri) => {
659
- let map = occurrenceCache.get(docIri);
660
- if (!map) {
661
- map = sectionOccurrences(graph, docIri);
662
- occurrenceCache.set(docIri, map);
663
- }
664
- return map.get(sectionIri) ?? 0;
665
- };
666
- const fetchDoc = (url) => {
667
- let pending = cache.get(url);
668
- if (!pending) {
669
- pending = doFetch(url).then((r) => r.ok ? r.text() : void 0).catch(() => void 0);
670
- cache.set(url, pending);
671
- }
672
- return pending;
673
- };
674
- return {
675
- async resolve(iri2) {
676
- const { doc, fragment } = splitFragment(iri2);
677
- const path2 = graph.literal(doc, GRAPH_PATH);
678
- if (!path2) return void 0;
679
- const sourceUrl = toUrl(path2);
680
- const body = await fetchDoc(sourceUrl);
681
- const title = graph.literal(iri2, DCTERMS_TITLE);
682
- if (body === void 0) {
683
- options.trace?.resolutions.push({
684
- iri: iri2,
685
- sourceUrl,
686
- ok: false,
687
- error: "fetch failed"
688
- });
689
- return void 0;
690
- }
691
- let text = body;
692
- if (fragment) {
693
- const levelText = graph.literal(iri2, GRAPH_LEVEL);
694
- const level = levelText ? Number.parseInt(levelText, 10) : void 0;
695
- const slice2 = title === void 0 ? void 0 : sliceSection(
696
- body,
697
- title,
698
- Number.isNaN(level) ? void 0 : level,
699
- occurrenceOf(doc, iri2)
700
- );
701
- if (slice2 === void 0) {
702
- options.trace?.resolutions.push({
703
- iri: iri2,
704
- sourceUrl,
705
- ok: false,
706
- error: "section heading not found"
707
- });
708
- return void 0;
709
- }
710
- text = slice2;
711
- }
712
- options.trace?.resolutions.push({ iri: iri2, sourceUrl, ok: true });
713
- return { iri: iri2, text, sourceUrl, title };
714
- }
715
- };
716
- }
717
-
718
- // src/graph/core/search-index.ts
719
- var GRAPH_DOCUMENT2 = `${NS.graph}Document`;
720
- var GRAPH_SECTION = `${NS.graph}Section`;
721
- var SKOS_CONCEPT = `${NS.skos}Concept`;
722
- var GRAPH_PATH2 = `${NS.graph}path`;
723
- var GRAPH_LEVEL2 = `${NS.graph}level`;
724
- var DCTERMS_TITLE2 = `${NS.dcterms}title`;
725
- var DCTERMS_DESCRIPTION = `${NS.dcterms}description`;
726
- var SKOS_PREF_LABEL = `${NS.skos}prefLabel`;
727
- var SKOS_ALT_LABEL = `${NS.skos}altLabel`;
728
- var DCTERMS_LANGUAGE = `${NS.dcterms}language`;
729
- function joinLabels(values) {
730
- const seen = /* @__PURE__ */ new Map();
731
- for (const v of values) {
732
- const key = v.trim().toLowerCase();
733
- if (key && !seen.has(key)) seen.set(key, v.trim());
734
- }
735
- const out = [...seen.values()].sort(byCodeUnit);
736
- return out.length > 0 ? out.join(" ") : void 0;
737
- }
738
- var FRONTMATTER = /^---[ \t]*\r?\n(?:[\s\S]*?\r?\n)?(?:---|\.\.\.)[ \t]*(?:\r?\n|$)(?:\r?\n)*/;
739
- function stripFrontmatter(markdown) {
740
- return markdown.replace(FRONTMATTER, "");
741
- }
742
- function plainText(nodes) {
743
- return nodes.map((n) => n.text.replace(/\r\n/g, "\n").trim()).filter((t) => t !== "");
744
- }
745
- function treeText(source, path2) {
746
- const format = formatOf(path2);
747
- if (format === "markdown" || format === "mdx") return void 0;
748
- const minted = mintTreeSections(parseTree(source, path2, format).sections);
749
- const sections = /* @__PURE__ */ new Map();
750
- for (const [n, section] of minted.sections.entries()) {
751
- const own = plainText(minted.owned[n] ?? []);
752
- sections.set(section.slug, [section.title, ...own].join("\n\n"));
753
- }
754
- const lead = plainText(minted.unowned);
755
- return { doc: lead.length > 0 ? lead.join("\n\n") : void 0, sections };
756
- }
757
- function entry(id, type, fields) {
758
- const out = { id, type };
759
- if (fields.title !== void 0) out.title = fields.title;
760
- if (fields.labels !== void 0) out.labels = fields.labels;
761
- if (fields.description !== void 0) out.description = fields.description;
762
- if (fields.text !== void 0) out.text = fields.text;
763
- return out;
764
- }
765
- function buildSearchIndex(graph, cwd, options = {}) {
766
- const warnings = options.warnings ?? [];
767
- const readDoc = options.readDoc ?? ((path2) => {
768
- const abs = resolve2(cwd, path2);
769
- return existsSync2(abs) ? readFileSync(abs, "utf8") : void 0;
770
- });
771
- const entries = [];
772
- const sourceOf = /* @__PURE__ */ new Map();
773
- const treeTextOf = /* @__PURE__ */ new Map();
774
- const allSections = graph.instancesOf(GRAPH_SECTION);
775
- const sectionsOf = /* @__PURE__ */ new Map();
776
- for (const section of allSections) {
777
- const hash = section.indexOf("#");
778
- if (hash < 0) continue;
779
- const doc = section.slice(0, hash);
780
- const siblings = sectionsOf.get(doc);
781
- if (siblings) siblings.push(section);
782
- else sectionsOf.set(doc, [section]);
783
- }
784
- for (const doc of graph.instancesOf(GRAPH_DOCUMENT2)) {
785
- const path2 = graph.literal(doc, GRAPH_PATH2);
786
- const source = path2 === void 0 ? void 0 : readDoc(path2);
787
- sourceOf.set(doc, source);
788
- if (path2 !== void 0 && source === void 0) {
789
- warnings.push(
790
- `Source not found for ${path2} \u2014 indexed without body text.`
791
- );
792
- }
793
- const tree = source === void 0 || path2 === void 0 ? void 0 : treeText(source, path2);
794
- if (tree) treeTextOf.set(doc, tree);
795
- const body = source === void 0 ? void 0 : stripFrontmatter(source).replace(/\r\n/g, "\n");
796
- const text = tree ? tree.doc : body === void 0 ? void 0 : (sectionsOf.get(doc)?.length ?? 0) > 0 ? documentPreamble(body) : body;
797
- entries.push(
798
- entry(doc, compactIri(GRAPH_DOCUMENT2), {
799
- title: graph.literal(doc, DCTERMS_TITLE2),
800
- description: graph.literal(doc, DCTERMS_DESCRIPTION),
801
- text
802
- })
803
- );
804
- }
805
- const occurrencesOf = /* @__PURE__ */ new Map();
806
- for (const section of allSections) {
807
- const hash = section.indexOf("#");
808
- const doc = hash < 0 ? section : section.slice(0, hash);
809
- const title = graph.literal(section, DCTERMS_TITLE2);
810
- const source = sourceOf.get(doc);
811
- const levelText = graph.literal(section, GRAPH_LEVEL2);
812
- const parsed = levelText === void 0 ? NaN : Number.parseInt(levelText, 10);
813
- const level = Number.isNaN(parsed) ? void 0 : parsed;
814
- let occurrences = occurrencesOf.get(doc);
815
- if (!occurrences) {
816
- occurrences = sectionOccurrences(graph, doc);
817
- occurrencesOf.set(doc, occurrences);
818
- }
819
- const tree = treeTextOf.get(doc);
820
- 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;
821
- entries.push(entry(section, compactIri(GRAPH_SECTION), { title, text }));
822
- }
823
- for (const concept of graph.instancesOf(SKOS_CONCEPT)) {
824
- const pref = graph.literals(concept, SKOS_PREF_LABEL);
825
- const alt = graph.literals(concept, SKOS_ALT_LABEL);
826
- entries.push(
827
- entry(concept, compactIri(SKOS_CONCEPT), {
828
- title: pref[0],
829
- labels: joinLabels([...pref, ...alt])
830
- })
831
- );
832
- }
833
- entries.sort((a, b) => byCodeUnit(a.id, b.id));
834
- return { version: 1, entries };
835
- }
836
- function emitSearchIndex(doc) {
837
- return `${JSON.stringify(doc, null, 2)}
838
- `;
839
- }
840
- function partitionByLanguage(graph, index) {
841
- const documentType = compactIri(GRAPH_DOCUMENT2);
842
- const sectionType = compactIri(GRAPH_SECTION);
843
- const languageOf = (entry2) => {
844
- if (entry2.type !== documentType && entry2.type !== sectionType) {
845
- return void 0;
846
- }
847
- const hash = entry2.id.indexOf("#");
848
- const doc = hash === -1 ? entry2.id : entry2.id.slice(0, hash);
849
- return languageOfDocument(graph, doc);
850
- };
851
- const out = /* @__PURE__ */ new Map();
852
- const shared = [];
853
- for (const entry2 of index.entries) {
854
- const language = languageOf(entry2);
855
- if (language === void 0) {
856
- shared.push(entry2);
857
- continue;
858
- }
859
- const bucket = out.get(language);
860
- if (bucket) bucket.entries.push(entry2);
861
- else out.set(language, { version: 1, entries: [entry2] });
862
- }
863
- if (out.size === 0 && shared.length > 0) {
864
- out.set(UNDETERMINED, { version: 1, entries: [] });
865
- }
866
- for (const bucket of out.values()) {
867
- bucket.entries.push(...shared);
868
- bucket.entries.sort((a, b) => byCodeUnit(a.id, b.id));
869
- }
870
- return out;
871
- }
872
- function languageOfDocument(graph, doc) {
873
- const tag = graph.literal(doc, DCTERMS_LANGUAGE);
874
- return tag === void 0 ? UNDETERMINED : canonicalLanguageTag(tag);
875
- }
876
- function documentsByLanguage(graph) {
877
- const out = /* @__PURE__ */ new Map();
878
- for (const doc of graph.instancesOf(GRAPH_DOCUMENT2)) {
879
- const language = languageOfDocument(graph, doc);
880
- out.set(language, (out.get(language) ?? 0) + 1);
881
- }
882
- return out;
883
- }
884
-
885
- // src/graph/runtime/graph.ts
886
- var XSD_STRING4 = "http://www.w3.org/2001/XMLSchema#string";
887
- function compareValues(a, b) {
888
- if (a.kind !== b.kind) return a.kind === "iri" ? -1 : 1;
889
- const v = byCodeUnit(a.value, b.value);
890
- if (v !== 0) return v;
891
- const da = a.kind === "literal" ? a.datatype ?? "" : "";
892
- const db = b.kind === "literal" ? b.datatype ?? "" : "";
893
- return byCodeUnit(da, db);
894
- }
895
- var GraphIndex = class _GraphIndex {
896
- nodes = /* @__PURE__ */ new Map();
897
- /** subject → predicate → object IRIs (sorted). */
898
- outbound = /* @__PURE__ */ new Map();
899
- /** object → predicate → subject IRIs (sorted). */
900
- inbound = /* @__PURE__ */ new Map();
901
- /** class IRI → instance IRIs (sorted). */
902
- byType = /* @__PURE__ */ new Map();
903
- /** prefix → namespace, from the artifact's own `@context`. */
904
- context;
905
- constructor(context) {
906
- this.context = context;
907
- }
908
- /**
909
- * Build from a `graph.jsonld` document — a JSON string or the parsed object.
910
- * CURIE keys and `@type` values are expanded using the document's own
911
- * `@context`, so the index is self-describing.
912
- */
913
- static fromJsonLd(input) {
914
- const doc = typeof input === "string" ? JSON.parse(input) : input;
915
- const context = doc["@context"] ?? {};
916
- const index = new _GraphIndex({ ...context });
917
- for (const raw of doc["@graph"] ?? []) {
918
- index.addJsonLdNode(raw);
919
- }
920
- index.finalize();
921
- return index;
922
- }
923
- /** Build from dockg's internal quad shape (the Node/CLI path). */
924
- static fromQuads(quads, context = {}) {
925
- const index = new _GraphIndex({ ...context });
926
- for (const q of quads) {
927
- const o = q.o;
928
- const value = o.kind === "iri" ? { kind: "iri", value: o.value } : o.datatype && o.datatype !== XSD_STRING4 ? { kind: "literal", value: o.value, datatype: o.datatype } : { kind: "literal", value: o.value };
929
- index.addTriple(q.s, q.p, value);
930
- }
931
- index.finalize();
932
- return index;
933
- }
934
- /** Expand a CURIE (`dcterms:title`) to a full IRI using `@context`. */
935
- expand(term) {
936
- const colon = term.indexOf(":");
937
- if (colon <= 0) return term;
938
- const ns = this.context[term.slice(0, colon)];
939
- return ns ? `${ns}${term.slice(colon + 1)}` : term;
940
- }
941
- /** Compact a full IRI to a CURIE when a context namespace matches. */
942
- compact(iri2) {
943
- let best = iri2;
944
- let bestNs = "";
945
- for (const [prefix, ns] of Object.entries(this.context)) {
946
- if (iri2.startsWith(ns) && iri2.length > ns.length && ns.length > bestNs.length) {
947
- best = `${prefix}:${iri2.slice(ns.length)}`;
948
- bestNs = ns;
949
- }
950
- }
951
- return best;
952
- }
953
- addJsonLdNode(raw) {
954
- const id = typeof raw["@id"] === "string" ? this.expand(raw["@id"]) : "";
955
- if (!id) return;
956
- this.ensureNode(id);
957
- const rawTypes = raw["@type"];
958
- for (const t of toArray(rawTypes)) {
959
- if (typeof t === "string") {
960
- this.addTriple(id, RDF_TYPE, { kind: "iri", value: this.expand(t) });
961
- }
962
- }
963
- for (const [key, rawValue] of Object.entries(raw)) {
964
- if (key === "@id" || key === "@type") continue;
965
- const predicate = this.expand(key);
966
- for (const v of toArray(rawValue)) {
967
- const value = this.toValue(v);
968
- if (value) this.addTriple(id, predicate, value);
969
- }
970
- }
971
- }
972
- toValue(v) {
973
- if (typeof v === "string") return { kind: "literal", value: v };
974
- if (typeof v === "number" || typeof v === "boolean") {
975
- return { kind: "literal", value: String(v) };
976
- }
977
- if (v && typeof v === "object") {
978
- const o = v;
979
- if (typeof o["@id"] === "string") {
980
- return { kind: "iri", value: this.expand(o["@id"]) };
981
- }
982
- if (o["@value"] !== void 0) {
983
- const raw = o["@value"];
984
- const lexical = typeof raw === "string" ? raw : JSON.stringify(raw);
985
- const dt = typeof o["@type"] === "string" ? this.expand(o["@type"]) : void 0;
986
- return dt && dt !== XSD_STRING4 ? { kind: "literal", value: lexical, datatype: dt } : { kind: "literal", value: lexical };
987
- }
988
- }
989
- return void 0;
990
- }
991
- ensureNode(id) {
992
- let node = this.nodes.get(id);
993
- if (!node) {
994
- node = { id, types: [], props: /* @__PURE__ */ new Map() };
995
- this.nodes.set(id, node);
996
- }
997
- return node;
998
- }
999
- addTriple(s, p, o) {
1000
- const node = this.ensureNode(s);
1001
- let values = node.props.get(p);
1002
- if (!values) {
1003
- values = [];
1004
- node.props.set(p, values);
1005
- }
1006
- values.push(o);
1007
- if (p === RDF_TYPE && o.kind === "iri") {
1008
- node.types.push(o.value);
1009
- const instances = this.byType.get(o.value);
1010
- if (instances) instances.push(s);
1011
- else this.byType.set(o.value, [s]);
1012
- }
1013
- if (o.kind === "iri") {
1014
- addEdge(this.outbound, s, p, o.value);
1015
- addEdge(this.inbound, o.value, p, s);
1016
- this.ensureNode(o.value);
1017
- }
1018
- }
1019
- /** Sort every collection so traversal order is deterministic. */
1020
- finalize() {
1021
- for (const node of this.nodes.values()) {
1022
- node.types.sort(byCodeUnit);
1023
- for (const values of node.props.values()) values.sort(compareValues);
1024
- }
1025
- for (const instances of this.byType.values()) instances.sort(byCodeUnit);
1026
- for (const map of [this.outbound, this.inbound]) {
1027
- for (const preds of map.values()) {
1028
- for (const targets of preds.values()) targets.sort(byCodeUnit);
1029
- }
1030
- }
1031
- }
1032
- /** Every node IRI, sorted. */
1033
- ids() {
1034
- return [...this.nodes.keys()].sort(byCodeUnit);
1035
- }
1036
- size() {
1037
- return this.nodes.size;
1038
- }
1039
- node(iri2) {
1040
- return this.nodes.get(iri2);
1041
- }
1042
- has(iri2) {
1043
- return this.nodes.has(iri2);
1044
- }
1045
- /** Class IRIs of a node, sorted. */
1046
- types(iri2) {
1047
- return this.nodes.get(iri2)?.types ?? [];
1048
- }
1049
- /** Instances of a class, sorted. */
1050
- instancesOf(classIri) {
1051
- return this.byType.get(classIri) ?? [];
1052
- }
1053
- /** All values for a predicate, sorted. */
1054
- values(iri2, predicate) {
1055
- return this.nodes.get(iri2)?.props.get(predicate) ?? [];
1056
- }
1057
- /** Literal lexical forms for a predicate, sorted. */
1058
- literals(iri2, predicate) {
1059
- return this.values(iri2, predicate).filter((v) => v.kind === "literal").map((v) => v.value);
1060
- }
1061
- /** The first literal for a predicate, if any. */
1062
- literal(iri2, predicate) {
1063
- return this.literals(iri2, predicate)[0];
1064
- }
1065
- /** Outbound IRI targets: all predicates, or one. Sorted. */
1066
- out(iri2, predicate) {
1067
- return edges(this.outbound, iri2, predicate);
1068
- }
1069
- /** Inbound IRI sources (who points at this node). Sorted. */
1070
- in(iri2, predicate) {
1071
- return edges(this.inbound, iri2, predicate);
1072
- }
1073
- };
1074
- function addEdge(map, key, predicate, target) {
1075
- let preds = map.get(key);
1076
- if (!preds) {
1077
- preds = /* @__PURE__ */ new Map();
1078
- map.set(key, preds);
1079
- }
1080
- const targets = preds.get(predicate);
1081
- if (targets) targets.push(target);
1082
- else preds.set(predicate, [target]);
1083
- }
1084
- function edges(map, iri2, predicate) {
1085
- const preds = map.get(iri2);
1086
- if (!preds) return [];
1087
- const out = [];
1088
- const keys = predicate ? [predicate] : [...preds.keys()].sort(byCodeUnit);
1089
- for (const p of keys) {
1090
- for (const target of preds.get(p) ?? []) out.push({ predicate: p, target });
1091
- }
1092
- return out;
1093
- }
1094
- function toArray(v) {
1095
- if (v === void 0 || v === null) return [];
1096
- return Array.isArray(v) ? v : [v];
1097
- }
1098
-
1099
499
  // src/graph/commands/export.ts
1100
- var EXPORT_TARGETS = ["jsonld", "iirds", "search"];
500
+ var EXPORT_TARGETS = ["jsonld", "iirds", "search", "questions"];
1101
501
  var EXTENSION = {
1102
502
  jsonld: ".jsonld",
1103
503
  iirds: ".iirds"
@@ -1110,7 +510,7 @@ function runJsonLd(cwd, graphPath, out) {
1110
510
  const quads = storeToQuads(store);
1111
511
  const serialized = emitJsonLd(quads);
1112
512
  const nodes = new Set(quads.map((q) => q.s)).size;
1113
- const outPath = out ? resolve3(cwd, out) : withExtension(graphPath, EXTENSION.jsonld);
513
+ const outPath = out ? resolve2(cwd, out) : withExtension(graphPath, EXTENSION.jsonld);
1114
514
  mkdirSync(dirname(outPath), { recursive: true });
1115
515
  writeFileSync(outPath, serialized, "utf8");
1116
516
  return { outPath, format: "jsonld", nodes, warnings: [] };
@@ -1128,7 +528,7 @@ function runIirds(cwd, sources, graphPath, iirds, baseIri, out) {
1128
528
  sources
1129
529
  );
1130
530
  const metadata = emitRdfXml(projection.quads, projection.prefixes);
1131
- const content = [...projection.contentFiles].sort((a, b) => byCodeUnit(a.zipPath, b.zipPath)).map((f) => ({ name: f.zipPath, data: readFileSync2(f.absPath) }));
531
+ const content = [...projection.contentFiles].sort((a, b) => byCodeUnit(a.zipPath, b.zipPath)).map((f) => ({ name: f.zipPath, data: readFileSync(f.absPath) }));
1132
532
  const entries = [
1133
533
  {
1134
534
  name: "mimetype",
@@ -1138,7 +538,7 @@ function runIirds(cwd, sources, graphPath, iirds, baseIri, out) {
1138
538
  { name: "META-INF/metadata.rdf", data: Buffer.from(metadata, "utf8") },
1139
539
  ...content
1140
540
  ];
1141
- const outPath = out ? resolve3(cwd, out) : withExtension(graphPath, EXTENSION.iirds);
541
+ const outPath = out ? resolve2(cwd, out) : withExtension(graphPath, EXTENSION.iirds);
1142
542
  mkdirSync(dirname(outPath), { recursive: true });
1143
543
  writeFileSync(outPath, writeZip(entries));
1144
544
  const nodes = new Set(projection.quads.map((q) => q.s)).size;
@@ -1157,7 +557,7 @@ function runSearchIndex(cwd, sources, graphPath, out) {
1157
557
  const index = buildSearchIndex(graph, sources, { warnings });
1158
558
  const byLanguage = partitionByLanguage(graph, index);
1159
559
  const docCounts = documentsByLanguage(graph);
1160
- const outDir = out ? resolve3(cwd, out) : dirname(graphPath);
560
+ const outDir = out ? resolve2(cwd, out) : dirname(graphPath);
1161
561
  mkdirSync(outDir, { recursive: true });
1162
562
  const languages = [];
1163
563
  for (const [language, doc] of byLanguage) {
@@ -1192,12 +592,24 @@ function runSearchIndex(cwd, sources, graphPath, out) {
1192
592
  warnings
1193
593
  };
1194
594
  }
1195
- function runExport(opts) {
1196
- return new Promise((ok) => {
1197
- ok(exportOnce(opts));
1198
- });
595
+ async function runQuestions(cwd, sources, graphPath, config, source, out) {
596
+ const { lines, stale } = await exportSampleQuestions(
597
+ loadGraph(graphPath),
598
+ sources,
599
+ { source, cwd },
600
+ config.questionFiles,
601
+ config.configDir
602
+ );
603
+ const outPath = out ? resolve2(cwd, out) : join(dirname(graphPath), "questions.jsonl");
604
+ mkdirSync(dirname(outPath), { recursive: true });
605
+ writeFileSync(outPath, lines.map((l) => `${l}
606
+ `).join(""), "utf8");
607
+ const warnings = stale === 0 ? [] : [
608
+ stale === 1 ? "1 answer changed since it was pinned, so it is exported as it reads now, marked stale." : `${String(stale)} answers changed since they were pinned, so they are exported as they read now, marked stale.`
609
+ ];
610
+ return { outPath, format: "questions", nodes: lines.length, warnings };
1199
611
  }
1200
- function exportOnce(opts) {
612
+ async function runExport(opts) {
1201
613
  const cwd = opts.cwd ?? process.cwd();
1202
614
  if (!EXPORT_TARGETS.includes(opts.format)) {
1203
615
  throw new GraphError(
@@ -1211,12 +623,25 @@ function exportOnce(opts) {
1211
623
  },
1212
624
  cwd
1213
625
  );
1214
- const graphPath = resolve3(cwd, opts.graph ?? config.out);
626
+ const graphPath = resolve2(cwd, opts.graph ?? config.out);
1215
627
  const sources = documentBase(config, {}, cwd);
1216
628
  if (opts.format === "jsonld") return runJsonLd(cwd, graphPath, opts.out);
1217
629
  if (opts.format === "search") {
1218
630
  return runSearchIndex(cwd, sources, graphPath, opts.out);
1219
631
  }
632
+ if (opts.format === "questions") {
633
+ return runQuestions(
634
+ cwd,
635
+ sources,
636
+ graphPath,
637
+ config,
638
+ {
639
+ ...opts.config === void 0 ? {} : { configPath: opts.config },
640
+ ...opts.noConfig === void 0 ? {} : { noConfig: opts.noConfig }
641
+ },
642
+ opts.out
643
+ );
644
+ }
1220
645
  return runIirds(
1221
646
  cwd,
1222
647
  sources,
@@ -1228,7 +653,7 @@ function exportOnce(opts) {
1228
653
  }
1229
654
 
1230
655
  // src/graph/commands/query.ts
1231
- import { resolve as resolve4 } from "node:path";
656
+ import { resolve as resolve3 } from "node:path";
1232
657
  import { DataFactory as DataFactory2 } from "n3";
1233
658
  function runQuery(opts = {}) {
1234
659
  const cwd = opts.cwd ?? process.cwd();
@@ -1239,7 +664,7 @@ function runQuery(opts = {}) {
1239
664
  },
1240
665
  cwd
1241
666
  );
1242
- const store = loadGraph(resolve4(cwd, opts.graph ?? config.out));
667
+ const store = loadGraph(resolve3(cwd, opts.graph ?? config.out));
1243
668
  const s = opts.s ? DataFactory2.namedNode(expandTerm(opts.s)) : null;
1244
669
  const p = opts.p ? DataFactory2.namedNode(expandTerm(opts.p)) : null;
1245
670
  const oValue = opts.o ? expandTerm(opts.o) : null;
@@ -1253,12 +678,13 @@ function runQuery(opts = {}) {
1253
678
  o: obj.termType === "Literal" ? {
1254
679
  kind: "literal",
1255
680
  value: obj.value,
1256
- ...obj.datatype.value !== "http://www.w3.org/2001/XMLSchema#string" ? { datatype: obj.datatype.value } : {}
681
+ ...obj.language !== "" ? { language: obj.language } : {},
682
+ ...obj.language === "" && obj.datatype.value !== "http://www.w3.org/2001/XMLSchema#string" ? { datatype: obj.datatype.value } : {}
1257
683
  } : { kind: "iri", value: obj.value }
1258
684
  });
1259
685
  }
1260
686
  matches.sort(
1261
- (a, b) => byCodeUnit(a.s, b.s) || byCodeUnit(a.p, b.p) || byCodeUnit(a.o.kind, b.o.kind) || byCodeUnit(a.o.value, b.o.value)
687
+ (a, b) => byCodeUnit(a.s, b.s) || byCodeUnit(a.p, b.p) || byCodeUnit(a.o.kind, b.o.kind) || byCodeUnit(a.o.value, b.o.value) || byCodeUnit(a.o.language ?? "", b.o.language ?? "")
1262
688
  );
1263
689
  return { matches };
1264
690
  }
@@ -1266,7 +692,7 @@ function renderQuery(result, format) {
1266
692
  if (format === "json") return JSON.stringify(result, null, 2);
1267
693
  if (result.matches.length === 0) return "No matches.";
1268
694
  const lines = result.matches.map((m) => {
1269
- const o = m.o.kind === "iri" ? `<${m.o.value}>` : `"${m.o.value}"${m.o.datatype ? `^^${compactIri(m.o.datatype)}` : ""}`;
695
+ const o = m.o.kind === "iri" ? `<${m.o.value}>` : `"${m.o.value}"${m.o.language ? `@${m.o.language}` : m.o.datatype ? `^^${compactIri(m.o.datatype)}` : ""}`;
1270
696
  return `<${m.s}> ${compactIri(m.p)} ${o}`;
1271
697
  });
1272
698
  lines.push(`
@@ -1275,12 +701,20 @@ ${result.matches.length} match(es)`);
1275
701
  }
1276
702
 
1277
703
  // src/graph/commands/fill.ts
1278
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
704
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
1279
705
  import { readFile } from "node:fs/promises";
1280
706
  import { resolve as resolve6 } from "node:path";
1281
707
 
1282
708
  // src/graph/core/frontmatter-edit.ts
1283
- import { Document, YAMLMap, YAMLSeq, isMap, parseDocument } from "yaml";
709
+ import {
710
+ Document,
711
+ YAMLMap,
712
+ YAMLSeq,
713
+ isCollection,
714
+ isMap,
715
+ parseDocument,
716
+ visit
717
+ } from "yaml";
1284
718
  var GRAPH_KEY = "graph";
1285
719
  function frontmatterKind(content) {
1286
720
  const body = content.charCodeAt(0) === 65279 ? content.slice(1) : content;
@@ -1354,7 +788,7 @@ function applyGraphFields(content, path2, values, options = {}) {
1354
788
  });
1355
789
  const graph2 = doc2.get("graph", true);
1356
790
  if (isMap(graph2)) flowSeqs(graph2);
1357
- let block = doc2.toString();
791
+ let block = doc2.toString({ lineWidth: 0 });
1358
792
  if (eol === "\r\n") block = block.replace(/(?<!\r)\n/g, "\r\n");
1359
793
  return {
1360
794
  content: `---${eol}${block}---${eol}${eol}${content}`,
@@ -1384,10 +818,26 @@ function applyGraphFields(content, path2, values, options = {}) {
1384
818
  if (applied.length === 0 && pageEntries.length === 0) {
1385
819
  return { content, applied, skipped };
1386
820
  }
1387
- let newBlock = doc.toString();
821
+ let newBlock = doc.toString({
822
+ flowCollectionPadding: flowPadding(doc, split.block),
823
+ lineWidth: 0
824
+ });
1388
825
  if (split.eol === "\r\n") newBlock = newBlock.replace(/(?<!\r)\n/g, "\r\n");
1389
826
  return { content: split.open + newBlock + split.suffix, applied, skipped };
1390
827
  }
828
+ function flowPadding(doc, block) {
829
+ let padded;
830
+ visit(doc, (_key, node) => {
831
+ if (!isCollection(node) || node.flow !== true || node.items.length === 0) {
832
+ return;
833
+ }
834
+ if (!node.range) return;
835
+ const start = node.range[0];
836
+ padded = /^[[{][ \t]/.test(block.slice(start, start + 2));
837
+ return visit.BREAK;
838
+ });
839
+ return padded ?? true;
840
+ }
1391
841
  function writable(values) {
1392
842
  return Object.entries(values).filter(
1393
843
  ([, v]) => v !== void 0 && v !== null && !(Array.isArray(v) && v.length === 0)
@@ -1483,8 +933,8 @@ function existingGraphFields(content) {
1483
933
  }
1484
934
 
1485
935
  // src/graph/core/fill-guard.ts
1486
- import { readFileSync as readFileSync3 } from "node:fs";
1487
- import { resolve as resolve5 } from "node:path";
936
+ import { readFileSync as readFileSync2 } from "node:fs";
937
+ import { resolve as resolve4 } from "node:path";
1488
938
  import { DataFactory as DataFactory3, Store } from "n3";
1489
939
  var GUARDED_FIELDS = [
1490
940
  "label",
@@ -1510,7 +960,7 @@ function toStore(quads) {
1510
960
  DataFactory3.namedNode(q.p),
1511
961
  q.o.kind === "iri" ? DataFactory3.namedNode(q.o.value) : DataFactory3.literal(
1512
962
  q.o.value,
1513
- q.o.datatype ? DataFactory3.namedNode(q.o.datatype) : void 0
963
+ q.o.language ?? (q.o.datatype ? DataFactory3.namedNode(q.o.datatype) : void 0)
1514
964
  )
1515
965
  )
1516
966
  );
@@ -1573,7 +1023,7 @@ var FillGuard = class _FillGuard {
1573
1023
  guard.models.set(
1574
1024
  path2,
1575
1025
  analyzeDoc(
1576
- inline.get(path2) ?? readFileSync3(resolve5(cwd, path2), "utf8"),
1026
+ inline.get(path2) ?? readFileSync2(resolve4(cwd, path2), "utf8"),
1577
1027
  path2,
1578
1028
  guard.allPaths,
1579
1029
  inputs.has(path2) ? guard.analyzeOptions : byExtension
@@ -1722,6 +1172,7 @@ import { JsonCache, buildCacheKey, sha256 } from "@hawkeyexl/inference";
1722
1172
 
1723
1173
  // src/graph/llm/prompt.ts
1724
1174
  var PROMPT_VERSION = 4;
1175
+ var QUESTION_PROMPT_VERSION = 1;
1725
1176
  var TOPIC_TYPES = [
1726
1177
  "task",
1727
1178
  "concept",
@@ -1917,6 +1368,73 @@ function buildUserPrompt(doc, body, fields, options = {}) {
1917
1368
  body.slice(0, EXCERPT_CHARS)
1918
1369
  ].join("\n");
1919
1370
  }
1371
+ var QUESTION_SYSTEM_PROMPT = [
1372
+ "You write and answer the questions a reader asks of a documentation page.",
1373
+ "Every answer is a passage quoted VERBATIM from the page text you are given:",
1374
+ "copy it exactly, as one contiguous run of the text, and quote no more than",
1375
+ "the passage that answers. Never paraphrase a quote.",
1376
+ "",
1377
+ "For each numbered question, quote the passage that answers it, or an empty",
1378
+ "string when no passage in this text does. Draft only as many new questions",
1379
+ "as asked. Ask them as a reader would, in a reader's own words rather than",
1380
+ "the page's, and only questions this text answers. For every pair and draft,",
1381
+ "give a `confidence` (0..1) that the quote answers the question. Score",
1382
+ "honestly and conservatively."
1383
+ ].join("\n");
1384
+ function buildQuestionPrompt(request, chunk) {
1385
+ const concepts = request.concepts.map(
1386
+ (c) => c.altLabels.length > 0 ? `- ${c.label} (also: ${c.altLabels.join(", ")})` : `- ${c.label}`
1387
+ );
1388
+ return [
1389
+ `Path: ${request.path}`,
1390
+ `Title: ${request.title}`,
1391
+ ...concepts.length > 0 ? ["", "Concepts this page is about:", ...concepts] : [],
1392
+ "",
1393
+ request.pair.length > 0 ? `Questions to pair, by number:
1394
+ ${request.pair.map((q, i) => `${String(i + 1)}. ${q}`).join("\n")}` : "Questions to pair: none.",
1395
+ "",
1396
+ request.draft > 0 ? `Draft ${String(request.draft)} new question${request.draft === 1 ? "" : "s"}, each with the quote that answers it.` : "Draft no new questions.",
1397
+ "",
1398
+ "Page text:",
1399
+ chunk
1400
+ ].join("\n");
1401
+ }
1402
+ function questionSchema(lenient = false) {
1403
+ const confidence = lenient ? {} : { type: "number", minimum: 0, maximum: 1 };
1404
+ const item = (properties, optional = []) => ({
1405
+ type: "object",
1406
+ additionalProperties: false,
1407
+ required: Object.keys(properties).filter((k) => !lenient || !optional.includes(k)),
1408
+ properties
1409
+ });
1410
+ return item(
1411
+ {
1412
+ pairs: {
1413
+ type: "array",
1414
+ items: item(
1415
+ {
1416
+ index: { type: "integer", minimum: 1, description: "The question's number." },
1417
+ quote: { type: "string", description: "The verbatim passage that answers it, or an empty string." },
1418
+ confidence
1419
+ },
1420
+ ["confidence"]
1421
+ )
1422
+ },
1423
+ drafts: {
1424
+ type: "array",
1425
+ items: item(
1426
+ {
1427
+ question: { type: "string", minLength: 1, description: "A question a reader would ask." },
1428
+ quote: { type: "string", minLength: 1, description: "The verbatim passage that answers it." },
1429
+ confidence
1430
+ },
1431
+ ["confidence"]
1432
+ )
1433
+ }
1434
+ },
1435
+ ["pairs", "drafts"]
1436
+ );
1437
+ }
1920
1438
 
1921
1439
  // src/graph/llm/cache.ts
1922
1440
  function cacheKey(provider, model, content, fields, sections = false) {
@@ -1933,6 +1451,16 @@ function cacheKey(provider, model, content, fields, sections = false) {
1933
1451
  sections ? "sections" : "doc-only"
1934
1452
  ]);
1935
1453
  }
1454
+ function questionCacheKey(provider, model, chunk, request) {
1455
+ return buildCacheKey([
1456
+ provider,
1457
+ model,
1458
+ `q${QUESTION_PROMPT_VERSION}`,
1459
+ sha256(chunk),
1460
+ "questions",
1461
+ sha256(JSON.stringify(request))
1462
+ ]);
1463
+ }
1936
1464
  var FillCache = class extends JsonCache {
1937
1465
  constructor(dir, enabled = true) {
1938
1466
  super(dir, enabled, "manni");
@@ -1960,8 +1488,8 @@ function selectProvider2(config, flags = {}) {
1960
1488
  }
1961
1489
  ]);
1962
1490
  }
1963
- function announceSelection(selection) {
1964
- const message = localNotice(selection);
1491
+ function announceSelection(selection, flags = {}) {
1492
+ const message = localNotice(selection, flags);
1965
1493
  if (message !== void 0) noticeOnce(message);
1966
1494
  }
1967
1495
  function assertProviderSelection(selection) {
@@ -1976,16 +1504,19 @@ function assertProviderSelection(selection) {
1976
1504
  function providerSpecFor2(config, selection) {
1977
1505
  return providerSpecFor(config.providers, selection);
1978
1506
  }
1979
- async function resolveProviderIdentity(config, flags = {}) {
1507
+ function selectedSpec(config, flags) {
1980
1508
  const selection = selectProvider2(config, flags);
1981
1509
  const { provider, model } = selection;
1982
1510
  assertKnownProvider(provider, toGraphError);
1983
1511
  assertModelHasProvider(provider, model, CONFIG_KEY, toGraphError);
1984
- announceSelection(selection);
1985
- return resolveIdentity(
1986
- providerSpecFor2(config, { provider, model: model ?? null }),
1987
- toGraphError
1988
- );
1512
+ announceSelection(selection, flags);
1513
+ return providerSpecFor2(config, { provider, model: model ?? null });
1514
+ }
1515
+ async function resolveProviderIdentity(config, flags = {}) {
1516
+ return resolveIdentity(selectedSpec(config, flags), toGraphError);
1517
+ }
1518
+ async function resolveOfflineIdentity(config, flags) {
1519
+ return offlineIdentity(selectedSpec(config, flags), toGraphError);
1989
1520
  }
1990
1521
  function constructProvider(config, identity) {
1991
1522
  try {
@@ -1998,33 +1529,408 @@ async function makeProvider(config, flags = {}) {
1998
1529
  return constructProvider(config, await resolveProviderIdentity(config, flags));
1999
1530
  }
2000
1531
 
2001
- // src/graph/commands/fill.ts
2002
- var TURN_BUDGET_REASON = "turn budget";
2003
- var RELATION_FIELDS = [
2004
- "alt-labels",
2005
- "broader",
2006
- "narrower",
2007
- "related-concepts"
2008
- ];
2009
- function numberMap(v) {
2010
- const out = {};
2011
- if (v && typeof v === "object" && !Array.isArray(v)) {
2012
- for (const [k, val] of Object.entries(v)) {
2013
- if (typeof val === "number" && val >= 0 && val <= 1) out[k] = val;
2014
- }
2015
- }
2016
- return out;
1532
+ // src/graph/commands/fill-questions.ts
1533
+ import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
1534
+ import { resolve as resolve5 } from "node:path";
1535
+ import { isMap as isMap2, isSeq, parseDocument as parseDocument2 } from "yaml";
1536
+ var QUESTIONS_KEY = "sample-questions";
1537
+ function isRecord(value) {
1538
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2017
1539
  }
2018
- function stringMap(v) {
2019
- const out = {};
2020
- if (v && typeof v === "object" && !Array.isArray(v)) {
2021
- for (const [k, val] of Object.entries(v)) {
2022
- if (typeof val === "string") out[k] = val;
1540
+ function round2(n) {
1541
+ return Math.round(n * 100) / 100;
1542
+ }
1543
+ function score(value) {
1544
+ return typeof value === "number" && value >= 0 && value <= 1 ? value : 0;
1545
+ }
1546
+ function linesOf(pin) {
1547
+ const spec = pin.lines;
1548
+ return typeof spec === "number" || typeof spec === "string" ? parseLines(spec) : void 0;
1549
+ }
1550
+ function readProposal(value) {
1551
+ const list = (v) => Array.isArray(v) ? v.filter(isRecord) : [];
1552
+ return {
1553
+ pairs: list(value.pairs).flatMap(
1554
+ (p) => typeof p.index === "number" && typeof p.quote === "string" ? [{ index: p.index, quote: p.quote, confidence: score(p.confidence) }] : []
1555
+ ),
1556
+ drafts: list(value.drafts).flatMap(
1557
+ (d) => typeof d.question === "string" && d.question.trim() !== "" && typeof d.quote === "string" ? [{ question: d.question.trim(), quote: d.quote, confidence: score(d.confidence) }] : []
1558
+ )
1559
+ };
1560
+ }
1561
+ function locateQuote(body, quote) {
1562
+ const wanted = quote.replace(/\s+/gu, " ").trim();
1563
+ if (wanted === "") return void 0;
1564
+ const text = body.join("\n");
1565
+ let folded = "";
1566
+ const at = [];
1567
+ let space = false;
1568
+ for (let i = 0; i < text.length; i++) {
1569
+ const ch = text.charAt(i);
1570
+ if (/\s/u.test(ch)) {
1571
+ if (!space && folded !== "") {
1572
+ folded += " ";
1573
+ at.push(i);
1574
+ }
1575
+ space = true;
1576
+ } else {
1577
+ folded += ch;
1578
+ at.push(i);
1579
+ space = false;
1580
+ }
1581
+ }
1582
+ const found = folded.indexOf(wanted);
1583
+ if (found < 0) return void 0;
1584
+ const lineAt = (offset) => text.slice(0, offset).split("\n").length;
1585
+ const start = at[found];
1586
+ const end = at[found + wanted.length - 1];
1587
+ if (start === void 0 || end === void 0) return void 0;
1588
+ const twice = folded.indexOf(wanted, found + wanted.length) >= 0;
1589
+ return { start: lineAt(start), end: lineAt(end), ...twice ? { ambiguous: true } : {} };
1590
+ }
1591
+ function twiceWarning(path2, question, lines) {
1592
+ return `${path2}: the answer to "${question}" appears more than once, so ${spellLines(lines)} is pinned; check it is the right one.`;
1593
+ }
1594
+ function pinFor(body, lines) {
1595
+ return { lines: lineSpec(lines), integrity: pinOfLines(body, lines) };
1596
+ }
1597
+ function relocate(pins, before, after) {
1598
+ const delta = after.length - before.length;
1599
+ let moved = false;
1600
+ for (const pin of pins) {
1601
+ const lines = linesOf(pin);
1602
+ const integrity = pin.integrity;
1603
+ if (lines === void 0 || typeof integrity !== "string") continue;
1604
+ if (pinOfLines(after, lines) === integrity) continue;
1605
+ const length = lines.end - lines.start + 1;
1606
+ const around = lines.start + delta;
1607
+ const { starts } = findWindows(after, length, integrity, void 0, { around });
1608
+ const [first, ...rest] = starts;
1609
+ if (first === void 0) continue;
1610
+ const nearest = rest.reduce(
1611
+ (best, s) => Math.abs(s - around) < Math.abs(best - around) ? s : best,
1612
+ first
1613
+ );
1614
+ pin.lines = lineSpec({ start: nearest, end: nearest + length - 1 });
1615
+ moved = true;
1616
+ }
1617
+ return moved;
1618
+ }
1619
+ function settle(tracked) {
1620
+ return tracked.map(({ pin, ...outcome }) => {
1621
+ const lines = pin === void 0 ? void 0 : linesOf(pin);
1622
+ return lines === void 0 ? outcome : { ...outcome, lines: spellLines(lines) };
1623
+ });
1624
+ }
1625
+ async function fillPageQuestions(input) {
1626
+ const { path: path2, format, content, settings, asker, known } = input;
1627
+ const { body } = bodyOf(content, format);
1628
+ const held = metadataOf(content, path2, format).data[QUESTIONS_KEY];
1629
+ const raw = Array.isArray(held) ? held : held === void 0 ? [] : [held];
1630
+ const tracked = [];
1631
+ const warnings = [];
1632
+ const items = [];
1633
+ const pins = [];
1634
+ const needs = [];
1635
+ let questions = 0;
1636
+ let changed = false;
1637
+ for (const value of raw) {
1638
+ if (typeof value === "string" && value !== "") {
1639
+ questions++;
1640
+ needs.push({ at: items.length, question: value, stale: /* @__PURE__ */ new Set() });
1641
+ items.push(value);
1642
+ continue;
1643
+ }
1644
+ if (!isRecord(value) || typeof value.question !== "string" || value.question === "") {
1645
+ items.push(value);
1646
+ continue;
1647
+ }
1648
+ questions++;
1649
+ const question = value.question;
1650
+ const answers = Array.isArray(value.answers) ? value.answers.map((a) => isRecord(a) ? { ...a } : a) : [];
1651
+ const item = { ...value };
1652
+ if (Array.isArray(value.answers)) item.answers = answers;
1653
+ const stale = /* @__PURE__ */ new Set();
1654
+ for (const answer of answers) {
1655
+ if (!isRecord(answer)) continue;
1656
+ const lines = linesOf(answer);
1657
+ if (lines === void 0) continue;
1658
+ const integrity = answer.integrity;
1659
+ if (typeof integrity !== "string") {
1660
+ const minted = pinOfLines(body, lines);
1661
+ if (minted === void 0) {
1662
+ stale.add(answer);
1663
+ continue;
1664
+ }
1665
+ answer.integrity = minted;
1666
+ pins.push(answer);
1667
+ tracked.push({ action: "re-pinned", question, pin: answer });
1668
+ changed = true;
1669
+ continue;
1670
+ }
1671
+ const fresh = freshness(body, { lines, integrity, reviewed: true });
1672
+ if (fresh.state === "changed") {
1673
+ stale.add(answer);
1674
+ continue;
1675
+ }
1676
+ pins.push(answer);
1677
+ const [range] = fresh.ranges;
1678
+ if (fresh.state === "moved" && range !== void 0) {
1679
+ answer.lines = lineSpec(range);
1680
+ tracked.push({ action: "re-pinned", question, from: spellLines(lines), pin: answer });
1681
+ changed = true;
1682
+ } else if (fresh.state === "ambiguous") {
1683
+ warnings.push(
1684
+ `${path2}: the answer to "${question}" moved, and ${matchList(fresh.ranges)}, so pin one of them by hand.`
1685
+ );
1686
+ }
1687
+ }
1688
+ if (answers.length === 0 || stale.size > 0) {
1689
+ needs.push({ at: items.length, question, stale });
1690
+ }
1691
+ items.push(item);
1692
+ }
1693
+ const draft = Math.max(0, settings.perPage - questions);
1694
+ let state = "complete";
1695
+ let skipped;
1696
+ if (needs.length > 0 || draft > 0) {
1697
+ const proposals = asker === void 0 ? "offline" : await ask(asker);
1698
+ if (typeof proposals === "string" || asker === void 0) {
1699
+ skipped = typeof proposals === "string" ? proposals : "offline";
1700
+ state = "skipped";
1701
+ } else {
1702
+ state = "unchanged";
1703
+ if (pair(proposals)) changed = true;
1704
+ if (drafts(proposals, asker)) changed = true;
1705
+ }
1706
+ }
1707
+ if (!changed) {
1708
+ return { content, outcomes: settle(tracked), state, ...skipped ? { skipped } : {}, warnings };
1709
+ }
1710
+ const write = () => writeGraphFields(content, path2, format, {}, { page: { [QUESTIONS_KEY]: items } }).content;
1711
+ let next = write();
1712
+ let before = body;
1713
+ for (let round = 0; ; round++) {
1714
+ const after = bodyOf(next, format).body;
1715
+ if (!relocate(pins, before, after)) break;
1716
+ if (round === 2) {
1717
+ next = write();
1718
+ warnings.push(`${path2}: the answer pins did not settle, so check their lines.`);
1719
+ break;
1720
+ }
1721
+ before = after;
1722
+ next = write();
1723
+ }
1724
+ return {
1725
+ content: next,
1726
+ outcomes: settle(tracked),
1727
+ state: "changed",
1728
+ ...skipped ? { skipped } : {},
1729
+ warnings
1730
+ };
1731
+ async function ask(model) {
1732
+ const request = {
1733
+ path: path2,
1734
+ title: input.title,
1735
+ concepts: await input.concepts(),
1736
+ pair: needs.map((n) => n.question),
1737
+ draft
1738
+ };
1739
+ const out = [];
1740
+ for (const chunk of splitBody(body.join("\n"), settings.chunkChars)) {
1741
+ const answer = await model.ask(request, chunk);
1742
+ if (answer === void 0) return "turn budget";
1743
+ out.push(readProposal(answer));
1744
+ }
1745
+ return out;
1746
+ }
1747
+ function pair(proposals) {
1748
+ let wrote = false;
1749
+ for (const [n, need] of needs.entries()) {
1750
+ const action = need.stale.size > 0 ? "re-paired" : "paired";
1751
+ const question = need.question;
1752
+ const candidates = proposals.flatMap((p) => p.pairs.filter((c) => c.index === n + 1 && c.quote.trim() !== "")).sort((a, b) => b.confidence - a.confidence);
1753
+ if (candidates.length === 0) {
1754
+ tracked.push({ action: "not-paired", question, reason: "no passage answers it" });
1755
+ continue;
1756
+ }
1757
+ let found;
1758
+ for (const c of candidates) {
1759
+ const lines = locateQuote(body, c.quote);
1760
+ if (lines !== void 0) {
1761
+ if (lines.ambiguous) warnings.push(twiceWarning(path2, question, lines));
1762
+ found = { lines: { start: lines.start, end: lines.end }, confidence: c.confidence };
1763
+ break;
1764
+ }
1765
+ }
1766
+ if (found === void 0) {
1767
+ tracked.push({ action: "not-paired", question, reason: "answer not on page" });
1768
+ continue;
1769
+ }
1770
+ const confidence = round2(found.confidence);
1771
+ if (found.confidence < settings.confidenceThreshold) {
1772
+ tracked.push({ action: "not-paired", question, confidence, reason: "low confidence, not written" });
1773
+ continue;
1774
+ }
1775
+ const pin = { ...pinFor(body, found.lines), reviewed: false };
1776
+ pins.push(pin);
1777
+ const item = items[need.at];
1778
+ if (typeof item === "string") {
1779
+ items[need.at] = { question: item, answers: [pin] };
1780
+ } else if (isRecord(item)) {
1781
+ const kept = Array.isArray(item.answers) ? item.answers.filter((a) => !need.stale.has(a)) : [];
1782
+ item.answers = [...kept, pin];
1783
+ }
1784
+ tracked.push({ action, question, confidence, pin });
1785
+ wrote = true;
1786
+ }
1787
+ return wrote;
1788
+ }
1789
+ function drafts(proposals, model) {
1790
+ const pool = proposals.flatMap((p) => p.drafts).sort((a, b) => b.confidence - a.confidence);
1791
+ let accepted = 0;
1792
+ for (const d of pool) {
1793
+ if (accepted >= draft) break;
1794
+ const question = d.question;
1795
+ const confidence = round2(d.confidence);
1796
+ const located = locateQuote(body, d.quote);
1797
+ if (located === void 0) {
1798
+ tracked.push({ action: "dropped", question, reason: "answer not on page" });
1799
+ continue;
1800
+ }
1801
+ if (located.ambiguous) warnings.push(twiceWarning(path2, question, located));
1802
+ const lines = { start: located.start, end: located.end };
1803
+ const key = questionKey(question);
1804
+ const asked = key === "" ? void 0 : known.get(key);
1805
+ if (asked !== void 0) {
1806
+ tracked.push({ action: "dropped", question, reason: `duplicate of ${asked}` });
1807
+ continue;
1808
+ }
1809
+ const copied = copiedPairs(question, body.slice(lines.start - 1, lines.end).join("\n"));
1810
+ if (copied && copied.copied / copied.of > COPIED_SHARE) {
1811
+ tracked.push({
1812
+ action: "dropped",
1813
+ question,
1814
+ reason: `copies its answer (${String(copied.copied)} of ${String(copied.of)} word pairs)`
1815
+ });
1816
+ continue;
1817
+ }
1818
+ if (d.confidence < settings.confidenceThreshold) {
1819
+ tracked.push({ action: "dropped", question, confidence, reason: "low confidence, not written" });
1820
+ continue;
1821
+ }
1822
+ const pin = pinFor(body, lines);
1823
+ pins.push(pin);
1824
+ items.push({
1825
+ question,
1826
+ answers: [pin],
1827
+ ...settings.writeProvenance ? { "generated-by": model.generatedBy, confidence } : {},
1828
+ reviewed: false
1829
+ });
1830
+ if (key !== "") known.set(key, path2);
1831
+ tracked.push({ action: "drafted", question, confidence, pin });
1832
+ accepted++;
1833
+ }
1834
+ return accepted > 0;
1835
+ }
1836
+ }
1837
+ function questionsIn(content, path2, format) {
1838
+ const held = metadataOf(content, path2, format).data[QUESTIONS_KEY];
1839
+ const raw = Array.isArray(held) ? held : held === void 0 ? [] : [held];
1840
+ return raw.flatMap(
1841
+ (v) => typeof v === "string" ? [v] : isRecord(v) && typeof v.question === "string" ? [v.question] : []
1842
+ );
1843
+ }
1844
+ function questionsInFile(abs, label) {
1845
+ return readQuestionFile(abs, label).map((q) => q.question);
1846
+ }
1847
+ function repinQuestionFile(abs, label, configDir, dryRun) {
1848
+ const text = existsSync2(abs) ? readFileSync3(abs, "utf8") : void 0;
1849
+ readQuestionFile(abs, label, text);
1850
+ const doc = parseDocument2(text ?? "");
1851
+ const outcomes = [];
1852
+ const warnings = [];
1853
+ const bodies = /* @__PURE__ */ new Map();
1854
+ const bodyAt = (file) => {
1855
+ if (!bodies.has(file)) {
1856
+ const page = resolve5(configDir, file);
1857
+ bodies.set(
1858
+ file,
1859
+ existsSync2(page) ? bodyOf(readFileSync3(page, "utf8"), formatOf(file)).body : void 0
1860
+ );
1861
+ }
1862
+ return bodies.get(file);
1863
+ };
1864
+ let changed = false;
1865
+ const seq = doc.contents;
1866
+ for (const node of isSeq(seq) ? seq.items : []) {
1867
+ if (!isMap2(node)) continue;
1868
+ const question = node.get("question");
1869
+ const answers = node.get("answers", true);
1870
+ if (typeof question !== "string" || !isSeq(answers)) continue;
1871
+ for (const answer of answers.items) {
1872
+ if (!isMap2(answer)) continue;
1873
+ const file = answer.get("file");
1874
+ const spec = answer.get("lines");
1875
+ const lines = typeof spec === "number" || typeof spec === "string" ? parseLines(spec) : void 0;
1876
+ if (typeof file !== "string" || lines === void 0) continue;
1877
+ const body = bodyAt(file);
1878
+ if (body === void 0) continue;
1879
+ const integrity = answer.get("integrity");
1880
+ if (typeof integrity !== "string") {
1881
+ const minted = pinOfLines(body, lines);
1882
+ if (minted === void 0) continue;
1883
+ answer.set("integrity", minted);
1884
+ outcomes.push({ action: "re-pinned", question, file, lines: spellLines(lines) });
1885
+ changed = true;
1886
+ continue;
1887
+ }
1888
+ const fresh = freshness(body, { lines, integrity, reviewed: true });
1889
+ if (fresh.state === "changed") continue;
1890
+ const [range] = fresh.ranges;
1891
+ if (fresh.state === "moved" && range !== void 0) {
1892
+ answer.set("lines", lineSpec(range));
1893
+ outcomes.push({ action: "re-pinned", question, file, from: spellLines(lines), lines: spellLines(range) });
1894
+ changed = true;
1895
+ } else if (fresh.state === "ambiguous") {
1896
+ warnings.push(
1897
+ `${label}: the answer to "${question}" in ${file} moved, and ${matchList(fresh.ranges)}, so pin one of them by hand.`
1898
+ );
1899
+ }
1900
+ }
1901
+ }
1902
+ if (changed && !dryRun) writeFileSync2(abs, doc.toString(), "utf8");
1903
+ return { changed, outcomes, warnings };
1904
+ }
1905
+
1906
+ // src/graph/commands/fill.ts
1907
+ var TURN_BUDGET_REASON = "turn budget";
1908
+ var OFFLINE_REASON = "offline";
1909
+ var RELATION_FIELDS = [
1910
+ "alt-labels",
1911
+ "broader",
1912
+ "narrower",
1913
+ "related-concepts"
1914
+ ];
1915
+ function numberMap(v) {
1916
+ const out = {};
1917
+ if (v && typeof v === "object" && !Array.isArray(v)) {
1918
+ for (const [k, val] of Object.entries(v)) {
1919
+ if (typeof val === "number" && val >= 0 && val <= 1) out[k] = val;
2023
1920
  }
2024
1921
  }
2025
1922
  return out;
2026
1923
  }
2027
- function round2(n) {
1924
+ function stringMap(v) {
1925
+ const out = {};
1926
+ if (v && typeof v === "object" && !Array.isArray(v)) {
1927
+ for (const [k, val] of Object.entries(v)) {
1928
+ if (typeof val === "string") out[k] = val;
1929
+ }
1930
+ }
1931
+ return out;
1932
+ }
1933
+ function round22(n) {
2028
1934
  return Math.round(n * 100) / 100;
2029
1935
  }
2030
1936
  var META_PROVENANCE_KEY = "meta-provenance";
@@ -2042,10 +1948,12 @@ async function runFill(opts = {}) {
2042
1948
  },
2043
1949
  cwd
2044
1950
  );
1951
+ const offline = opts.offline ?? config.fill.offline;
2045
1952
  const flags = {
2046
1953
  ...opts.provider === void 0 ? {} : { provider: opts.provider },
2047
1954
  ...opts.model === void 0 ? {} : { model: opts.model },
2048
- ...opts.local === void 0 ? {} : { local: opts.local }
1955
+ ...opts.local === void 0 ? {} : { local: opts.local },
1956
+ ...offline ? { offline: true } : {}
2049
1957
  };
2050
1958
  const selection = selectProvider2(config, flags);
2051
1959
  assertProviderSelection(selection);
@@ -2077,11 +1985,20 @@ async function runFill(opts = {}) {
2077
1985
  };
2078
1986
  let identity;
2079
1987
  let construct;
2080
- if (opts.providerInstance) {
1988
+ if (offline) {
1989
+ const local = await resolveOfflineIdentity(config, flags);
1990
+ const injected = opts.providerInstance;
1991
+ identity = local === void 0 || injected === void 0 ? local : { provider: injected.provider(), model: injected.modelName() };
1992
+ construct = () => {
1993
+ if (injected !== void 0) return injected;
1994
+ if (local === void 0) throw new GraphError(noOfflineModel("nothing was proposed"));
1995
+ return constructProvider(config, local);
1996
+ };
1997
+ } else if (opts.providerInstance) {
2081
1998
  const injected = opts.providerInstance;
2082
1999
  identity = { provider: injected.provider(), model: injected.modelName() };
2083
2000
  construct = () => injected;
2084
- announceSelection(selection);
2001
+ announceSelection(selection, flags);
2085
2002
  } else {
2086
2003
  const resolved = await resolveProviderIdentity(config, flags);
2087
2004
  identity = resolved;
@@ -2089,7 +2006,11 @@ async function runFill(opts = {}) {
2089
2006
  }
2090
2007
  let provider;
2091
2008
  const getProvider = () => provider ??= construct();
2092
- const fields = opts.fields ?? config.fill.fields;
2009
+ const configured = opts.fields ?? config.fill.fields;
2010
+ const withQuestions = configured.includes("sample-questions");
2011
+ const fields = configured.filter(
2012
+ (f) => f !== "sample-questions"
2013
+ );
2093
2014
  const withSections = opts.sections ?? config.fill.sections;
2094
2015
  const sectionFields = withSections ? fields.filter((f) => SECTION_FILL_FIELDS.includes(f)) : void 0;
2095
2016
  const validateProposal = validatorFor(
@@ -2178,6 +2099,93 @@ async function runFill(opts = {}) {
2178
2099
  );
2179
2100
  }
2180
2101
  }
2102
+ const questionSettings = {
2103
+ perPage: config.fill.questions.perPage,
2104
+ confidenceThreshold,
2105
+ chunkChars: opts.chunkChars ?? config.fill.chunkChars,
2106
+ writeProvenance: config.fill.writeProvenance
2107
+ };
2108
+ const known = /* @__PURE__ */ new Map();
2109
+ if (withQuestions) {
2110
+ const asked = [
2111
+ ...[...mergedTexts].sort(([a], [b]) => byCodeUnit(a, b)),
2112
+ ...stdinContent === void 0 ? [] : [[STDIN_PATH, stdinContent]]
2113
+ ];
2114
+ for (const [path2, text] of asked) {
2115
+ for (const q of questionsIn(text, path2, formatOf(path2, formatFor(path2)))) {
2116
+ const key = questionKey(q);
2117
+ if (key !== "" && !known.has(key)) known.set(key, path2);
2118
+ }
2119
+ }
2120
+ for (const label of config.questionFiles) {
2121
+ for (const q of questionsInFile(resolve6(config.configDir, label), label)) {
2122
+ const key = questionKey(q);
2123
+ if (key !== "" && !known.has(key)) known.set(key, label);
2124
+ }
2125
+ }
2126
+ }
2127
+ const questionValidator = validatorFor(questionSchema(true));
2128
+ const questionRequestSchema = questionSchema();
2129
+ const asker = identity === void 0 ? void 0 : questionAsker(identity);
2130
+ function questionAsker(id) {
2131
+ return {
2132
+ generatedBy: `${id.provider}/${id.model}`,
2133
+ ask: async (request, chunk) => {
2134
+ const key = questionCacheKey(id.provider, id.model, chunk, request);
2135
+ const hit = cache.get(key);
2136
+ if (hit !== void 0 && questionValidator(hit)) return hit;
2137
+ if (maxTurns !== null && turnsUsed >= maxTurns) return void 0;
2138
+ turnsUsed++;
2139
+ const run = await completeValidatedJSON({
2140
+ provider: getProvider(),
2141
+ system: QUESTION_SYSTEM_PROMPT,
2142
+ user: buildQuestionPrompt(request, chunk),
2143
+ schema: questionRequestSchema,
2144
+ validate: questionValidator,
2145
+ temperature: config.fill.temperature
2146
+ });
2147
+ if (run.result === void 0) {
2148
+ throw new Error(run.error ?? "provider returned no questions");
2149
+ }
2150
+ cache.set(key, run.result);
2151
+ return run.result;
2152
+ }
2153
+ };
2154
+ }
2155
+ let terms;
2156
+ const termsOnce = () => terms ??= (async () => {
2157
+ const source = termLabelSource();
2158
+ if (!config.build.derive.includes("terms") || source === void 0) return void 0;
2159
+ try {
2160
+ return termIndex(
2161
+ await source({
2162
+ cwd,
2163
+ ...opts.config === void 0 ? {} : { configPath: opts.config },
2164
+ ...opts.noConfig === true ? { noConfig: true } : {}
2165
+ })
2166
+ );
2167
+ } catch {
2168
+ return void 0;
2169
+ }
2170
+ })();
2171
+ async function conceptsOf(data) {
2172
+ const strings = (v) => typeof v === "string" ? [v] : Array.isArray(v) ? v.filter((s) => typeof s === "string") : [];
2173
+ const block = data[GRAPH_KEY2];
2174
+ const graph = block !== null && typeof block === "object" && !Array.isArray(block) ? block : {};
2175
+ const out = /* @__PURE__ */ new Map();
2176
+ for (const label of strings(graph["label"])) out.set(label, strings(graph["alt-labels"]));
2177
+ const named = strings(graph["concepts"] ?? data["concepts"]);
2178
+ const index = named.length > 0 ? await termsOnce() : void 0;
2179
+ const language = typeof data["language"] === "string" ? canonicalLanguage(data["language"]) : void 0;
2180
+ for (const label of named) {
2181
+ const id = index?.resolve(label, language);
2182
+ const group = id === void 0 ? void 0 : index?.groups.get(id);
2183
+ const term = group?.find((t) => canonicalLanguage(t.language) === language) ?? group?.[0];
2184
+ if (term === void 0) out.set(label, out.get(label) ?? []);
2185
+ else out.set(term.label, [...term.altLabels ?? []]);
2186
+ }
2187
+ return [...out].map(([label, altLabels]) => ({ label, altLabels }));
2188
+ }
2181
2189
  for (const path2 of files) {
2182
2190
  const absPath = resolve6(base2, path2);
2183
2191
  let content;
@@ -2189,7 +2197,7 @@ async function runFill(opts = {}) {
2189
2197
  );
2190
2198
  }
2191
2199
  try {
2192
- results.push(await fillOne(path2, absPath, content));
2200
+ results.push(await fillPage(path2, absPath, content));
2193
2201
  } catch (e) {
2194
2202
  results.push({
2195
2203
  path: path2,
@@ -2204,7 +2212,7 @@ async function runFill(opts = {}) {
2204
2212
  let stdinDocument;
2205
2213
  if (stdinContent !== void 0) {
2206
2214
  try {
2207
- const result = await fillOne(STDIN_PATH, void 0, stdinContent);
2215
+ const result = await fillPage(STDIN_PATH, void 0, stdinContent);
2208
2216
  results.push(result);
2209
2217
  stdinDocument = result.filledContent ?? stdinContent;
2210
2218
  Reflect.deleteProperty(result, "filledContent");
@@ -2219,6 +2227,32 @@ async function runFill(opts = {}) {
2219
2227
  });
2220
2228
  }
2221
2229
  }
2230
+ if (withQuestions) {
2231
+ for (const label of config.questionFiles) {
2232
+ const repinned = repinQuestionFile(
2233
+ resolve6(config.configDir, label),
2234
+ label,
2235
+ config.configDir,
2236
+ opts.dryRun ?? false
2237
+ );
2238
+ warnings.push(...repinned.warnings);
2239
+ results.push({
2240
+ path: label,
2241
+ status: repinned.changed ? opts.dryRun ? "proposed" : "filled" : "complete",
2242
+ fields: repinned.changed ? [QUESTIONS_KEY] : [],
2243
+ preserved: [],
2244
+ cached: false,
2245
+ ...repinned.outcomes.length > 0 ? { questions: repinned.outcomes } : {}
2246
+ });
2247
+ }
2248
+ }
2249
+ if (offline && identity === void 0 && results.some((r) => r.reason === OFFLINE_REASON)) {
2250
+ warnings.push(
2251
+ noOfflineModel(
2252
+ withQuestions ? "only answer pins were updated, and nothing was drafted or proposed" : "nothing was proposed"
2253
+ )
2254
+ );
2255
+ }
2222
2256
  if (sectionsUnrecorded.length > 0) {
2223
2257
  warnings.push(
2224
2258
  "Section metadata was written but is NOT recorded in meta-provenance: /graph/sections is one of the three hand-curated pointers graph refuses, with /graph/revision-of and /graph/derived-from, so recording it would make `manni graph check` report an error. Review section values by hand \u2014 the review queue will not list them."
@@ -2233,6 +2267,45 @@ async function runFill(opts = {}) {
2233
2267
  exitCode: hasErrors ? 1 : 0,
2234
2268
  ...stdinDocument === void 0 ? {} : { stdinDocument }
2235
2269
  };
2270
+ async function fillPage(path2, absPath, content) {
2271
+ const { after, merged, ...result } = await fillOne(path2, absPath, content);
2272
+ if (!withQuestions) return result;
2273
+ const pageFormat = formatOf(path2, format);
2274
+ const data = metadataOf(merged ?? mergedTexts.get(path2) ?? content, path2, pageFormat).data;
2275
+ const pass = await fillPageQuestions({
2276
+ path: path2,
2277
+ format: pageFormat,
2278
+ content: after ?? content,
2279
+ title: typeof data["title"] === "string" ? data["title"] : path2,
2280
+ concepts: () => conceptsOf(data),
2281
+ settings: questionSettings,
2282
+ asker,
2283
+ known
2284
+ });
2285
+ warnings.push(...pass.warnings);
2286
+ const outcomes = pass.outcomes.length > 0 ? { questions: pass.outcomes } : {};
2287
+ if (pass.state === "changed") {
2288
+ if (!opts.dryRun) {
2289
+ if (absPath === void 0) result.filledContent = pass.content;
2290
+ else writeFileSync3(absPath, pass.content, "utf8");
2291
+ }
2292
+ return {
2293
+ ...result,
2294
+ status: opts.dryRun ? "proposed" : "filled",
2295
+ fields: [...result.fields, QUESTIONS_KEY],
2296
+ ...pass.skipped === void 0 ? {} : { reason: pass.skipped },
2297
+ ...outcomes
2298
+ };
2299
+ }
2300
+ const idle = result.status === "complete" || result.status === "nothing-proposed";
2301
+ if (idle && pass.state === "skipped") {
2302
+ return { ...result, status: "skipped", ...pass.skipped === void 0 ? {} : { reason: pass.skipped }, ...outcomes };
2303
+ }
2304
+ if (result.status === "complete" && pass.state === "unchanged") {
2305
+ return { ...result, status: "nothing-proposed", ...outcomes };
2306
+ }
2307
+ return { ...result, ...outcomes };
2308
+ }
2236
2309
  async function fillOne(path2, absPath, content) {
2237
2310
  const pageFormat = formatOf(path2, format);
2238
2311
  if ((pageFormat === "markdown" || pageFormat === "mdx") && frontmatterKind(content) === "unsupported") {
@@ -2262,7 +2335,7 @@ async function runFill(opts = {}) {
2262
2335
  heldGraph !== null && typeof heldGraph === "object" && !Array.isArray(heldGraph) ? Object.keys(heldGraph) : []
2263
2336
  );
2264
2337
  const missing = opts.force ? fields : fields.filter((f) => !present.has(f));
2265
- if (missing.length === 0 && !withSections) {
2338
+ if (fields.length === 0 || missing.length === 0 && !withSections) {
2266
2339
  return {
2267
2340
  path: path2,
2268
2341
  status: "complete",
@@ -2271,6 +2344,16 @@ async function runFill(opts = {}) {
2271
2344
  cached: false
2272
2345
  };
2273
2346
  }
2347
+ if (identity === void 0) {
2348
+ return {
2349
+ path: path2,
2350
+ status: "skipped",
2351
+ reason: OFFLINE_REASON,
2352
+ fields: [],
2353
+ preserved: [],
2354
+ cached: false
2355
+ };
2356
+ }
2274
2357
  const key = cacheKey(
2275
2358
  identity.provider,
2276
2359
  identity.model,
@@ -2321,15 +2404,15 @@ async function runFill(opts = {}) {
2321
2404
  const unknownSlugs = [];
2322
2405
  if (withSections) {
2323
2406
  const realSlugs = new Set(doc().sections.map((s) => s.slug));
2324
- for (const entry2 of Array.isArray(proposal["sections"]) ? proposal["sections"] : []) {
2325
- const slug = typeof entry2["slug"] === "string" ? entry2["slug"] : "";
2407
+ for (const entry of Array.isArray(proposal["sections"]) ? proposal["sections"] : []) {
2408
+ const slug = typeof entry["slug"] === "string" ? entry["slug"] : "";
2326
2409
  if (!realSlugs.has(slug)) {
2327
2410
  if (slug) unknownSlugs.push(slug);
2328
2411
  continue;
2329
2412
  }
2330
- const entryConfidence = numberMap(entry2["confidence"]);
2331
- const entryReasoning = stringMap(entry2["reasoning"]);
2332
- for (const [field, value] of Object.entries(entry2)) {
2413
+ const entryConfidence = numberMap(entry["confidence"]);
2414
+ const entryReasoning = stringMap(entry["reasoning"]);
2415
+ for (const [field, value] of Object.entries(entry)) {
2333
2416
  if (!SECTION_FILL_FIELDS.includes(field)) continue;
2334
2417
  if (!fields.includes(field)) continue;
2335
2418
  const name = `sections.${slug}.${field}`;
@@ -2401,7 +2484,7 @@ async function runFill(opts = {}) {
2401
2484
  const c = confidence[f];
2402
2485
  return {
2403
2486
  name,
2404
- confidence: c === void 0 ? priorConfidence[name] ?? 0 : round2(c)
2487
+ confidence: c === void 0 ? priorConfidence[name] ?? 0 : round22(c)
2405
2488
  };
2406
2489
  });
2407
2490
  const unscored = recordable.map((f) => [f, graphPointer(f)]).filter(
@@ -2474,7 +2557,7 @@ async function runFill(opts = {}) {
2474
2557
  }
2475
2558
  const manifests = await spliceAll(manifestWrites);
2476
2559
  if (!opts.dryRun && absPath !== void 0) {
2477
- if (pageContent !== content) writeFileSync2(absPath, pageContent, "utf8");
2560
+ if (pageContent !== content) writeFileSync3(absPath, pageContent, "utf8");
2478
2561
  for (const m of manifests) {
2479
2562
  if (m.text !== m.before) {
2480
2563
  await writeFileAtomic(m.absPath, m.text, { createParents: true });
@@ -2483,6 +2566,8 @@ async function runFill(opts = {}) {
2483
2566
  }
2484
2567
  guard?.commit(path2, applied.content);
2485
2568
  return {
2569
+ after: pageContent,
2570
+ merged: applied.content,
2486
2571
  path: path2,
2487
2572
  status: opts.dryRun ? "proposed" : "filled",
2488
2573
  fields: reportedFields,
@@ -2573,6 +2658,17 @@ async function openMetaViewPerPage(source, cwd, paths, base2, failures) {
2573
2658
  return openMetaView(source, cwd, readable, base2);
2574
2659
  }
2575
2660
  }
2661
+ function questionLine(q) {
2662
+ const score2 = q.confidence === void 0 ? "" : ` (${q.confidence.toFixed(2)})`;
2663
+ let detail;
2664
+ if (q.reason !== void 0) {
2665
+ detail = `${q.reason}${q.reason.startsWith("low confidence") ? score2 : ""}`;
2666
+ } else {
2667
+ const where = `${q.file === void 0 ? "" : `${q.file} `}lines ${q.from ?? q.lines ?? ""}`;
2668
+ detail = q.action === "re-pinned" ? `${where}${q.from === void 0 || q.lines === void 0 ? "" : ` to ${q.lines}`}` : `${where}${score2}`;
2669
+ }
2670
+ return `${q.action.padEnd(11)}"${q.question}" ${detail}`;
2671
+ }
2576
2672
  function renderFill(report, format) {
2577
2673
  if (format === "json") {
2578
2674
  return JSON.stringify({ ...report, stdinDocument: void 0 }, null, 2);
@@ -2608,6 +2704,7 @@ function renderFill(report, format) {
2608
2704
  lines.push(`error ${r.path}: ${r.error ?? ""}`);
2609
2705
  break;
2610
2706
  }
2707
+ for (const q of r.questions ?? []) lines.push(` ${questionLine(q)}`);
2611
2708
  }
2612
2709
  if (report.maxTurns !== null && report.results.some((r) => r.status === "skipped")) {
2613
2710
  lines.push(
@@ -2620,196 +2717,10 @@ function renderFill(report, format) {
2620
2717
 
2621
2718
  // src/graph/commands/embed.ts
2622
2719
  import { createHash as createHash2 } from "node:crypto";
2623
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
2720
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
2624
2721
  import path, { dirname as dirname2, join as join2, resolve as resolve7 } from "node:path";
2625
-
2626
- // src/graph/core/vector-index.ts
2627
- var MAGIC = new Uint8Array([77, 71, 82, 86]);
2628
- var FORMAT_VERSION = 2;
2629
- var HEADER_OFFSET = 12;
2630
- var VectorIndexError = class extends Error {
2631
- constructor(message) {
2632
- super(message);
2633
- this.name = "VectorIndexError";
2634
- }
2635
- };
2636
- function normalize(vector) {
2637
- let sum = 0;
2638
- for (const value of vector) sum += value * value;
2639
- if (sum === 0) return vector;
2640
- const inv = 1 / Math.sqrt(sum);
2641
- vector.set(vector.map((value) => value * inv));
2642
- return vector;
2643
- }
2644
- function encodeVectorIndex(entries, meta) {
2645
- const sorted = [...entries].sort((a, b) => byCodeUnit(a.id, b.id));
2646
- const dims = sorted[0]?.vector.length ?? 0;
2647
- for (const entry2 of sorted) {
2648
- if (entry2.vector.length !== dims) {
2649
- throw new VectorIndexError(
2650
- `Inconsistent vector dimensions: ${entry2.id} has ${entry2.vector.length}, expected ${dims}.`
2651
- );
2652
- }
2653
- }
2654
- const header = {
2655
- model: meta.model,
2656
- dtype: meta.dtype,
2657
- dims,
2658
- count: sorted.length,
2659
- ids: sorted.map((e) => e.id),
2660
- source: meta.source,
2661
- language: meta.language
2662
- };
2663
- const headerBytes = new TextEncoder().encode(JSON.stringify(header));
2664
- const payloadBytes = sorted.length * dims * 4;
2665
- const out = new Uint8Array(HEADER_OFFSET + headerBytes.length + payloadBytes);
2666
- const view = new DataView(out.buffer);
2667
- out.set(MAGIC, 0);
2668
- view.setUint32(4, FORMAT_VERSION, true);
2669
- view.setUint32(8, headerBytes.length, true);
2670
- out.set(headerBytes, HEADER_OFFSET);
2671
- let offset = HEADER_OFFSET + headerBytes.length;
2672
- for (const entry2 of sorted) {
2673
- const vector = normalize(Float32Array.from(entry2.vector));
2674
- for (const value of vector) {
2675
- view.setFloat32(offset, value, true);
2676
- offset += 4;
2677
- }
2678
- }
2679
- return out;
2680
- }
2681
- function decodeVectorIndex(bytes) {
2682
- if (bytes.length < HEADER_OFFSET) {
2683
- throw new VectorIndexError("Not a manni graph vector index: file is too short.");
2684
- }
2685
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
2686
- if (!MAGIC.every((byte, i) => bytes[i] === byte)) {
2687
- throw new VectorIndexError(
2688
- "Not a manni graph vector index: bad magic \u2014 is this the right file?"
2689
- );
2690
- }
2691
- const version = view.getUint32(4, true);
2692
- if (version !== FORMAT_VERSION) {
2693
- throw new VectorIndexError(
2694
- `Unsupported vector index version ${version} (this build reads ${FORMAT_VERSION}) \u2014 re-run \`manni graph embed\`.`
2695
- );
2696
- }
2697
- const headerLength = view.getUint32(8, true);
2698
- if (HEADER_OFFSET + headerLength > bytes.length) {
2699
- throw new VectorIndexError("Vector index is truncated: header overruns.");
2700
- }
2701
- let header;
2702
- try {
2703
- header = JSON.parse(
2704
- new TextDecoder().decode(
2705
- bytes.subarray(HEADER_OFFSET, HEADER_OFFSET + headerLength)
2706
- )
2707
- );
2708
- } catch {
2709
- throw new VectorIndexError("Vector index header is not valid JSON.");
2710
- }
2711
- if (!Array.isArray(header.ids) || typeof header.dims !== "number" || header.ids.length !== header.count) {
2712
- throw new VectorIndexError("Vector index header is malformed.");
2713
- }
2714
- const start = HEADER_OFFSET + headerLength;
2715
- const expected = header.count * header.dims * 4;
2716
- if (bytes.length - start !== expected) {
2717
- throw new VectorIndexError(
2718
- `Vector index is truncated: expected ${expected} payload bytes, found ${bytes.length - start}.`
2719
- );
2720
- }
2721
- const vectors = new Float32Array(header.count * header.dims);
2722
- for (let i = 0; i < vectors.length; i++) {
2723
- vectors[i] = view.getFloat32(start + i * 4, true);
2724
- }
2725
- return { header, vectors };
2726
- }
2727
-
2728
- // src/graph/embed/local.ts
2729
- var EmbedderUnavailableError = class extends Error {
2730
- constructor(detail) {
2731
- super(
2732
- `Local embeddings need @huggingface/transformers, which is an optional peer dependency.
2733
- Install it: npm install @huggingface/transformers
2734
- (${detail})`
2735
- );
2736
- this.name = "EmbedderUnavailableError";
2737
- }
2738
- };
2739
- async function loadTransformers(injected) {
2740
- if (injected) return injected;
2741
- try {
2742
- return await import("@huggingface/transformers");
2743
- } catch (e) {
2744
- throw new EmbedderUnavailableError(
2745
- errorMessage(e)
2746
- );
2747
- }
2748
- }
2749
- async function createLocalEmbedder(options = {}) {
2750
- const model = options.model ?? DEFAULT_MODEL;
2751
- const dtype = options.dtype ?? "q8";
2752
- const role = options.role ?? "passage";
2753
- const profile = profileFor(model);
2754
- const transformers = await loadTransformers(options.transformers);
2755
- if (transformers.env?.backends?.onnx?.wasm) {
2756
- transformers.env.backends.onnx.wasm.numThreads = 1;
2757
- }
2758
- const extractor = await transformers.pipeline("feature-extraction", model, {
2759
- // No `device`: its accepted values are disjoint across platforms, so any
2760
- // hardcoded choice throws on one of them (ADR 01025).
2761
- ...options.device === void 0 ? {} : { device: options.device },
2762
- dtype
2763
- });
2764
- let dims = 0;
2765
- return {
2766
- model,
2767
- dtype,
2768
- get dims() {
2769
- return dims;
2770
- },
2771
- async embed(text) {
2772
- const output = await extractor(withPrefix(profile, role, text), {
2773
- pooling: "mean",
2774
- normalize: true
2775
- });
2776
- const data = Float32Array.from(output.data);
2777
- dims = data.length;
2778
- return data;
2779
- }
2780
- };
2781
- }
2782
-
2783
- // src/graph/embed/mock.ts
2784
- function fnv1a(text, seed) {
2785
- let hash = (2166136261 ^ seed) >>> 0;
2786
- for (let i = 0; i < text.length; i++) {
2787
- hash = (hash ^ text.charCodeAt(i)) >>> 0;
2788
- hash = hash + ((hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24)) >>> 0;
2789
- }
2790
- return hash >>> 0;
2791
- }
2792
- function createMockEmbedder(options = {}) {
2793
- const dims = options.dims ?? 8;
2794
- const model = options.model ?? "mock";
2795
- const dtype = options.dtype ?? "mock";
2796
- return {
2797
- model,
2798
- dtype,
2799
- dims,
2800
- embed(text) {
2801
- const vector = new Float32Array(dims);
2802
- for (let i = 0; i < dims; i++) {
2803
- vector[i] = fnv1a(text, i) / 4294967296 * 2 - 1;
2804
- }
2805
- return Promise.resolve(vector);
2806
- }
2807
- };
2808
- }
2809
-
2810
- // src/graph/commands/embed.ts
2811
- function embedText(entry2) {
2812
- return [entry2.title, entry2.labels, entry2.description, entry2.text].filter((part) => part !== void 0 && part !== "").join("\n\n");
2722
+ function embedText(entry) {
2723
+ return [entry.title, entry.labels, entry.description, entry.text].filter((part) => part !== void 0 && part !== "").join("\n\n");
2813
2724
  }
2814
2725
  function sha2562(text) {
2815
2726
  return createHash2("sha256").update(text, "utf8").digest("hex");
@@ -2838,7 +2749,7 @@ var VectorCache = class {
2838
2749
  set(key, vector) {
2839
2750
  if (!this.enabled) return;
2840
2751
  mkdirSync2(this.dir, { recursive: true });
2841
- writeFileSync3(this.path(key), JSON.stringify([...vector]), "utf8");
2752
+ writeFileSync4(this.path(key), JSON.stringify([...vector]), "utf8");
2842
2753
  }
2843
2754
  };
2844
2755
  async function makeEmbedder(opts, model, dtype) {
@@ -2906,8 +2817,8 @@ async function runEmbed(opts = {}) {
2906
2817
  };
2907
2818
  const languages = [];
2908
2819
  const updated = [];
2909
- for (const entry2 of manifest.languages) {
2910
- const indexPath = join2(indexDir, entry2.search.path);
2820
+ for (const entry of manifest.languages) {
2821
+ const indexPath = join2(indexDir, entry.search.path);
2911
2822
  if (!existsSync3(indexPath)) {
2912
2823
  throw new GraphError(
2913
2824
  `Search index not found: ${indexPath} \u2014 the manifest names it; re-run \`manni graph export search\`.`
@@ -2915,9 +2826,9 @@ async function runEmbed(opts = {}) {
2915
2826
  }
2916
2827
  const raw = readFileSync5(indexPath, "utf8");
2917
2828
  const source = `sha256:${sha2562(raw)}`;
2918
- if (entry2.search.digest !== source) {
2829
+ if (entry.search.digest !== source) {
2919
2830
  throw new GraphError(
2920
- `Stale manifest: ${entry2.search.path} does not match the digest recorded for "${entry2.language}" \u2014 re-run \`manni graph export search\`.`
2831
+ `Stale manifest: ${entry.search.path} does not match the digest recorded for "${entry.language}" \u2014 re-run \`manni graph export search\`.`
2921
2832
  );
2922
2833
  }
2923
2834
  let parsed;
@@ -2934,7 +2845,7 @@ async function runEmbed(opts = {}) {
2934
2845
  `Not a manni graph search index: ${indexPath} \u2014 expected an \`entries\` array.`
2935
2846
  );
2936
2847
  }
2937
- const wanted = canonicalLanguageTag(entry2.language);
2848
+ const wanted = canonicalLanguageTag(entry.language);
2938
2849
  const perLanguage = Object.entries(config.embed.byLanguage).find(
2939
2850
  ([tag]) => canonicalLanguageTag(tag) === wanted
2940
2851
  )?.[1];
@@ -2960,21 +2871,21 @@ async function runEmbed(opts = {}) {
2960
2871
  }
2961
2872
  vectors.push({ id: item.id, vector });
2962
2873
  }
2963
- const filename = vectorIndexFilename(entry2.language);
2874
+ const filename = vectorIndexFilename(entry.language);
2964
2875
  const outPath = join2(outDir, filename);
2965
2876
  const manifestRelative = manifestRelativePath(indexDir, outPath);
2966
- writeFileSync3(
2877
+ writeFileSync4(
2967
2878
  outPath,
2968
2879
  encodeVectorIndex(vectors, {
2969
2880
  model: identity.model,
2970
2881
  dtype: identity.dtype,
2971
2882
  source,
2972
- language: entry2.language
2883
+ language: entry.language
2973
2884
  })
2974
2885
  );
2975
2886
  const dims = vectors[0]?.vector.length ?? 0;
2976
2887
  languages.push({
2977
- language: entry2.language,
2888
+ language: entry.language,
2978
2889
  outPath,
2979
2890
  model: identity.model,
2980
2891
  dims,
@@ -2983,7 +2894,7 @@ async function runEmbed(opts = {}) {
2983
2894
  total: vectors.length
2984
2895
  });
2985
2896
  updated.push({
2986
- ...entry2,
2897
+ ...entry,
2987
2898
  vectors: {
2988
2899
  path: manifestRelative,
2989
2900
  model: identity.model,
@@ -2993,7 +2904,7 @@ async function runEmbed(opts = {}) {
2993
2904
  }
2994
2905
  });
2995
2906
  }
2996
- writeFileSync3(
2907
+ writeFileSync4(
2997
2908
  manifestPath,
2998
2909
  emitLocalizations({ version: 1, languages: updated }),
2999
2910
  "utf8"
@@ -3022,397 +2933,8 @@ function renderEmbed(report, format) {
3022
2933
  ].join("\n");
3023
2934
  }
3024
2935
 
3025
- // src/graph/commands/search.ts
3026
- import { createHash as createHash3 } from "node:crypto";
3027
- import { existsSync as existsSync4, readFileSync as readFileSync6 } from "node:fs";
3028
- import { dirname as dirname3, join as join3, resolve as resolve8 } from "node:path";
3029
-
3030
- // src/graph/runtime/vector.ts
3031
- var VectorMismatchError = class extends Error {
3032
- reason;
3033
- constructor(mismatch) {
3034
- super(mismatch.detail);
3035
- this.name = "VectorMismatchError";
3036
- this.reason = mismatch.reason;
3037
- }
3038
- };
3039
- async function searchIndexDigest2(raw) {
3040
- const webCrypto = globalThis.crypto;
3041
- const subtle = webCrypto?.subtle;
3042
- if (!subtle) {
3043
- throw new Error(
3044
- "searchIndexDigest needs Web Crypto (globalThis.crypto.subtle), which is unavailable here \u2014 a page served over plain HTTP does not get it."
3045
- );
3046
- }
3047
- const digest = await subtle.digest("SHA-256", new TextEncoder().encode(raw));
3048
- const hex = [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
3049
- return `sha256:${hex}`;
3050
- }
3051
- function createVectorIndex(bytes) {
3052
- const { header, vectors } = decodeVectorIndex(bytes);
3053
- const { dims, ids } = header;
3054
- return {
3055
- model: header.model,
3056
- dtype: header.dtype,
3057
- dims,
3058
- source: header.source,
3059
- size: () => ids.length,
3060
- idAt: (row) => ids[row],
3061
- check(expected) {
3062
- if (expected.model !== void 0 && expected.model !== header.model) {
3063
- return {
3064
- reason: "model",
3065
- detail: `Vector index was built with ${header.model}, but the embedder is ${expected.model}. Re-run \`manni graph embed\`.`
3066
- };
3067
- }
3068
- if (expected.dtype !== void 0 && expected.dtype !== header.dtype) {
3069
- return {
3070
- reason: "dtype",
3071
- 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\`.`
3072
- };
3073
- }
3074
- if (expected.dims !== void 0 && expected.dims !== dims) {
3075
- return {
3076
- reason: "dims",
3077
- detail: `Vector index has ${dims} dimensions, but the embedder produces ${expected.dims}.`
3078
- };
3079
- }
3080
- if (expected.source !== void 0 && expected.source !== header.source) {
3081
- return {
3082
- reason: "stale-source",
3083
- detail: "Vector index was built from a different search index \u2014 the corpus changed. Re-run `manni graph embed`."
3084
- };
3085
- }
3086
- return void 0;
3087
- },
3088
- search(query, options = {}) {
3089
- if (dims === 0 || ids.length === 0) return [];
3090
- if (query.length !== dims) {
3091
- throw new VectorMismatchError({
3092
- reason: "dims",
3093
- detail: `Query vector has ${query.length} dimensions, but the index has ${dims}.`
3094
- });
3095
- }
3096
- const limit = normalizeLimit(options.limit);
3097
- if (limit === 0) return [];
3098
- const minScore = options.minScore ?? 0;
3099
- const q = normalize(Float32Array.from(query));
3100
- const scored = [];
3101
- for (const [row, iri2] of ids.entries()) {
3102
- const stored = vectors.subarray(row * dims, (row + 1) * dims);
3103
- let score = 0;
3104
- for (const [i, value] of q.entries()) score += value * (stored[i] ?? 0);
3105
- if (score >= minScore) {
3106
- scored.push({ iri: iri2, score, via: "vector" });
3107
- }
3108
- }
3109
- scored.sort(
3110
- (a, b) => a.score === b.score ? byCodeUnit(a.iri, b.iri) : b.score - a.score
3111
- );
3112
- return scored.slice(0, limit);
3113
- }
3114
- };
3115
- }
3116
- function normalizeLimit(limit) {
3117
- return typeof limit === "number" && Number.isInteger(limit) && limit >= 0 ? limit : 10;
3118
- }
3119
-
3120
- // src/graph/runtime/trace.ts
3121
- function createTrace() {
3122
- return { entry: [], hops: [], exclusions: [], resolutions: [] };
3123
- }
3124
- function reachedNodes(trace) {
3125
- const reached = /* @__PURE__ */ new Set();
3126
- for (const e of trace.entry) reached.add(e.iri);
3127
- for (const h of trace.hops) reached.add(h.to);
3128
- return reached;
3129
- }
3130
-
3131
- // src/graph/runtime/entry.ts
3132
- function rrfMerge(rankings, k = 60) {
3133
- const fused = /* @__PURE__ */ new Map();
3134
- for (const ranking of rankings) {
3135
- ranking.forEach((candidate, i) => {
3136
- const current = fused.get(candidate.iri) ?? {
3137
- score: 0,
3138
- vias: /* @__PURE__ */ new Set()
3139
- };
3140
- current.score += 1 / (k + i + 1);
3141
- current.vias.add(candidate.via);
3142
- fused.set(candidate.iri, current);
3143
- });
3144
- }
3145
- return [...fused.entries()].map(([iri2, { score, vias }]) => {
3146
- const [only] = vias;
3147
- return {
3148
- iri: iri2,
3149
- score,
3150
- via: vias.size === 1 && only !== void 0 ? only : "hybrid"
3151
- };
3152
- }).sort(
3153
- (a, b) => a.score === b.score ? byCodeUnit(a.iri, b.iri) : b.score - a.score
3154
- );
3155
- }
3156
- async function findEntry(query, options) {
3157
- const trace = options.trace ?? createTrace();
3158
- const limit = typeof options.limit === "number" && Number.isInteger(options.limit) && options.limit >= 0 ? options.limit : 10;
3159
- const lexical = options.lexical.search(query, { limit });
3160
- const embedder = options.embedder;
3161
- const embed = embedder ? (q) => embedder.embed(q) : options.embedQuery;
3162
- let vector = [];
3163
- if (options.vectors && embed && query.trim() !== "") {
3164
- const mismatch = options.vectors.check({
3165
- ...options.embedder ? { model: options.embedder.model, dtype: options.embedder.dtype } : {},
3166
- // Staleness is checked whichever way the query is embedded — it is a
3167
- // property of the corpus, not of the embedder, so even the unverified
3168
- // `embedQuery` path gets it when the caller supplies a digest.
3169
- ...options.source === void 0 ? {} : { source: options.source }
3170
- });
3171
- if (mismatch) throw new VectorMismatchError(mismatch);
3172
- const queryVector = await embed(query);
3173
- vector = options.vectors.search(queryVector, { limit });
3174
- }
3175
- const rankings = vector.length > 0 ? [lexical, vector] : [lexical];
3176
- const candidates = rrfMerge(rankings).slice(0, limit);
3177
- for (const candidate of candidates) trace.entry.push(candidate);
3178
- return { candidates, lexical, vector, trace };
3179
- }
3180
-
3181
- // src/graph/runtime/lexical.ts
3182
- import MiniSearch from "minisearch";
3183
- function createLexicalIndex(input) {
3184
- const doc = typeof input === "string" ? JSON.parse(input) : input;
3185
- const entries = Array.isArray(doc?.entries) ? doc.entries : [];
3186
- const mini = new MiniSearch({
3187
- fields: ["title", "labels", "description", "text"],
3188
- storeFields: ["id"],
3189
- idField: "id",
3190
- // Titles and labels are the strongest signal that a node *is* the subject;
3191
- // body text is evidence it merely mentions it.
3192
- searchOptions: {
3193
- boost: { title: 3, labels: 2, description: 1.5 },
3194
- prefix: true,
3195
- fuzzy: 0.2
3196
- }
3197
- });
3198
- mini.addAll(entries);
3199
- const byIri = new Map(entries.map((e) => [e.id, e]));
3200
- return {
3201
- size: () => entries.length,
3202
- entry: (iri2) => byIri.get(iri2),
3203
- search(query, options = {}) {
3204
- const trimmed = query.trim();
3205
- if (trimmed === "") return [];
3206
- const hits = mini.search(trimmed);
3207
- hits.sort(
3208
- (a, b) => a.score === b.score ? byCodeUnit(String(a.id), String(b.id)) : b.score - a.score
3209
- );
3210
- return hits.slice(0, options.limit ?? 10).map((h) => ({ iri: String(h.id), score: h.score, via: "lexical" }));
3211
- }
3212
- };
3213
- }
3214
-
3215
- // src/graph/commands/search.ts
3216
- var SEARCH_MODES = ["lexical", "hybrid", "vector"];
3217
- function loadSearchIndex(indexPath) {
3218
- const raw = readFileSync6(indexPath, "utf8");
3219
- let parsed;
3220
- try {
3221
- parsed = JSON.parse(raw);
3222
- } catch (e) {
3223
- throw new GraphError(
3224
- `Failed to parse ${indexPath}: ${errorMessage(e)} \u2014 re-run \`manni graph export search\`.`
3225
- );
3226
- }
3227
- const entries = parsed?.entries;
3228
- if (!Array.isArray(entries)) {
3229
- throw new GraphError(
3230
- `Not a manni graph search index: ${indexPath} \u2014 expected an \`entries\` array; re-run \`manni graph export search\`.`
3231
- );
3232
- }
3233
- return {
3234
- doc: parsed,
3235
- // Same recipe `manni graph embed` uses, so the two digests are comparable.
3236
- source: `sha256:${createHash3("sha256").update(raw, "utf8").digest("hex")}`
3237
- };
3238
- }
3239
- function resolveLocalization(indexDir, lang) {
3240
- const manifestPath = join3(indexDir, LOCALIZATIONS_FILENAME);
3241
- if (!existsSync4(manifestPath)) {
3242
- throw new GraphError(
3243
- `Localization manifest not found: ${manifestPath} \u2014 run \`manni graph export search\` first.`
3244
- );
3245
- }
3246
- const manifest = parseLocalizations(readFileSync6(manifestPath, "utf8"));
3247
- if (!manifest) {
3248
- throw new GraphError(
3249
- `Not a manni graph localization manifest: ${manifestPath} \u2014 re-run \`manni graph export search\`.`
3250
- );
3251
- }
3252
- const available = manifest.languages.map((l) => l.language);
3253
- if (lang !== void 0) {
3254
- const wanted = canonicalLanguageTag(lang);
3255
- const hit = manifest.languages.find(
3256
- (l) => canonicalLanguageTag(l.language) === wanted
3257
- );
3258
- if (!hit) {
3259
- throw new GraphError(
3260
- `No index for language "${lang}" \u2014 this corpus has ${available.join(", ") || "none"}.`
3261
- );
3262
- }
3263
- return hit;
3264
- }
3265
- const only = manifest.languages[0];
3266
- if (!only) {
3267
- throw new GraphError(
3268
- `The localization manifest lists no languages: ${manifestPath} \u2014 re-run \`manni graph export search\`.`
3269
- );
3270
- }
3271
- if (manifest.languages.length > 1) {
3272
- throw new GraphError(
3273
- `This corpus has more than one localization (${available.join(", ")}) \u2014 pass --lang to choose one.`
3274
- );
3275
- }
3276
- return only;
3277
- }
3278
- async function runSearch(opts) {
3279
- const cwd = opts.cwd ?? process.cwd();
3280
- const config = loadRunConfig(
3281
- {
3282
- ...opts.config === void 0 ? {} : { configPath: opts.config },
3283
- ...opts.noConfig === void 0 ? {} : { noConfig: opts.noConfig }
3284
- },
3285
- cwd
3286
- );
3287
- const graphPath = resolve8(cwd, opts.graph ?? config.out);
3288
- const indexDir = opts.index ? resolve8(cwd, opts.index) : dirname3(graphPath);
3289
- const localization = resolveLocalization(indexDir, opts.lang);
3290
- const indexPath = join3(indexDir, localization.search.path);
3291
- if (!existsSync4(indexPath)) {
3292
- throw new GraphError(
3293
- `Search index not found: ${indexPath} \u2014 the manifest names it; re-run \`manni graph export search\`.`
3294
- );
3295
- }
3296
- const { doc, source } = loadSearchIndex(indexPath);
3297
- if (localization.search.digest !== source) {
3298
- throw new GraphError(
3299
- `Stale manifest: ${localization.search.path} does not match the digest recorded for "${localization.language}". Re-run \`manni graph export search\`.`
3300
- );
3301
- }
3302
- const lexical = createLexicalIndex(doc);
3303
- const explicitVectors = opts.vectors !== void 0 && opts.vectors !== "" ? opts.vectors : void 0;
3304
- const wantsVector = opts.mode === "vector" || opts.mode === "hybrid" || explicitVectors !== void 0 && opts.mode !== "lexical";
3305
- const vectorsPath = explicitVectors ? resolve8(cwd, explicitVectors) : localization.vectors ? resolve8(indexDir, localization.vectors.path) : void 0;
3306
- let vectors;
3307
- if (opts.mode !== "lexical" && vectorsPath !== void 0 && existsSync4(vectorsPath)) {
3308
- try {
3309
- vectors = createVectorIndex(readFileSync6(vectorsPath));
3310
- } catch (e) {
3311
- if (e instanceof VectorIndexError) {
3312
- throw new GraphError(
3313
- `${e.message} (${vectorsPath}) \u2014 re-run \`manni graph embed\`, or use \`--mode lexical\`.`
3314
- );
3315
- }
3316
- throw e;
3317
- }
3318
- const stale = vectors.check({ source });
3319
- if (stale && wantsVector) throw new GraphError(stale.detail);
3320
- if (stale) {
3321
- warn(`${stale.detail} Falling back to lexical.`);
3322
- vectors = void 0;
3323
- }
3324
- } else if (wantsVector) {
3325
- const named = vectorsPath ?? `no sidecar recorded for "${localization.language}"`;
3326
- throw new GraphError(
3327
- `Vector index not found: ${named} \u2014 run \`manni graph embed\` first, or use \`--mode lexical\`.`
3328
- );
3329
- }
3330
- let embedder;
3331
- if (vectors) {
3332
- const requireEmbedder = opts.mode === "vector" || opts.mode === "hybrid";
3333
- embedder = await resolveEmbedder(opts, vectors, requireEmbedder);
3334
- }
3335
- const entry2 = await findEntry(opts.query, {
3336
- lexical,
3337
- // Pass the embedder, not a bare function: findEntry then verifies it
3338
- // against the index itself rather than trusting the CLI to have done so.
3339
- ...embedder && vectors ? { vectors, embedder } : {},
3340
- limit: opts.limit
3341
- }).catch((e) => {
3342
- if (e instanceof VectorMismatchError) {
3343
- throw new GraphError(`${e.message} Re-run \`manni graph embed\`.`);
3344
- }
3345
- throw e;
3346
- });
3347
- const decorate = (c) => {
3348
- const found = lexical.entry(c.iri);
3349
- return {
3350
- iri: c.iri,
3351
- score: c.score,
3352
- via: c.via,
3353
- ...found?.type === void 0 ? {} : { type: found.type },
3354
- ...found?.title === void 0 ? {} : { title: found.title }
3355
- };
3356
- };
3357
- const mode = opts.mode ?? (embedder && vectors ? "hybrid" : "lexical");
3358
- const results = opts.mode === "vector" ? entry2.vector : entry2.candidates;
3359
- if (opts.mode === "vector") {
3360
- entry2.trace.entry.length = 0;
3361
- entry2.trace.entry.push(...entry2.vector);
3362
- }
3363
- return {
3364
- query: opts.query,
3365
- mode,
3366
- results: results.map(decorate),
3367
- lexical: entry2.lexical.map(decorate),
3368
- vector: entry2.vector.map(decorate),
3369
- trace: entry2.trace
3370
- };
3371
- }
3372
- async function resolveEmbedder(opts, vectors, required) {
3373
- const { model, dtype } = vectors;
3374
- try {
3375
- const embedder = opts.embedder ?? (model === "mock" ? createMockEmbedder({ dtype }) : await createLocalEmbedder({ model, dtype, role: "query" }));
3376
- const mismatch = vectors.check({
3377
- model: embedder.model,
3378
- dtype: embedder.dtype,
3379
- // Defense in depth: a future model that changes output size without
3380
- // changing its id would otherwise only surface deep inside search().
3381
- ...embedder.dims > 0 ? { dims: embedder.dims } : {}
3382
- });
3383
- if (mismatch) throw new GraphError(mismatch.detail);
3384
- return embedder;
3385
- } catch (e) {
3386
- if (e instanceof GraphError) throw e;
3387
- if (e instanceof EmbedderUnavailableError) {
3388
- if (required) throw new GraphError(e.message);
3389
- return void 0;
3390
- }
3391
- throw new GraphError(
3392
- `Failed to load embedding model ${model}: ${errorMessage(e)}`
3393
- );
3394
- }
3395
- }
3396
- function renderSearch(report, format) {
3397
- if (format === "json") return JSON.stringify(report, null, 2);
3398
- const lines = [];
3399
- for (const r of report.results) {
3400
- const label = r.title ? ` \u2014 ${r.title}` : "";
3401
- const type = r.type ? ` [${r.type}]` : "";
3402
- lines.push(`${r.score.toFixed(4)} ${compactIri(r.iri)}${type}${label}`);
3403
- }
3404
- if (report.results.length === 0) {
3405
- lines.push("(no matches)");
3406
- }
3407
- lines.push("");
3408
- lines.push(
3409
- `${report.results.length} result${report.results.length === 1 ? "" : "s"} (${report.mode})`
3410
- );
3411
- return lines.join("\n");
3412
- }
3413
-
3414
2936
  // src/graph/commands/stats.ts
3415
- import { resolve as resolve9 } from "node:path";
2937
+ import { resolve as resolve8 } from "node:path";
3416
2938
  import { DataFactory as DataFactory4 } from "n3";
3417
2939
  function subjectsOfType(store, typeIri) {
3418
2940
  return store.getQuads(null, DataFactory4.namedNode(RDF_TYPE), DataFactory4.namedNode(typeIri), null).map((q) => q.subject.value).sort();
@@ -3433,7 +2955,7 @@ function runStats(opts = {}) {
3433
2955
  },
3434
2956
  cwd
3435
2957
  );
3436
- const store = loadGraph(resolve9(cwd, opts.graph ?? config.out));
2958
+ const store = loadGraph(resolve8(cwd, opts.graph ?? config.out));
3437
2959
  const top = opts.top ?? 5;
3438
2960
  const docIris = subjectsOfType(store, `${NS.graph}Document`);
3439
2961
  const docSet = new Set(docIris);
@@ -3538,8 +3060,18 @@ function runStats(opts = {}) {
3538
3060
  };
3539
3061
  })
3540
3062
  };
3063
+ const asked = /* @__PURE__ */ new Set();
3064
+ for (const q of store.getQuads(null, DataFactory4.namedNode(`${NS.graph}sampleQuestion`), null, null)) {
3065
+ if (!docSet.has(q.subject.value)) continue;
3066
+ for (const p of [`${NS.foaf}primaryTopic`, `${NS.dcterms}subject`]) {
3067
+ for (const t of store.getQuads(q.subject, DataFactory4.namedNode(p), null, null)) {
3068
+ asked.add(t.object.value);
3069
+ }
3070
+ }
3071
+ }
3072
+ const conceptsWithoutQuestions = subjectsOfType(store, `${NS.skos}Concept`).filter((c) => !asked.has(c)).sort(byCodeUnit);
3541
3073
  const uniform = opts.coverageThreshold;
3542
- const thresholds = uniform != null ? Object.fromEntries(coverage.map((c) => [c.field, uniform])) : config.stats.coverageThreshold;
3074
+ const thresholds = uniform != null ? Object.fromEntries(UNIFORM_COVERAGE_FIELD_NAMES.map((f) => [f, uniform])) : config.stats.coverageThreshold;
3543
3075
  const coverageFindings = coverage.flatMap((c) => {
3544
3076
  const threshold = thresholds[c.field];
3545
3077
  if (threshold === void 0) return [];
@@ -3571,13 +3103,21 @@ function runStats(opts = {}) {
3571
3103
  coverage,
3572
3104
  sectionCoverage,
3573
3105
  localization,
3106
+ conceptsWithoutQuestions,
3107
+ conceptLabels: Object.fromEntries(
3108
+ conceptsWithoutQuestions.map((c) => [c, conceptLabel(store, c)])
3109
+ ),
3574
3110
  coverageFindings,
3575
3111
  exitCode: failed ? 1 : 0
3576
3112
  };
3577
3113
  }
3114
+ function conceptLabel(store, iri2) {
3115
+ const labels = store.getQuads(DataFactory4.namedNode(iri2), DataFactory4.namedNode(`${NS.skos}prefLabel`), null, null).map((q) => q.object.value).sort(byCodeUnit);
3116
+ return labels[0] ?? compactIri(iri2);
3117
+ }
3578
3118
  function renderStats(report, format) {
3579
3119
  if (format === "json") {
3580
- const { exitCode: _exitCode, ...rest } = report;
3120
+ const { exitCode: _exitCode, conceptLabels: _labels, ...rest } = report;
3581
3121
  return JSON.stringify(rest, null, 2);
3582
3122
  }
3583
3123
  const lines = [
@@ -3639,16 +3179,22 @@ function renderStats(report, format) {
3639
3179
  }
3640
3180
  lines.push(` no language: ${report.localization.unlabelled}`);
3641
3181
  }
3182
+ if (report.concepts > 0) {
3183
+ const unasked = report.conceptsWithoutQuestions;
3184
+ lines.push("", `Concepts with no question (${String(unasked.length)}):`);
3185
+ for (const c of unasked) lines.push(` ${report.conceptLabels[c] ?? compactIri(c)}`);
3186
+ if (unasked.length === 0) lines.push(" (none)");
3187
+ }
3642
3188
  return lines.join("\n");
3643
3189
  }
3644
3190
 
3645
3191
  // src/graph/commands/traverse.ts
3646
- import { resolve as resolve10 } from "node:path";
3192
+ import { resolve as resolve9 } from "node:path";
3647
3193
 
3648
3194
  // src/graph/runtime/traverse.ts
3649
- var DCTERMS_TITLE3 = `${NS.dcterms}title`;
3195
+ var DCTERMS_TITLE = `${NS.dcterms}title`;
3650
3196
  var DCTERMS_REFERENCES = `${NS.dcterms}references`;
3651
- var DCTERMS_LANGUAGE2 = `${NS.dcterms}language`;
3197
+ var DCTERMS_LANGUAGE = `${NS.dcterms}language`;
3652
3198
  function documentOf(iri2) {
3653
3199
  const hash = iri2.indexOf("#");
3654
3200
  return hash === -1 ? iri2 : iri2.slice(0, hash);
@@ -3658,7 +3204,7 @@ function resolveVariant(graph, input) {
3658
3204
  const wanted = input.toLowerCase();
3659
3205
  const variants = graph.instancesOf(`${NS.iirds}ProductVariant`);
3660
3206
  for (const v of variants) {
3661
- if (graph.literal(v, DCTERMS_TITLE3)?.toLowerCase() === wanted) return v;
3207
+ if (graph.literal(v, DCTERMS_TITLE)?.toLowerCase() === wanted) return v;
3662
3208
  }
3663
3209
  for (const v of variants) {
3664
3210
  if (v.slice(v.lastIndexOf("/") + 1).toLowerCase() === wanted) return v;
@@ -3680,7 +3226,7 @@ function scopeExclusion(graph, iri2, scope) {
3680
3226
  "iri"
3681
3227
  ],
3682
3228
  [scope.subjectIri, IIRDS_HAS_SUBJECT, GRAPH_NOT_SOFTWARE_SUBJECT, "iri"],
3683
- [scope.language, DCTERMS_LANGUAGE2, void 0, "literal"]
3229
+ [scope.language, DCTERMS_LANGUAGE, void 0, "literal"]
3684
3230
  ];
3685
3231
  for (const [target, positive, negative, kind] of checks) {
3686
3232
  if (!target) continue;
@@ -3688,7 +3234,7 @@ function scopeExclusion(graph, iri2, scope) {
3688
3234
  const denied = graph.values(iri2, negative).some((v) => v.kind === kind && v.value === target);
3689
3235
  if (denied) return { node: iri2, rule: negative, value: target };
3690
3236
  }
3691
- const subject = positive === DCTERMS_LANGUAGE2 ? documentOf(iri2) : iri2;
3237
+ const subject = positive === DCTERMS_LANGUAGE ? documentOf(iri2) : iri2;
3692
3238
  const claimed = graph.values(subject, positive);
3693
3239
  if (claimed.length > 0) {
3694
3240
  const matches = claimed.some(
@@ -3807,7 +3353,7 @@ function runTraverse(opts) {
3807
3353
  },
3808
3354
  cwd
3809
3355
  );
3810
- const store = loadGraph(resolve10(cwd, opts.graph ?? config.out));
3356
+ const store = loadGraph(resolve9(cwd, opts.graph ?? config.out));
3811
3357
  const quads = storeToQuads(store);
3812
3358
  const graph = GraphIndex.fromQuads(quads, Object.fromEntries(PREFIXES));
3813
3359
  const node = expandTerm(opts.node);
@@ -3897,19 +3443,6 @@ export {
3897
3443
  emitRdfXml,
3898
3444
  writeZip,
3899
3445
  projectPackage,
3900
- splitFragment,
3901
- documentSectionOrder,
3902
- sectionOccurrences,
3903
- sectionOccurrence,
3904
- documentPreamble,
3905
- sliceSection,
3906
- sectionOwnText,
3907
- createFetchResolver,
3908
- buildSearchIndex,
3909
- emitSearchIndex,
3910
- partitionByLanguage,
3911
- documentsByLanguage,
3912
- GraphIndex,
3913
3446
  EXPORT_TARGETS,
3914
3447
  runExport,
3915
3448
  runQuery,
@@ -3926,27 +3459,9 @@ export {
3926
3459
  makeProvider,
3927
3460
  runFill,
3928
3461
  renderFill,
3929
- VectorIndexError,
3930
- normalize,
3931
- encodeVectorIndex,
3932
- decodeVectorIndex,
3933
- EmbedderUnavailableError,
3934
- createLocalEmbedder,
3935
- createMockEmbedder,
3936
3462
  embedText,
3937
3463
  runEmbed,
3938
3464
  renderEmbed,
3939
- VectorMismatchError,
3940
- searchIndexDigest2 as searchIndexDigest,
3941
- createVectorIndex,
3942
- createTrace,
3943
- reachedNodes,
3944
- rrfMerge,
3945
- findEntry,
3946
- createLexicalIndex,
3947
- SEARCH_MODES,
3948
- runSearch,
3949
- renderSearch,
3950
3465
  runStats,
3951
3466
  renderStats,
3952
3467
  resolveVariant,
@@ -3958,4 +3473,4 @@ export {
3958
3473
  runTraverse,
3959
3474
  renderTraverse
3960
3475
  };
3961
- //# sourceMappingURL=chunk-2DRHT73U.js.map
3476
+ //# sourceMappingURL=chunk-CVZMKWZ5.js.map