@hawkeyexl/manni 4.10.0 → 4.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/README.md +1 -1
  2. package/dist/batch-EFSFG6GC.js +46 -0
  3. package/dist/build-NIO3UIWJ.js +36 -0
  4. package/dist/calibrate-6VO7URRV.js +41 -0
  5. package/dist/capture-NMBVJO3C.js +38 -0
  6. package/dist/check-SHOYX2B5.js +30 -0
  7. package/dist/check-XMJKH3KV.js +34 -0
  8. package/dist/{check-NLL4NRYA.js → check-Y62BJNC4.js} +35 -31
  9. package/dist/{check-NLL4NRYA.js.map → check-Y62BJNC4.js.map} +1 -1
  10. package/dist/{check-7B77ZD2N.js → check-ZMO3ZLMW.js} +19 -15
  11. package/dist/{chunk-PK7EKJ5T.js → chunk-24AJK2RG.js} +5 -10
  12. package/dist/chunk-24AJK2RG.js.map +1 -0
  13. package/dist/chunk-2OZF2VUB.js +164 -0
  14. package/dist/chunk-2OZF2VUB.js.map +1 -0
  15. package/dist/chunk-2UJZXVHR.js +1868 -0
  16. package/dist/chunk-2UJZXVHR.js.map +1 -0
  17. package/dist/{chunk-DNZUBCGZ.js → chunk-3BRJCQPY.js} +36 -7
  18. package/dist/chunk-3BRJCQPY.js.map +1 -0
  19. package/dist/{chunk-D2UW3ZCK.js → chunk-42VFJOAF.js} +13 -11
  20. package/dist/{chunk-D2UW3ZCK.js.map → chunk-42VFJOAF.js.map} +1 -1
  21. package/dist/{chunk-LQZQ5KGG.js → chunk-4Q24OYI3.js} +7 -7
  22. package/dist/{chunk-XU73V5XT.js → chunk-4WJTURRO.js} +15 -15
  23. package/dist/{chunk-WN47MNSK.js → chunk-5SID2SVL.js} +4 -4
  24. package/dist/{chunk-5REEVKGH.js → chunk-AANUY4BH.js} +2 -2
  25. package/dist/chunk-ANPIIYRH.js +2272 -0
  26. package/dist/chunk-ANPIIYRH.js.map +1 -0
  27. package/dist/{chunk-RZVCWX5R.js → chunk-AYVKN3PG.js} +4 -4
  28. package/dist/{chunk-2UCC26MO.js → chunk-CH6IOGXV.js} +9 -9
  29. package/dist/{chunk-RODZ7MB7.js → chunk-D5Z6B4M7.js} +6 -6
  30. package/dist/{chunk-IJ62BB2T.js → chunk-D6SELXQ2.js} +7 -7
  31. package/dist/{chunk-2JV7C4YV.js → chunk-DDILEF5F.js} +890 -1399
  32. package/dist/chunk-DDILEF5F.js.map +1 -0
  33. package/dist/{chunk-3E2X2RBU.js → chunk-DGRLHDM4.js} +9 -9
  34. package/dist/{chunk-2BV4FBC7.js → chunk-DHGB4JBG.js} +4 -3
  35. package/dist/chunk-DHGB4JBG.js.map +1 -0
  36. package/dist/{chunk-6LR73NMO.js → chunk-DLU5FCFT.js} +30 -28
  37. package/dist/chunk-DLU5FCFT.js.map +1 -0
  38. package/dist/chunk-DSWKBZWW.js +16 -0
  39. package/dist/chunk-DSWKBZWW.js.map +1 -0
  40. package/dist/{chunk-BD7IXBWE.js → chunk-F564FHMQ.js} +8 -8
  41. package/dist/{chunk-ABHG6I3G.js → chunk-FKAZUZQU.js} +469 -85
  42. package/dist/chunk-FKAZUZQU.js.map +1 -0
  43. package/dist/{chunk-DFCQ6BMI.js → chunk-G3MBUL5M.js} +4 -4
  44. package/dist/{chunk-SUTMSDQ2.js → chunk-GQ3KRY3B.js} +3 -3
  45. package/dist/chunk-HDPN46JP.js +393 -0
  46. package/dist/chunk-HDPN46JP.js.map +1 -0
  47. package/dist/{chunk-EAUIQADY.js → chunk-IQQAJROD.js} +6 -6
  48. package/dist/{chunk-ZHRPH52H.js → chunk-IZXN3WI6.js} +2 -2
  49. package/dist/{chunk-NCPWXHDO.js → chunk-JEUFYSFQ.js} +4 -4
  50. package/dist/{chunk-G6FNWRAI.js → chunk-JSSM7VVV.js} +4 -4
  51. package/dist/{chunk-CCWMACJL.js → chunk-KFGZPVFA.js} +229 -821
  52. package/dist/chunk-KFGZPVFA.js.map +1 -0
  53. package/dist/{chunk-H26VDFDS.js → chunk-KGFSPJLD.js} +2 -2
  54. package/dist/{chunk-AO5TSDKT.js → chunk-KMORGMCW.js} +7 -7
  55. package/dist/{chunk-WQWKYAAK.js → chunk-NUQF5F5J.js} +2 -2
  56. package/dist/{chunk-2YJ5H5E6.js → chunk-NZ6KSHFD.js} +23 -21
  57. package/dist/{chunk-2YJ5H5E6.js.map → chunk-NZ6KSHFD.js.map} +1 -1
  58. package/dist/{chunk-7Y3N4Y72.js → chunk-OHUDZRLK.js} +3 -3
  59. package/dist/{chunk-GCKHSBEN.js → chunk-OKMTUAWO.js} +4 -4
  60. package/dist/{chunk-6M5VJOC2.js → chunk-OTJOBZJ5.js} +167 -166
  61. package/dist/chunk-OTJOBZJ5.js.map +1 -0
  62. package/dist/{chunk-2W34HX2U.js → chunk-PS257NIU.js} +288 -373
  63. package/dist/chunk-PS257NIU.js.map +1 -0
  64. package/dist/chunk-QDC7JPFG.js +685 -0
  65. package/dist/chunk-QDC7JPFG.js.map +1 -0
  66. package/dist/chunk-QEAGL4V5.js +3185 -0
  67. package/dist/chunk-QEAGL4V5.js.map +1 -0
  68. package/dist/{chunk-GB2XPITA.js → chunk-QMY57PVW.js} +2 -2
  69. package/dist/{chunk-55GMOVKW.js → chunk-QNDDPXFR.js} +2 -2
  70. package/dist/{chunk-GXN7RRHR.js → chunk-QSIXNTRV.js} +4 -4
  71. package/dist/{chunk-G7XD4QH5.js → chunk-RGLFV4KO.js} +11372 -10218
  72. package/dist/chunk-RGLFV4KO.js.map +1 -0
  73. package/dist/{chunk-QBVP3SQP.js → chunk-SGR7ZAVE.js} +13 -7
  74. package/dist/chunk-SGR7ZAVE.js.map +1 -0
  75. package/dist/{chunk-KBG463UK.js → chunk-SMHAYK6I.js} +2409 -5243
  76. package/dist/chunk-SMHAYK6I.js.map +1 -0
  77. package/dist/{chunk-XG6PVB5S.js → chunk-SSQZB5SQ.js} +1 -11
  78. package/dist/chunk-SSQZB5SQ.js.map +1 -0
  79. package/dist/{chunk-GDLHTI5R.js → chunk-STPGIKPY.js} +8 -2
  80. package/dist/chunk-STPGIKPY.js.map +1 -0
  81. package/dist/{chunk-QJRQ4BRC.js → chunk-U22TOL5Y.js} +17 -17
  82. package/dist/{chunk-NGTVREIH.js → chunk-U6IHLEWT.js} +2 -2
  83. package/dist/{chunk-Z5YHMRLF.js → chunk-UUCCV3SI.js} +2 -2
  84. package/dist/{chunk-ZXXFP2LA.js → chunk-UXMCHBOJ.js} +2 -2
  85. package/dist/{chunk-VZVEP2OZ.js → chunk-VFAKGIDG.js} +2 -2
  86. package/dist/{chunk-QCHVR4ZM.js → chunk-VM7VAFJW.js} +33 -7
  87. package/dist/chunk-VM7VAFJW.js.map +1 -0
  88. package/dist/{chunk-VQK4VHLF.js → chunk-YQV75FIL.js} +2 -2
  89. package/dist/chunk-YUTVAMWW.js +2 -0
  90. package/dist/chunk-ZPLVANY2.js +15 -0
  91. package/dist/chunk-ZPLVANY2.js.map +1 -0
  92. package/dist/{cli-BIIUKPTE.js → cli-4CR7QIOK.js} +26 -22
  93. package/dist/{cli-BIIUKPTE.js.map → cli-4CR7QIOK.js.map} +1 -1
  94. package/dist/{cli-V2DI3K2R.js → cli-6R4W3HAH.js} +37 -33
  95. package/dist/{cli-V2DI3K2R.js.map → cli-6R4W3HAH.js.map} +1 -1
  96. package/dist/{cli-HO62TOM2.js → cli-BOMA2FMP.js} +14 -11
  97. package/dist/{cli-EH3I4IUX.js → cli-EDQPQOCE.js} +47 -41
  98. package/dist/cli-EDQPQOCE.js.map +1 -0
  99. package/dist/{cli-P2X7PNY6.js → cli-GBF76EXT.js} +25 -20
  100. package/dist/{cli-P2X7PNY6.js.map → cli-GBF76EXT.js.map} +1 -1
  101. package/dist/{cli-4YYC6LCY.js → cli-MQYCF3SR.js} +27 -23
  102. package/dist/{cli-4YYC6LCY.js.map → cli-MQYCF3SR.js.map} +1 -1
  103. package/dist/{cli-UETKXHJN.js → cli-OJKFXQOM.js} +9 -9
  104. package/dist/{cli-LWST5FQH.js → cli-PHZAO4LZ.js} +4 -4
  105. package/dist/{cli-TELPAMJO.js → cli-PTKFFNOW.js} +59 -35
  106. package/dist/cli-PTKFFNOW.js.map +1 -0
  107. package/dist/{cli-SGVFZOAW.js → cli-UW2K5Z5J.js} +34 -29
  108. package/dist/{cli-SGVFZOAW.js.map → cli-UW2K5Z5J.js.map} +1 -1
  109. package/dist/cli-XXTNPPKB.js +83 -0
  110. package/dist/cli-XXTNPPKB.js.map +1 -0
  111. package/dist/cli.js +24 -12
  112. package/dist/cli.js.map +1 -1
  113. package/dist/{config-TYXYQK3I.js → config-JJW75SBX.js} +5 -5
  114. package/dist/docmeta.js +13 -10
  115. package/dist/docmeta.js.map +1 -1
  116. package/dist/github-6VQKSB67.js +35 -0
  117. package/dist/github-RBSEM7ZK.js +30 -0
  118. package/dist/graph/runtime.d.ts +4 -1
  119. package/dist/graph/runtime.js +20 -9
  120. package/dist/graph/runtime.js.map +1 -1
  121. package/dist/{host-6GURH5HE.js → host-YSTYO5DX.js} +3 -3
  122. package/dist/index.d.ts +1214 -940
  123. package/dist/index.js +190 -160
  124. package/dist/index.js.map +1 -1
  125. package/dist/{json-QR4G35KA.js → json-PU2HLHDQ.js} +18 -14
  126. package/dist/labels-SJNSOPBI.js +73 -0
  127. package/dist/labels-SJNSOPBI.js.map +1 -0
  128. package/dist/lint-KIVTHLGU.js +34 -0
  129. package/dist/list-RDEJDDJF.js +32 -0
  130. package/dist/list-SUTDWL2J.js +35 -0
  131. package/dist/{page-FSXDXCDD.js → page-UMAA3NCM.js} +18 -13
  132. package/dist/{prepare-UPIBTFNS.js → prepare-3I3LOXXD.js} +24 -20
  133. package/dist/{prepare-UPIBTFNS.js.map → prepare-3I3LOXXD.js.map} +1 -1
  134. package/dist/{pretty-4IONWSEP.js → pretty-ANLRHU4O.js} +18 -14
  135. package/dist/{pretty-R4BQD2VH.js → pretty-Q3ZFKOR7.js} +18 -14
  136. package/dist/{provider-527CO4DM.js → provider-LBX4IXGX.js} +3 -3
  137. package/dist/{release-3D5PK6M7.js → release-VV3UTKE5.js} +3 -3
  138. package/dist/{reporters-M6LJ2IYU.js → reporters-26H3N2KW.js} +3 -3
  139. package/dist/{reporters-DHO2BXAD.js → reporters-ILT36CIH.js} +7 -7
  140. package/dist/{reporters-FPP6JNDP.js → reporters-PNDLWT6K.js} +16 -12
  141. package/dist/run-QXCK33Z4.js +37 -0
  142. package/dist/{term-ASM4LVYC.js → term-D2N7UN4P.js} +981 -138
  143. package/dist/term-D2N7UN4P.js.map +1 -0
  144. package/dist/validate-BRUCOT2O.js +19 -0
  145. package/dist/validate-BRUCOT2O.js.map +1 -0
  146. package/package.json +3 -2
  147. package/plugin/manni/.claude-plugin/plugin.json +1 -1
  148. package/shapes/graph/shapes-1.1.0.ttl +785 -0
  149. package/dist/batch-6NICDKRE.js +0 -41
  150. package/dist/build-3KP2K3XN.js +0 -32
  151. package/dist/calibrate-LUA3DWKD.js +0 -36
  152. package/dist/capture-NETOHCB4.js +0 -34
  153. package/dist/check-E4QCB7LS.js +0 -20
  154. package/dist/check-PTECPIIB.js +0 -26
  155. package/dist/chunk-2BV4FBC7.js.map +0 -1
  156. package/dist/chunk-2JV7C4YV.js.map +0 -1
  157. package/dist/chunk-2W34HX2U.js.map +0 -1
  158. package/dist/chunk-4DLF5SA4.js +0 -392
  159. package/dist/chunk-4DLF5SA4.js.map +0 -1
  160. package/dist/chunk-6AGESHHM.js +0 -387
  161. package/dist/chunk-6AGESHHM.js.map +0 -1
  162. package/dist/chunk-6LR73NMO.js.map +0 -1
  163. package/dist/chunk-6M5VJOC2.js.map +0 -1
  164. package/dist/chunk-ABHG6I3G.js.map +0 -1
  165. package/dist/chunk-CCWMACJL.js.map +0 -1
  166. package/dist/chunk-DNZUBCGZ.js.map +0 -1
  167. package/dist/chunk-G7XD4QH5.js.map +0 -1
  168. package/dist/chunk-GDLHTI5R.js.map +0 -1
  169. package/dist/chunk-KBG463UK.js.map +0 -1
  170. package/dist/chunk-PK7EKJ5T.js.map +0 -1
  171. package/dist/chunk-QBVP3SQP.js.map +0 -1
  172. package/dist/chunk-QCHVR4ZM.js.map +0 -1
  173. package/dist/chunk-VVVKBUBO.js +0 -818
  174. package/dist/chunk-VVVKBUBO.js.map +0 -1
  175. package/dist/chunk-XG6PVB5S.js.map +0 -1
  176. package/dist/cli-EH3I4IUX.js.map +0 -1
  177. package/dist/cli-TELPAMJO.js.map +0 -1
  178. package/dist/github-ESK45CEZ.js +0 -26
  179. package/dist/github-WER7JUTN.js +0 -31
  180. package/dist/lint-QTI7XK2A.js +0 -30
  181. package/dist/list-JO4G3TS3.js +0 -30
  182. package/dist/list-O2MJUP5V.js +0 -28
  183. package/dist/run-YH5Z7RJ7.js +0 -32
  184. package/dist/term-ASM4LVYC.js.map +0 -1
  185. package/dist/validate-7JI3H57P.js +0 -17
  186. /package/dist/{batch-6NICDKRE.js.map → batch-EFSFG6GC.js.map} +0 -0
  187. /package/dist/{build-3KP2K3XN.js.map → build-NIO3UIWJ.js.map} +0 -0
  188. /package/dist/{calibrate-LUA3DWKD.js.map → calibrate-6VO7URRV.js.map} +0 -0
  189. /package/dist/{capture-NETOHCB4.js.map → capture-NMBVJO3C.js.map} +0 -0
  190. /package/dist/{check-7B77ZD2N.js.map → check-SHOYX2B5.js.map} +0 -0
  191. /package/dist/{check-E4QCB7LS.js.map → check-XMJKH3KV.js.map} +0 -0
  192. /package/dist/{check-PTECPIIB.js.map → check-ZMO3ZLMW.js.map} +0 -0
  193. /package/dist/{chunk-LQZQ5KGG.js.map → chunk-4Q24OYI3.js.map} +0 -0
  194. /package/dist/{chunk-XU73V5XT.js.map → chunk-4WJTURRO.js.map} +0 -0
  195. /package/dist/{chunk-WN47MNSK.js.map → chunk-5SID2SVL.js.map} +0 -0
  196. /package/dist/{chunk-5REEVKGH.js.map → chunk-AANUY4BH.js.map} +0 -0
  197. /package/dist/{chunk-RZVCWX5R.js.map → chunk-AYVKN3PG.js.map} +0 -0
  198. /package/dist/{chunk-2UCC26MO.js.map → chunk-CH6IOGXV.js.map} +0 -0
  199. /package/dist/{chunk-RODZ7MB7.js.map → chunk-D5Z6B4M7.js.map} +0 -0
  200. /package/dist/{chunk-IJ62BB2T.js.map → chunk-D6SELXQ2.js.map} +0 -0
  201. /package/dist/{chunk-3E2X2RBU.js.map → chunk-DGRLHDM4.js.map} +0 -0
  202. /package/dist/{chunk-BD7IXBWE.js.map → chunk-F564FHMQ.js.map} +0 -0
  203. /package/dist/{chunk-DFCQ6BMI.js.map → chunk-G3MBUL5M.js.map} +0 -0
  204. /package/dist/{chunk-SUTMSDQ2.js.map → chunk-GQ3KRY3B.js.map} +0 -0
  205. /package/dist/{chunk-EAUIQADY.js.map → chunk-IQQAJROD.js.map} +0 -0
  206. /package/dist/{chunk-ZHRPH52H.js.map → chunk-IZXN3WI6.js.map} +0 -0
  207. /package/dist/{chunk-NCPWXHDO.js.map → chunk-JEUFYSFQ.js.map} +0 -0
  208. /package/dist/{chunk-G6FNWRAI.js.map → chunk-JSSM7VVV.js.map} +0 -0
  209. /package/dist/{chunk-H26VDFDS.js.map → chunk-KGFSPJLD.js.map} +0 -0
  210. /package/dist/{chunk-AO5TSDKT.js.map → chunk-KMORGMCW.js.map} +0 -0
  211. /package/dist/{chunk-WQWKYAAK.js.map → chunk-NUQF5F5J.js.map} +0 -0
  212. /package/dist/{chunk-7Y3N4Y72.js.map → chunk-OHUDZRLK.js.map} +0 -0
  213. /package/dist/{chunk-GCKHSBEN.js.map → chunk-OKMTUAWO.js.map} +0 -0
  214. /package/dist/{chunk-GB2XPITA.js.map → chunk-QMY57PVW.js.map} +0 -0
  215. /package/dist/{chunk-55GMOVKW.js.map → chunk-QNDDPXFR.js.map} +0 -0
  216. /package/dist/{chunk-GXN7RRHR.js.map → chunk-QSIXNTRV.js.map} +0 -0
  217. /package/dist/{chunk-QJRQ4BRC.js.map → chunk-U22TOL5Y.js.map} +0 -0
  218. /package/dist/{chunk-NGTVREIH.js.map → chunk-U6IHLEWT.js.map} +0 -0
  219. /package/dist/{chunk-Z5YHMRLF.js.map → chunk-UUCCV3SI.js.map} +0 -0
  220. /package/dist/{chunk-ZXXFP2LA.js.map → chunk-UXMCHBOJ.js.map} +0 -0
  221. /package/dist/{chunk-VZVEP2OZ.js.map → chunk-VFAKGIDG.js.map} +0 -0
  222. /package/dist/{chunk-VQK4VHLF.js.map → chunk-YQV75FIL.js.map} +0 -0
  223. /package/dist/{cli-HO62TOM2.js.map → chunk-YUTVAMWW.js.map} +0 -0
  224. /package/dist/{config-TYXYQK3I.js.map → cli-BOMA2FMP.js.map} +0 -0
  225. /package/dist/{cli-UETKXHJN.js.map → cli-OJKFXQOM.js.map} +0 -0
  226. /package/dist/{cli-LWST5FQH.js.map → cli-PHZAO4LZ.js.map} +0 -0
  227. /package/dist/{github-ESK45CEZ.js.map → config-JJW75SBX.js.map} +0 -0
  228. /package/dist/{github-WER7JUTN.js.map → github-6VQKSB67.js.map} +0 -0
  229. /package/dist/{host-6GURH5HE.js.map → github-RBSEM7ZK.js.map} +0 -0
  230. /package/dist/{json-QR4G35KA.js.map → host-YSTYO5DX.js.map} +0 -0
  231. /package/dist/{lint-QTI7XK2A.js.map → json-PU2HLHDQ.js.map} +0 -0
  232. /package/dist/{list-JO4G3TS3.js.map → lint-KIVTHLGU.js.map} +0 -0
  233. /package/dist/{list-O2MJUP5V.js.map → list-RDEJDDJF.js.map} +0 -0
  234. /package/dist/{page-FSXDXCDD.js.map → list-SUTDWL2J.js.map} +0 -0
  235. /package/dist/{pretty-4IONWSEP.js.map → page-UMAA3NCM.js.map} +0 -0
  236. /package/dist/{pretty-R4BQD2VH.js.map → pretty-ANLRHU4O.js.map} +0 -0
  237. /package/dist/{provider-527CO4DM.js.map → pretty-Q3ZFKOR7.js.map} +0 -0
  238. /package/dist/{reporters-DHO2BXAD.js.map → provider-LBX4IXGX.js.map} +0 -0
  239. /package/dist/{release-3D5PK6M7.js.map → release-VV3UTKE5.js.map} +0 -0
  240. /package/dist/{reporters-FPP6JNDP.js.map → reporters-26H3N2KW.js.map} +0 -0
  241. /package/dist/{reporters-M6LJ2IYU.js.map → reporters-ILT36CIH.js.map} +0 -0
  242. /package/dist/{run-YH5Z7RJ7.js.map → reporters-PNDLWT6K.js.map} +0 -0
  243. /package/dist/{validate-7JI3H57P.js.map → run-QXCK33Z4.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-CCWMACJL.js";
5
+ termIndex
6
+ } from "./chunk-KFGZPVFA.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-6AGESHHM.js";
28
+ } from "./chunk-ANPIIYRH.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-VVVKBUBO.js";
80
+ mergePage,
81
+ metadataOf,
82
+ openMetaView,
83
+ parseLocalizations,
84
+ questionKey,
85
+ readQuestionFile,
86
+ resolveDocumentSet,
87
+ routesFrom,
88
+ searchIndexFilename,
89
+ vectorIndexFilename
90
+ } from "./chunk-2UJZXVHR.js";
91
+ import {
92
+ splitBody
93
+ } from "./chunk-PS257NIU.js";
76
94
  import {
77
95
  mergeMetaProvenance
78
- } from "./chunk-2W34HX2U.js";
96
+ } from "./chunk-2OZF2VUB.js";
79
97
  import {
98
+ findWindows,
80
99
  isMissing,
100
+ lineSpec,
101
+ parseLines,
102
+ pinOfLines,
103
+ spellLines,
81
104
  spliceManifestValue
82
- } from "./chunk-KBG463UK.js";
105
+ } from "./chunk-SMHAYK6I.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-G7XD4QH5.js";
113
+ } from "./chunk-RGLFV4KO.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,7 +701,7 @@ ${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
 
@@ -1483,8 +909,8 @@ function existingGraphFields(content) {
1483
909
  }
1484
910
 
1485
911
  // src/graph/core/fill-guard.ts
1486
- import { readFileSync as readFileSync3 } from "node:fs";
1487
- import { resolve as resolve5 } from "node:path";
912
+ import { readFileSync as readFileSync2 } from "node:fs";
913
+ import { resolve as resolve4 } from "node:path";
1488
914
  import { DataFactory as DataFactory3, Store } from "n3";
1489
915
  var GUARDED_FIELDS = [
1490
916
  "label",
@@ -1510,7 +936,7 @@ function toStore(quads) {
1510
936
  DataFactory3.namedNode(q.p),
1511
937
  q.o.kind === "iri" ? DataFactory3.namedNode(q.o.value) : DataFactory3.literal(
1512
938
  q.o.value,
1513
- q.o.datatype ? DataFactory3.namedNode(q.o.datatype) : void 0
939
+ q.o.language ?? (q.o.datatype ? DataFactory3.namedNode(q.o.datatype) : void 0)
1514
940
  )
1515
941
  )
1516
942
  );
@@ -1573,7 +999,7 @@ var FillGuard = class _FillGuard {
1573
999
  guard.models.set(
1574
1000
  path2,
1575
1001
  analyzeDoc(
1576
- inline.get(path2) ?? readFileSync3(resolve5(cwd, path2), "utf8"),
1002
+ inline.get(path2) ?? readFileSync2(resolve4(cwd, path2), "utf8"),
1577
1003
  path2,
1578
1004
  guard.allPaths,
1579
1005
  inputs.has(path2) ? guard.analyzeOptions : byExtension
@@ -1722,6 +1148,7 @@ import { JsonCache, buildCacheKey, sha256 } from "@hawkeyexl/inference";
1722
1148
 
1723
1149
  // src/graph/llm/prompt.ts
1724
1150
  var PROMPT_VERSION = 4;
1151
+ var QUESTION_PROMPT_VERSION = 1;
1725
1152
  var TOPIC_TYPES = [
1726
1153
  "task",
1727
1154
  "concept",
@@ -1917,6 +1344,73 @@ function buildUserPrompt(doc, body, fields, options = {}) {
1917
1344
  body.slice(0, EXCERPT_CHARS)
1918
1345
  ].join("\n");
1919
1346
  }
1347
+ var QUESTION_SYSTEM_PROMPT = [
1348
+ "You write and answer the questions a reader asks of a documentation page.",
1349
+ "Every answer is a passage quoted VERBATIM from the page text you are given:",
1350
+ "copy it exactly, as one contiguous run of the text, and quote no more than",
1351
+ "the passage that answers. Never paraphrase a quote.",
1352
+ "",
1353
+ "For each numbered question, quote the passage that answers it, or an empty",
1354
+ "string when no passage in this text does. Draft only as many new questions",
1355
+ "as asked. Ask them as a reader would, in a reader's own words rather than",
1356
+ "the page's, and only questions this text answers. For every pair and draft,",
1357
+ "give a `confidence` (0..1) that the quote answers the question. Score",
1358
+ "honestly and conservatively."
1359
+ ].join("\n");
1360
+ function buildQuestionPrompt(request, chunk) {
1361
+ const concepts = request.concepts.map(
1362
+ (c) => c.altLabels.length > 0 ? `- ${c.label} (also: ${c.altLabels.join(", ")})` : `- ${c.label}`
1363
+ );
1364
+ return [
1365
+ `Path: ${request.path}`,
1366
+ `Title: ${request.title}`,
1367
+ ...concepts.length > 0 ? ["", "Concepts this page is about:", ...concepts] : [],
1368
+ "",
1369
+ request.pair.length > 0 ? `Questions to pair, by number:
1370
+ ${request.pair.map((q, i) => `${String(i + 1)}. ${q}`).join("\n")}` : "Questions to pair: none.",
1371
+ "",
1372
+ request.draft > 0 ? `Draft ${String(request.draft)} new question${request.draft === 1 ? "" : "s"}, each with the quote that answers it.` : "Draft no new questions.",
1373
+ "",
1374
+ "Page text:",
1375
+ chunk
1376
+ ].join("\n");
1377
+ }
1378
+ function questionSchema(lenient = false) {
1379
+ const confidence = lenient ? {} : { type: "number", minimum: 0, maximum: 1 };
1380
+ const item = (properties, optional = []) => ({
1381
+ type: "object",
1382
+ additionalProperties: false,
1383
+ required: Object.keys(properties).filter((k) => !lenient || !optional.includes(k)),
1384
+ properties
1385
+ });
1386
+ return item(
1387
+ {
1388
+ pairs: {
1389
+ type: "array",
1390
+ items: item(
1391
+ {
1392
+ index: { type: "integer", minimum: 1, description: "The question's number." },
1393
+ quote: { type: "string", description: "The verbatim passage that answers it, or an empty string." },
1394
+ confidence
1395
+ },
1396
+ ["confidence"]
1397
+ )
1398
+ },
1399
+ drafts: {
1400
+ type: "array",
1401
+ items: item(
1402
+ {
1403
+ question: { type: "string", minLength: 1, description: "A question a reader would ask." },
1404
+ quote: { type: "string", minLength: 1, description: "The verbatim passage that answers it." },
1405
+ confidence
1406
+ },
1407
+ ["confidence"]
1408
+ )
1409
+ }
1410
+ },
1411
+ ["pairs", "drafts"]
1412
+ );
1413
+ }
1920
1414
 
1921
1415
  // src/graph/llm/cache.ts
1922
1416
  function cacheKey(provider, model, content, fields, sections = false) {
@@ -1933,6 +1427,16 @@ function cacheKey(provider, model, content, fields, sections = false) {
1933
1427
  sections ? "sections" : "doc-only"
1934
1428
  ]);
1935
1429
  }
1430
+ function questionCacheKey(provider, model, chunk, request) {
1431
+ return buildCacheKey([
1432
+ provider,
1433
+ model,
1434
+ `q${QUESTION_PROMPT_VERSION}`,
1435
+ sha256(chunk),
1436
+ "questions",
1437
+ sha256(JSON.stringify(request))
1438
+ ]);
1439
+ }
1936
1440
  var FillCache = class extends JsonCache {
1937
1441
  constructor(dir, enabled = true) {
1938
1442
  super(dir, enabled, "manni");
@@ -1960,8 +1464,8 @@ function selectProvider2(config, flags = {}) {
1960
1464
  }
1961
1465
  ]);
1962
1466
  }
1963
- function announceSelection(selection) {
1964
- const message = localNotice(selection);
1467
+ function announceSelection(selection, flags = {}) {
1468
+ const message = localNotice(selection, flags);
1965
1469
  if (message !== void 0) noticeOnce(message);
1966
1470
  }
1967
1471
  function assertProviderSelection(selection) {
@@ -1976,16 +1480,19 @@ function assertProviderSelection(selection) {
1976
1480
  function providerSpecFor2(config, selection) {
1977
1481
  return providerSpecFor(config.providers, selection);
1978
1482
  }
1979
- async function resolveProviderIdentity(config, flags = {}) {
1483
+ function selectedSpec(config, flags) {
1980
1484
  const selection = selectProvider2(config, flags);
1981
1485
  const { provider, model } = selection;
1982
1486
  assertKnownProvider(provider, toGraphError);
1983
1487
  assertModelHasProvider(provider, model, CONFIG_KEY, toGraphError);
1984
- announceSelection(selection);
1985
- return resolveIdentity(
1986
- providerSpecFor2(config, { provider, model: model ?? null }),
1987
- toGraphError
1988
- );
1488
+ announceSelection(selection, flags);
1489
+ return providerSpecFor2(config, { provider, model: model ?? null });
1490
+ }
1491
+ async function resolveProviderIdentity(config, flags = {}) {
1492
+ return resolveIdentity(selectedSpec(config, flags), toGraphError);
1493
+ }
1494
+ async function resolveOfflineIdentity(config, flags) {
1495
+ return offlineIdentity(selectedSpec(config, flags), toGraphError);
1989
1496
  }
1990
1497
  function constructProvider(config, identity) {
1991
1498
  try {
@@ -1998,36 +1505,411 @@ async function makeProvider(config, flags = {}) {
1998
1505
  return constructProvider(config, await resolveProviderIdentity(config, flags));
1999
1506
  }
2000
1507
 
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;
2017
- }
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;
2023
- }
2024
- }
2025
- return out;
1508
+ // src/graph/commands/fill-questions.ts
1509
+ import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
1510
+ import { resolve as resolve5 } from "node:path";
1511
+ import { isMap as isMap2, isSeq, parseDocument as parseDocument2 } from "yaml";
1512
+ var QUESTIONS_KEY = "sample-questions";
1513
+ function isRecord(value) {
1514
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2026
1515
  }
2027
1516
  function round2(n) {
2028
1517
  return Math.round(n * 100) / 100;
2029
1518
  }
2030
- var META_PROVENANCE_KEY = "meta-provenance";
1519
+ function score(value) {
1520
+ return typeof value === "number" && value >= 0 && value <= 1 ? value : 0;
1521
+ }
1522
+ function linesOf(pin) {
1523
+ const spec = pin.lines;
1524
+ return typeof spec === "number" || typeof spec === "string" ? parseLines(spec) : void 0;
1525
+ }
1526
+ function readProposal(value) {
1527
+ const list = (v) => Array.isArray(v) ? v.filter(isRecord) : [];
1528
+ return {
1529
+ pairs: list(value.pairs).flatMap(
1530
+ (p) => typeof p.index === "number" && typeof p.quote === "string" ? [{ index: p.index, quote: p.quote, confidence: score(p.confidence) }] : []
1531
+ ),
1532
+ drafts: list(value.drafts).flatMap(
1533
+ (d) => typeof d.question === "string" && d.question.trim() !== "" && typeof d.quote === "string" ? [{ question: d.question.trim(), quote: d.quote, confidence: score(d.confidence) }] : []
1534
+ )
1535
+ };
1536
+ }
1537
+ function locateQuote(body, quote) {
1538
+ const wanted = quote.replace(/\s+/gu, " ").trim();
1539
+ if (wanted === "") return void 0;
1540
+ const text = body.join("\n");
1541
+ let folded = "";
1542
+ const at = [];
1543
+ let space = false;
1544
+ for (let i = 0; i < text.length; i++) {
1545
+ const ch = text.charAt(i);
1546
+ if (/\s/u.test(ch)) {
1547
+ if (!space && folded !== "") {
1548
+ folded += " ";
1549
+ at.push(i);
1550
+ }
1551
+ space = true;
1552
+ } else {
1553
+ folded += ch;
1554
+ at.push(i);
1555
+ space = false;
1556
+ }
1557
+ }
1558
+ const found = folded.indexOf(wanted);
1559
+ if (found < 0) return void 0;
1560
+ const lineAt = (offset) => text.slice(0, offset).split("\n").length;
1561
+ const start = at[found];
1562
+ const end = at[found + wanted.length - 1];
1563
+ if (start === void 0 || end === void 0) return void 0;
1564
+ const twice = folded.indexOf(wanted, found + wanted.length) >= 0;
1565
+ return { start: lineAt(start), end: lineAt(end), ...twice ? { ambiguous: true } : {} };
1566
+ }
1567
+ function twiceWarning(path2, question, lines) {
1568
+ return `${path2}: the answer to "${question}" appears more than once, so ${spellLines(lines)} is pinned; check it is the right one.`;
1569
+ }
1570
+ function pinFor(body, lines) {
1571
+ return { lines: lineSpec(lines), integrity: pinOfLines(body, lines) };
1572
+ }
1573
+ function relocate(pins, before, after) {
1574
+ const delta = after.length - before.length;
1575
+ let moved = false;
1576
+ for (const pin of pins) {
1577
+ const lines = linesOf(pin);
1578
+ const integrity = pin.integrity;
1579
+ if (lines === void 0 || typeof integrity !== "string") continue;
1580
+ if (pinOfLines(after, lines) === integrity) continue;
1581
+ const length = lines.end - lines.start + 1;
1582
+ const around = lines.start + delta;
1583
+ const { starts } = findWindows(after, length, integrity, void 0, { around });
1584
+ const [first, ...rest] = starts;
1585
+ if (first === void 0) continue;
1586
+ const nearest = rest.reduce(
1587
+ (best, s) => Math.abs(s - around) < Math.abs(best - around) ? s : best,
1588
+ first
1589
+ );
1590
+ pin.lines = lineSpec({ start: nearest, end: nearest + length - 1 });
1591
+ moved = true;
1592
+ }
1593
+ return moved;
1594
+ }
1595
+ function settle(tracked) {
1596
+ return tracked.map(({ pin, ...outcome }) => {
1597
+ const lines = pin === void 0 ? void 0 : linesOf(pin);
1598
+ return lines === void 0 ? outcome : { ...outcome, lines: spellLines(lines) };
1599
+ });
1600
+ }
1601
+ async function fillPageQuestions(input) {
1602
+ const { path: path2, format, content, settings, asker, known } = input;
1603
+ const { body } = bodyOf(content, format);
1604
+ const held = metadataOf(content, path2, format).data[QUESTIONS_KEY];
1605
+ const raw = Array.isArray(held) ? held : held === void 0 ? [] : [held];
1606
+ const tracked = [];
1607
+ const warnings = [];
1608
+ const items = [];
1609
+ const pins = [];
1610
+ const needs = [];
1611
+ let questions = 0;
1612
+ let changed = false;
1613
+ for (const value of raw) {
1614
+ if (typeof value === "string" && value !== "") {
1615
+ questions++;
1616
+ needs.push({ at: items.length, question: value, stale: /* @__PURE__ */ new Set() });
1617
+ items.push(value);
1618
+ continue;
1619
+ }
1620
+ if (!isRecord(value) || typeof value.question !== "string" || value.question === "") {
1621
+ items.push(value);
1622
+ continue;
1623
+ }
1624
+ questions++;
1625
+ const question = value.question;
1626
+ const answers = Array.isArray(value.answers) ? value.answers.map((a) => isRecord(a) ? { ...a } : a) : [];
1627
+ const item = { ...value };
1628
+ if (Array.isArray(value.answers)) item.answers = answers;
1629
+ const stale = /* @__PURE__ */ new Set();
1630
+ for (const answer of answers) {
1631
+ if (!isRecord(answer)) continue;
1632
+ const lines = linesOf(answer);
1633
+ if (lines === void 0) continue;
1634
+ const integrity = answer.integrity;
1635
+ if (typeof integrity !== "string") {
1636
+ const minted = pinOfLines(body, lines);
1637
+ if (minted === void 0) {
1638
+ stale.add(answer);
1639
+ continue;
1640
+ }
1641
+ answer.integrity = minted;
1642
+ pins.push(answer);
1643
+ tracked.push({ action: "re-pinned", question, pin: answer });
1644
+ changed = true;
1645
+ continue;
1646
+ }
1647
+ const fresh = freshness(body, { lines, integrity, reviewed: true });
1648
+ if (fresh.state === "changed") {
1649
+ stale.add(answer);
1650
+ continue;
1651
+ }
1652
+ pins.push(answer);
1653
+ const [range] = fresh.ranges;
1654
+ if (fresh.state === "moved" && range !== void 0) {
1655
+ answer.lines = lineSpec(range);
1656
+ tracked.push({ action: "re-pinned", question, from: spellLines(lines), pin: answer });
1657
+ changed = true;
1658
+ } else if (fresh.state === "ambiguous") {
1659
+ warnings.push(
1660
+ `${path2}: the answer to "${question}" moved, and ${matchList(fresh.ranges)}, so pin one of them by hand.`
1661
+ );
1662
+ }
1663
+ }
1664
+ if (answers.length === 0 || stale.size > 0) {
1665
+ needs.push({ at: items.length, question, stale });
1666
+ }
1667
+ items.push(item);
1668
+ }
1669
+ const draft = Math.max(0, settings.perPage - questions);
1670
+ let state = "complete";
1671
+ let skipped;
1672
+ if (needs.length > 0 || draft > 0) {
1673
+ const proposals = asker === void 0 ? "offline" : await ask(asker);
1674
+ if (typeof proposals === "string" || asker === void 0) {
1675
+ skipped = typeof proposals === "string" ? proposals : "offline";
1676
+ state = "skipped";
1677
+ } else {
1678
+ state = "unchanged";
1679
+ if (pair(proposals)) changed = true;
1680
+ if (drafts(proposals, asker)) changed = true;
1681
+ }
1682
+ }
1683
+ if (!changed) {
1684
+ return { content, outcomes: settle(tracked), state, ...skipped ? { skipped } : {}, warnings };
1685
+ }
1686
+ const write = () => writeGraphFields(content, path2, format, {}, { page: { [QUESTIONS_KEY]: items } }).content;
1687
+ let next = write();
1688
+ let before = body;
1689
+ for (let round = 0; ; round++) {
1690
+ const after = bodyOf(next, format).body;
1691
+ if (!relocate(pins, before, after)) break;
1692
+ if (round === 2) {
1693
+ next = write();
1694
+ warnings.push(`${path2}: the answer pins did not settle, so check their lines.`);
1695
+ break;
1696
+ }
1697
+ before = after;
1698
+ next = write();
1699
+ }
1700
+ return {
1701
+ content: next,
1702
+ outcomes: settle(tracked),
1703
+ state: "changed",
1704
+ ...skipped ? { skipped } : {},
1705
+ warnings
1706
+ };
1707
+ async function ask(model) {
1708
+ const request = {
1709
+ path: path2,
1710
+ title: input.title,
1711
+ concepts: await input.concepts(),
1712
+ pair: needs.map((n) => n.question),
1713
+ draft
1714
+ };
1715
+ const out = [];
1716
+ for (const chunk of splitBody(body.join("\n"), settings.chunkChars)) {
1717
+ const answer = await model.ask(request, chunk);
1718
+ if (answer === void 0) return "turn budget";
1719
+ out.push(readProposal(answer));
1720
+ }
1721
+ return out;
1722
+ }
1723
+ function pair(proposals) {
1724
+ let wrote = false;
1725
+ for (const [n, need] of needs.entries()) {
1726
+ const action = need.stale.size > 0 ? "re-paired" : "paired";
1727
+ const question = need.question;
1728
+ const candidates = proposals.flatMap((p) => p.pairs.filter((c) => c.index === n + 1 && c.quote.trim() !== "")).sort((a, b) => b.confidence - a.confidence);
1729
+ if (candidates.length === 0) {
1730
+ tracked.push({ action: "not-paired", question, reason: "no passage answers it" });
1731
+ continue;
1732
+ }
1733
+ let found;
1734
+ for (const c of candidates) {
1735
+ const lines = locateQuote(body, c.quote);
1736
+ if (lines !== void 0) {
1737
+ if (lines.ambiguous) warnings.push(twiceWarning(path2, question, lines));
1738
+ found = { lines: { start: lines.start, end: lines.end }, confidence: c.confidence };
1739
+ break;
1740
+ }
1741
+ }
1742
+ if (found === void 0) {
1743
+ tracked.push({ action: "not-paired", question, reason: "answer not on page" });
1744
+ continue;
1745
+ }
1746
+ const confidence = round2(found.confidence);
1747
+ if (found.confidence < settings.confidenceThreshold) {
1748
+ tracked.push({ action: "not-paired", question, confidence, reason: "low confidence, not written" });
1749
+ continue;
1750
+ }
1751
+ const pin = { ...pinFor(body, found.lines), reviewed: false };
1752
+ pins.push(pin);
1753
+ const item = items[need.at];
1754
+ if (typeof item === "string") {
1755
+ items[need.at] = { question: item, answers: [pin] };
1756
+ } else if (isRecord(item)) {
1757
+ const kept = Array.isArray(item.answers) ? item.answers.filter((a) => !need.stale.has(a)) : [];
1758
+ item.answers = [...kept, pin];
1759
+ }
1760
+ tracked.push({ action, question, confidence, pin });
1761
+ wrote = true;
1762
+ }
1763
+ return wrote;
1764
+ }
1765
+ function drafts(proposals, model) {
1766
+ const pool = proposals.flatMap((p) => p.drafts).sort((a, b) => b.confidence - a.confidence);
1767
+ let accepted = 0;
1768
+ for (const d of pool) {
1769
+ if (accepted >= draft) break;
1770
+ const question = d.question;
1771
+ const confidence = round2(d.confidence);
1772
+ const located = locateQuote(body, d.quote);
1773
+ if (located === void 0) {
1774
+ tracked.push({ action: "dropped", question, reason: "answer not on page" });
1775
+ continue;
1776
+ }
1777
+ if (located.ambiguous) warnings.push(twiceWarning(path2, question, located));
1778
+ const lines = { start: located.start, end: located.end };
1779
+ const key = questionKey(question);
1780
+ const asked = key === "" ? void 0 : known.get(key);
1781
+ if (asked !== void 0) {
1782
+ tracked.push({ action: "dropped", question, reason: `duplicate of ${asked}` });
1783
+ continue;
1784
+ }
1785
+ const copied = copiedPairs(question, body.slice(lines.start - 1, lines.end).join("\n"));
1786
+ if (copied && copied.copied / copied.of > COPIED_SHARE) {
1787
+ tracked.push({
1788
+ action: "dropped",
1789
+ question,
1790
+ reason: `copies its answer (${String(copied.copied)} of ${String(copied.of)} word pairs)`
1791
+ });
1792
+ continue;
1793
+ }
1794
+ if (d.confidence < settings.confidenceThreshold) {
1795
+ tracked.push({ action: "dropped", question, confidence, reason: "low confidence, not written" });
1796
+ continue;
1797
+ }
1798
+ const pin = pinFor(body, lines);
1799
+ pins.push(pin);
1800
+ items.push({
1801
+ question,
1802
+ answers: [pin],
1803
+ ...settings.writeProvenance ? { "generated-by": model.generatedBy, confidence } : {},
1804
+ reviewed: false
1805
+ });
1806
+ if (key !== "") known.set(key, path2);
1807
+ tracked.push({ action: "drafted", question, confidence, pin });
1808
+ accepted++;
1809
+ }
1810
+ return accepted > 0;
1811
+ }
1812
+ }
1813
+ function questionsIn(content, path2, format) {
1814
+ const held = metadataOf(content, path2, format).data[QUESTIONS_KEY];
1815
+ const raw = Array.isArray(held) ? held : held === void 0 ? [] : [held];
1816
+ return raw.flatMap(
1817
+ (v) => typeof v === "string" ? [v] : isRecord(v) && typeof v.question === "string" ? [v.question] : []
1818
+ );
1819
+ }
1820
+ function questionsInFile(abs, label) {
1821
+ return readQuestionFile(abs, label).map((q) => q.question);
1822
+ }
1823
+ function repinQuestionFile(abs, label, configDir, dryRun) {
1824
+ const text = existsSync2(abs) ? readFileSync3(abs, "utf8") : void 0;
1825
+ readQuestionFile(abs, label, text);
1826
+ const doc = parseDocument2(text ?? "");
1827
+ const outcomes = [];
1828
+ const warnings = [];
1829
+ const bodies = /* @__PURE__ */ new Map();
1830
+ const bodyAt = (file) => {
1831
+ if (!bodies.has(file)) {
1832
+ const page = resolve5(configDir, file);
1833
+ bodies.set(
1834
+ file,
1835
+ existsSync2(page) ? bodyOf(readFileSync3(page, "utf8"), formatOf(file)).body : void 0
1836
+ );
1837
+ }
1838
+ return bodies.get(file);
1839
+ };
1840
+ let changed = false;
1841
+ const seq = doc.contents;
1842
+ for (const node of isSeq(seq) ? seq.items : []) {
1843
+ if (!isMap2(node)) continue;
1844
+ const question = node.get("question");
1845
+ const answers = node.get("answers", true);
1846
+ if (typeof question !== "string" || !isSeq(answers)) continue;
1847
+ for (const answer of answers.items) {
1848
+ if (!isMap2(answer)) continue;
1849
+ const file = answer.get("file");
1850
+ const spec = answer.get("lines");
1851
+ const lines = typeof spec === "number" || typeof spec === "string" ? parseLines(spec) : void 0;
1852
+ if (typeof file !== "string" || lines === void 0) continue;
1853
+ const body = bodyAt(file);
1854
+ if (body === void 0) continue;
1855
+ const integrity = answer.get("integrity");
1856
+ if (typeof integrity !== "string") {
1857
+ const minted = pinOfLines(body, lines);
1858
+ if (minted === void 0) continue;
1859
+ answer.set("integrity", minted);
1860
+ outcomes.push({ action: "re-pinned", question, file, lines: spellLines(lines) });
1861
+ changed = true;
1862
+ continue;
1863
+ }
1864
+ const fresh = freshness(body, { lines, integrity, reviewed: true });
1865
+ if (fresh.state === "changed") continue;
1866
+ const [range] = fresh.ranges;
1867
+ if (fresh.state === "moved" && range !== void 0) {
1868
+ answer.set("lines", lineSpec(range));
1869
+ outcomes.push({ action: "re-pinned", question, file, from: spellLines(lines), lines: spellLines(range) });
1870
+ changed = true;
1871
+ } else if (fresh.state === "ambiguous") {
1872
+ warnings.push(
1873
+ `${label}: the answer to "${question}" in ${file} moved, and ${matchList(fresh.ranges)}, so pin one of them by hand.`
1874
+ );
1875
+ }
1876
+ }
1877
+ }
1878
+ if (changed && !dryRun) writeFileSync2(abs, doc.toString(), "utf8");
1879
+ return { changed, outcomes, warnings };
1880
+ }
1881
+
1882
+ // src/graph/commands/fill.ts
1883
+ var TURN_BUDGET_REASON = "turn budget";
1884
+ var OFFLINE_REASON = "offline";
1885
+ var RELATION_FIELDS = [
1886
+ "alt-labels",
1887
+ "broader",
1888
+ "narrower",
1889
+ "related-concepts"
1890
+ ];
1891
+ function numberMap(v) {
1892
+ const out = {};
1893
+ if (v && typeof v === "object" && !Array.isArray(v)) {
1894
+ for (const [k, val] of Object.entries(v)) {
1895
+ if (typeof val === "number" && val >= 0 && val <= 1) out[k] = val;
1896
+ }
1897
+ }
1898
+ return out;
1899
+ }
1900
+ function stringMap(v) {
1901
+ const out = {};
1902
+ if (v && typeof v === "object" && !Array.isArray(v)) {
1903
+ for (const [k, val] of Object.entries(v)) {
1904
+ if (typeof val === "string") out[k] = val;
1905
+ }
1906
+ }
1907
+ return out;
1908
+ }
1909
+ function round22(n) {
1910
+ return Math.round(n * 100) / 100;
1911
+ }
1912
+ var META_PROVENANCE_KEY = "meta-provenance";
2031
1913
  function graphPointer(field) {
2032
1914
  return `/graph/${field.replace(/~/g, "~0").replace(/\//g, "~1")}`;
2033
1915
  }
@@ -2042,10 +1924,12 @@ async function runFill(opts = {}) {
2042
1924
  },
2043
1925
  cwd
2044
1926
  );
1927
+ const offline = opts.offline ?? config.fill.offline;
2045
1928
  const flags = {
2046
1929
  ...opts.provider === void 0 ? {} : { provider: opts.provider },
2047
1930
  ...opts.model === void 0 ? {} : { model: opts.model },
2048
- ...opts.local === void 0 ? {} : { local: opts.local }
1931
+ ...opts.local === void 0 ? {} : { local: opts.local },
1932
+ ...offline ? { offline: true } : {}
2049
1933
  };
2050
1934
  const selection = selectProvider2(config, flags);
2051
1935
  assertProviderSelection(selection);
@@ -2077,11 +1961,20 @@ async function runFill(opts = {}) {
2077
1961
  };
2078
1962
  let identity;
2079
1963
  let construct;
2080
- if (opts.providerInstance) {
1964
+ if (offline) {
1965
+ const local = await resolveOfflineIdentity(config, flags);
1966
+ const injected = opts.providerInstance;
1967
+ identity = local === void 0 || injected === void 0 ? local : { provider: injected.provider(), model: injected.modelName() };
1968
+ construct = () => {
1969
+ if (injected !== void 0) return injected;
1970
+ if (local === void 0) throw new GraphError(noOfflineModel("nothing was proposed"));
1971
+ return constructProvider(config, local);
1972
+ };
1973
+ } else if (opts.providerInstance) {
2081
1974
  const injected = opts.providerInstance;
2082
1975
  identity = { provider: injected.provider(), model: injected.modelName() };
2083
1976
  construct = () => injected;
2084
- announceSelection(selection);
1977
+ announceSelection(selection, flags);
2085
1978
  } else {
2086
1979
  const resolved = await resolveProviderIdentity(config, flags);
2087
1980
  identity = resolved;
@@ -2089,7 +1982,11 @@ async function runFill(opts = {}) {
2089
1982
  }
2090
1983
  let provider;
2091
1984
  const getProvider = () => provider ??= construct();
2092
- const fields = opts.fields ?? config.fill.fields;
1985
+ const configured = opts.fields ?? config.fill.fields;
1986
+ const withQuestions = configured.includes("sample-questions");
1987
+ const fields = configured.filter(
1988
+ (f) => f !== "sample-questions"
1989
+ );
2093
1990
  const withSections = opts.sections ?? config.fill.sections;
2094
1991
  const sectionFields = withSections ? fields.filter((f) => SECTION_FILL_FIELDS.includes(f)) : void 0;
2095
1992
  const validateProposal = validatorFor(
@@ -2178,6 +2075,93 @@ async function runFill(opts = {}) {
2178
2075
  );
2179
2076
  }
2180
2077
  }
2078
+ const questionSettings = {
2079
+ perPage: config.fill.questions.perPage,
2080
+ confidenceThreshold,
2081
+ chunkChars: opts.chunkChars ?? config.fill.chunkChars,
2082
+ writeProvenance: config.fill.writeProvenance
2083
+ };
2084
+ const known = /* @__PURE__ */ new Map();
2085
+ if (withQuestions) {
2086
+ const asked = [
2087
+ ...[...mergedTexts].sort(([a], [b]) => byCodeUnit(a, b)),
2088
+ ...stdinContent === void 0 ? [] : [[STDIN_PATH, stdinContent]]
2089
+ ];
2090
+ for (const [path2, text] of asked) {
2091
+ for (const q of questionsIn(text, path2, formatOf(path2, formatFor(path2)))) {
2092
+ const key = questionKey(q);
2093
+ if (key !== "" && !known.has(key)) known.set(key, path2);
2094
+ }
2095
+ }
2096
+ for (const label of config.questionFiles) {
2097
+ for (const q of questionsInFile(resolve6(config.configDir, label), label)) {
2098
+ const key = questionKey(q);
2099
+ if (key !== "" && !known.has(key)) known.set(key, label);
2100
+ }
2101
+ }
2102
+ }
2103
+ const questionValidator = validatorFor(questionSchema(true));
2104
+ const questionRequestSchema = questionSchema();
2105
+ const asker = identity === void 0 ? void 0 : questionAsker(identity);
2106
+ function questionAsker(id) {
2107
+ return {
2108
+ generatedBy: `${id.provider}/${id.model}`,
2109
+ ask: async (request, chunk) => {
2110
+ const key = questionCacheKey(id.provider, id.model, chunk, request);
2111
+ const hit = cache.get(key);
2112
+ if (hit !== void 0 && questionValidator(hit)) return hit;
2113
+ if (maxTurns !== null && turnsUsed >= maxTurns) return void 0;
2114
+ turnsUsed++;
2115
+ const run = await completeValidatedJSON({
2116
+ provider: getProvider(),
2117
+ system: QUESTION_SYSTEM_PROMPT,
2118
+ user: buildQuestionPrompt(request, chunk),
2119
+ schema: questionRequestSchema,
2120
+ validate: questionValidator,
2121
+ temperature: config.fill.temperature
2122
+ });
2123
+ if (run.result === void 0) {
2124
+ throw new Error(run.error ?? "provider returned no questions");
2125
+ }
2126
+ cache.set(key, run.result);
2127
+ return run.result;
2128
+ }
2129
+ };
2130
+ }
2131
+ let terms;
2132
+ const termsOnce = () => terms ??= (async () => {
2133
+ const source = termLabelSource();
2134
+ if (!config.build.derive.includes("terms") || source === void 0) return void 0;
2135
+ try {
2136
+ return termIndex(
2137
+ await source({
2138
+ cwd,
2139
+ ...opts.config === void 0 ? {} : { configPath: opts.config },
2140
+ ...opts.noConfig === true ? { noConfig: true } : {}
2141
+ })
2142
+ );
2143
+ } catch {
2144
+ return void 0;
2145
+ }
2146
+ })();
2147
+ async function conceptsOf(data) {
2148
+ const strings = (v) => typeof v === "string" ? [v] : Array.isArray(v) ? v.filter((s) => typeof s === "string") : [];
2149
+ const block = data[GRAPH_KEY2];
2150
+ const graph = block !== null && typeof block === "object" && !Array.isArray(block) ? block : {};
2151
+ const out = /* @__PURE__ */ new Map();
2152
+ for (const label of strings(graph["label"])) out.set(label, strings(graph["alt-labels"]));
2153
+ const named = strings(graph["concepts"] ?? data["concepts"]);
2154
+ const index = named.length > 0 ? await termsOnce() : void 0;
2155
+ const language = typeof data["language"] === "string" ? canonicalLanguage(data["language"]) : void 0;
2156
+ for (const label of named) {
2157
+ const id = index?.resolve(label, language);
2158
+ const group = id === void 0 ? void 0 : index?.groups.get(id);
2159
+ const term = group?.find((t) => canonicalLanguage(t.language) === language) ?? group?.[0];
2160
+ if (term === void 0) out.set(label, out.get(label) ?? []);
2161
+ else out.set(term.label, [...term.altLabels ?? []]);
2162
+ }
2163
+ return [...out].map(([label, altLabels]) => ({ label, altLabels }));
2164
+ }
2181
2165
  for (const path2 of files) {
2182
2166
  const absPath = resolve6(base2, path2);
2183
2167
  let content;
@@ -2189,7 +2173,7 @@ async function runFill(opts = {}) {
2189
2173
  );
2190
2174
  }
2191
2175
  try {
2192
- results.push(await fillOne(path2, absPath, content));
2176
+ results.push(await fillPage(path2, absPath, content));
2193
2177
  } catch (e) {
2194
2178
  results.push({
2195
2179
  path: path2,
@@ -2204,7 +2188,7 @@ async function runFill(opts = {}) {
2204
2188
  let stdinDocument;
2205
2189
  if (stdinContent !== void 0) {
2206
2190
  try {
2207
- const result = await fillOne(STDIN_PATH, void 0, stdinContent);
2191
+ const result = await fillPage(STDIN_PATH, void 0, stdinContent);
2208
2192
  results.push(result);
2209
2193
  stdinDocument = result.filledContent ?? stdinContent;
2210
2194
  Reflect.deleteProperty(result, "filledContent");
@@ -2219,6 +2203,32 @@ async function runFill(opts = {}) {
2219
2203
  });
2220
2204
  }
2221
2205
  }
2206
+ if (withQuestions) {
2207
+ for (const label of config.questionFiles) {
2208
+ const repinned = repinQuestionFile(
2209
+ resolve6(config.configDir, label),
2210
+ label,
2211
+ config.configDir,
2212
+ opts.dryRun ?? false
2213
+ );
2214
+ warnings.push(...repinned.warnings);
2215
+ results.push({
2216
+ path: label,
2217
+ status: repinned.changed ? opts.dryRun ? "proposed" : "filled" : "complete",
2218
+ fields: repinned.changed ? [QUESTIONS_KEY] : [],
2219
+ preserved: [],
2220
+ cached: false,
2221
+ ...repinned.outcomes.length > 0 ? { questions: repinned.outcomes } : {}
2222
+ });
2223
+ }
2224
+ }
2225
+ if (offline && identity === void 0 && results.some((r) => r.reason === OFFLINE_REASON)) {
2226
+ warnings.push(
2227
+ noOfflineModel(
2228
+ withQuestions ? "only answer pins were updated, and nothing was drafted or proposed" : "nothing was proposed"
2229
+ )
2230
+ );
2231
+ }
2222
2232
  if (sectionsUnrecorded.length > 0) {
2223
2233
  warnings.push(
2224
2234
  "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 +2243,45 @@ async function runFill(opts = {}) {
2233
2243
  exitCode: hasErrors ? 1 : 0,
2234
2244
  ...stdinDocument === void 0 ? {} : { stdinDocument }
2235
2245
  };
2246
+ async function fillPage(path2, absPath, content) {
2247
+ const { after, merged, ...result } = await fillOne(path2, absPath, content);
2248
+ if (!withQuestions) return result;
2249
+ const pageFormat = formatOf(path2, format);
2250
+ const data = metadataOf(merged ?? mergedTexts.get(path2) ?? content, path2, pageFormat).data;
2251
+ const pass = await fillPageQuestions({
2252
+ path: path2,
2253
+ format: pageFormat,
2254
+ content: after ?? content,
2255
+ title: typeof data["title"] === "string" ? data["title"] : path2,
2256
+ concepts: () => conceptsOf(data),
2257
+ settings: questionSettings,
2258
+ asker,
2259
+ known
2260
+ });
2261
+ warnings.push(...pass.warnings);
2262
+ const outcomes = pass.outcomes.length > 0 ? { questions: pass.outcomes } : {};
2263
+ if (pass.state === "changed") {
2264
+ if (!opts.dryRun) {
2265
+ if (absPath === void 0) result.filledContent = pass.content;
2266
+ else writeFileSync3(absPath, pass.content, "utf8");
2267
+ }
2268
+ return {
2269
+ ...result,
2270
+ status: opts.dryRun ? "proposed" : "filled",
2271
+ fields: [...result.fields, QUESTIONS_KEY],
2272
+ ...pass.skipped === void 0 ? {} : { reason: pass.skipped },
2273
+ ...outcomes
2274
+ };
2275
+ }
2276
+ const idle = result.status === "complete" || result.status === "nothing-proposed";
2277
+ if (idle && pass.state === "skipped") {
2278
+ return { ...result, status: "skipped", ...pass.skipped === void 0 ? {} : { reason: pass.skipped }, ...outcomes };
2279
+ }
2280
+ if (result.status === "complete" && pass.state === "unchanged") {
2281
+ return { ...result, status: "nothing-proposed", ...outcomes };
2282
+ }
2283
+ return { ...result, ...outcomes };
2284
+ }
2236
2285
  async function fillOne(path2, absPath, content) {
2237
2286
  const pageFormat = formatOf(path2, format);
2238
2287
  if ((pageFormat === "markdown" || pageFormat === "mdx") && frontmatterKind(content) === "unsupported") {
@@ -2262,7 +2311,7 @@ async function runFill(opts = {}) {
2262
2311
  heldGraph !== null && typeof heldGraph === "object" && !Array.isArray(heldGraph) ? Object.keys(heldGraph) : []
2263
2312
  );
2264
2313
  const missing = opts.force ? fields : fields.filter((f) => !present.has(f));
2265
- if (missing.length === 0 && !withSections) {
2314
+ if (fields.length === 0 || missing.length === 0 && !withSections) {
2266
2315
  return {
2267
2316
  path: path2,
2268
2317
  status: "complete",
@@ -2271,6 +2320,16 @@ async function runFill(opts = {}) {
2271
2320
  cached: false
2272
2321
  };
2273
2322
  }
2323
+ if (identity === void 0) {
2324
+ return {
2325
+ path: path2,
2326
+ status: "skipped",
2327
+ reason: OFFLINE_REASON,
2328
+ fields: [],
2329
+ preserved: [],
2330
+ cached: false
2331
+ };
2332
+ }
2274
2333
  const key = cacheKey(
2275
2334
  identity.provider,
2276
2335
  identity.model,
@@ -2321,15 +2380,15 @@ async function runFill(opts = {}) {
2321
2380
  const unknownSlugs = [];
2322
2381
  if (withSections) {
2323
2382
  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"] : "";
2383
+ for (const entry of Array.isArray(proposal["sections"]) ? proposal["sections"] : []) {
2384
+ const slug = typeof entry["slug"] === "string" ? entry["slug"] : "";
2326
2385
  if (!realSlugs.has(slug)) {
2327
2386
  if (slug) unknownSlugs.push(slug);
2328
2387
  continue;
2329
2388
  }
2330
- const entryConfidence = numberMap(entry2["confidence"]);
2331
- const entryReasoning = stringMap(entry2["reasoning"]);
2332
- for (const [field, value] of Object.entries(entry2)) {
2389
+ const entryConfidence = numberMap(entry["confidence"]);
2390
+ const entryReasoning = stringMap(entry["reasoning"]);
2391
+ for (const [field, value] of Object.entries(entry)) {
2333
2392
  if (!SECTION_FILL_FIELDS.includes(field)) continue;
2334
2393
  if (!fields.includes(field)) continue;
2335
2394
  const name = `sections.${slug}.${field}`;
@@ -2401,7 +2460,7 @@ async function runFill(opts = {}) {
2401
2460
  const c = confidence[f];
2402
2461
  return {
2403
2462
  name,
2404
- confidence: c === void 0 ? priorConfidence[name] ?? 0 : round2(c)
2463
+ confidence: c === void 0 ? priorConfidence[name] ?? 0 : round22(c)
2405
2464
  };
2406
2465
  });
2407
2466
  const unscored = recordable.map((f) => [f, graphPointer(f)]).filter(
@@ -2474,7 +2533,7 @@ async function runFill(opts = {}) {
2474
2533
  }
2475
2534
  const manifests = await spliceAll(manifestWrites);
2476
2535
  if (!opts.dryRun && absPath !== void 0) {
2477
- if (pageContent !== content) writeFileSync2(absPath, pageContent, "utf8");
2536
+ if (pageContent !== content) writeFileSync3(absPath, pageContent, "utf8");
2478
2537
  for (const m of manifests) {
2479
2538
  if (m.text !== m.before) {
2480
2539
  await writeFileAtomic(m.absPath, m.text, { createParents: true });
@@ -2483,6 +2542,8 @@ async function runFill(opts = {}) {
2483
2542
  }
2484
2543
  guard?.commit(path2, applied.content);
2485
2544
  return {
2545
+ after: pageContent,
2546
+ merged: applied.content,
2486
2547
  path: path2,
2487
2548
  status: opts.dryRun ? "proposed" : "filled",
2488
2549
  fields: reportedFields,
@@ -2573,6 +2634,17 @@ async function openMetaViewPerPage(source, cwd, paths, base2, failures) {
2573
2634
  return openMetaView(source, cwd, readable, base2);
2574
2635
  }
2575
2636
  }
2637
+ function questionLine(q) {
2638
+ const score2 = q.confidence === void 0 ? "" : ` (${q.confidence.toFixed(2)})`;
2639
+ let detail;
2640
+ if (q.reason !== void 0) {
2641
+ detail = `${q.reason}${q.reason.startsWith("low confidence") ? score2 : ""}`;
2642
+ } else {
2643
+ const where = `${q.file === void 0 ? "" : `${q.file} `}lines ${q.from ?? q.lines ?? ""}`;
2644
+ detail = q.action === "re-pinned" ? `${where}${q.from === void 0 || q.lines === void 0 ? "" : ` to ${q.lines}`}` : `${where}${score2}`;
2645
+ }
2646
+ return `${q.action.padEnd(11)}"${q.question}" ${detail}`;
2647
+ }
2576
2648
  function renderFill(report, format) {
2577
2649
  if (format === "json") {
2578
2650
  return JSON.stringify({ ...report, stdinDocument: void 0 }, null, 2);
@@ -2608,6 +2680,7 @@ function renderFill(report, format) {
2608
2680
  lines.push(`error ${r.path}: ${r.error ?? ""}`);
2609
2681
  break;
2610
2682
  }
2683
+ for (const q of r.questions ?? []) lines.push(` ${questionLine(q)}`);
2611
2684
  }
2612
2685
  if (report.maxTurns !== null && report.results.some((r) => r.status === "skipped")) {
2613
2686
  lines.push(
@@ -2620,196 +2693,10 @@ function renderFill(report, format) {
2620
2693
 
2621
2694
  // src/graph/commands/embed.ts
2622
2695
  import { createHash as createHash2 } from "node:crypto";
2623
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
2696
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
2624
2697
  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");
2698
+ function embedText(entry) {
2699
+ return [entry.title, entry.labels, entry.description, entry.text].filter((part) => part !== void 0 && part !== "").join("\n\n");
2813
2700
  }
2814
2701
  function sha2562(text) {
2815
2702
  return createHash2("sha256").update(text, "utf8").digest("hex");
@@ -2838,7 +2725,7 @@ var VectorCache = class {
2838
2725
  set(key, vector) {
2839
2726
  if (!this.enabled) return;
2840
2727
  mkdirSync2(this.dir, { recursive: true });
2841
- writeFileSync3(this.path(key), JSON.stringify([...vector]), "utf8");
2728
+ writeFileSync4(this.path(key), JSON.stringify([...vector]), "utf8");
2842
2729
  }
2843
2730
  };
2844
2731
  async function makeEmbedder(opts, model, dtype) {
@@ -2906,8 +2793,8 @@ async function runEmbed(opts = {}) {
2906
2793
  };
2907
2794
  const languages = [];
2908
2795
  const updated = [];
2909
- for (const entry2 of manifest.languages) {
2910
- const indexPath = join2(indexDir, entry2.search.path);
2796
+ for (const entry of manifest.languages) {
2797
+ const indexPath = join2(indexDir, entry.search.path);
2911
2798
  if (!existsSync3(indexPath)) {
2912
2799
  throw new GraphError(
2913
2800
  `Search index not found: ${indexPath} \u2014 the manifest names it; re-run \`manni graph export search\`.`
@@ -2915,9 +2802,9 @@ async function runEmbed(opts = {}) {
2915
2802
  }
2916
2803
  const raw = readFileSync5(indexPath, "utf8");
2917
2804
  const source = `sha256:${sha2562(raw)}`;
2918
- if (entry2.search.digest !== source) {
2805
+ if (entry.search.digest !== source) {
2919
2806
  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\`.`
2807
+ `Stale manifest: ${entry.search.path} does not match the digest recorded for "${entry.language}" \u2014 re-run \`manni graph export search\`.`
2921
2808
  );
2922
2809
  }
2923
2810
  let parsed;
@@ -2934,7 +2821,7 @@ async function runEmbed(opts = {}) {
2934
2821
  `Not a manni graph search index: ${indexPath} \u2014 expected an \`entries\` array.`
2935
2822
  );
2936
2823
  }
2937
- const wanted = canonicalLanguageTag(entry2.language);
2824
+ const wanted = canonicalLanguageTag(entry.language);
2938
2825
  const perLanguage = Object.entries(config.embed.byLanguage).find(
2939
2826
  ([tag]) => canonicalLanguageTag(tag) === wanted
2940
2827
  )?.[1];
@@ -2960,21 +2847,21 @@ async function runEmbed(opts = {}) {
2960
2847
  }
2961
2848
  vectors.push({ id: item.id, vector });
2962
2849
  }
2963
- const filename = vectorIndexFilename(entry2.language);
2850
+ const filename = vectorIndexFilename(entry.language);
2964
2851
  const outPath = join2(outDir, filename);
2965
2852
  const manifestRelative = manifestRelativePath(indexDir, outPath);
2966
- writeFileSync3(
2853
+ writeFileSync4(
2967
2854
  outPath,
2968
2855
  encodeVectorIndex(vectors, {
2969
2856
  model: identity.model,
2970
2857
  dtype: identity.dtype,
2971
2858
  source,
2972
- language: entry2.language
2859
+ language: entry.language
2973
2860
  })
2974
2861
  );
2975
2862
  const dims = vectors[0]?.vector.length ?? 0;
2976
2863
  languages.push({
2977
- language: entry2.language,
2864
+ language: entry.language,
2978
2865
  outPath,
2979
2866
  model: identity.model,
2980
2867
  dims,
@@ -2983,7 +2870,7 @@ async function runEmbed(opts = {}) {
2983
2870
  total: vectors.length
2984
2871
  });
2985
2872
  updated.push({
2986
- ...entry2,
2873
+ ...entry,
2987
2874
  vectors: {
2988
2875
  path: manifestRelative,
2989
2876
  model: identity.model,
@@ -2993,7 +2880,7 @@ async function runEmbed(opts = {}) {
2993
2880
  }
2994
2881
  });
2995
2882
  }
2996
- writeFileSync3(
2883
+ writeFileSync4(
2997
2884
  manifestPath,
2998
2885
  emitLocalizations({ version: 1, languages: updated }),
2999
2886
  "utf8"
@@ -3022,397 +2909,8 @@ function renderEmbed(report, format) {
3022
2909
  ].join("\n");
3023
2910
  }
3024
2911
 
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
2912
  // src/graph/commands/stats.ts
3415
- import { resolve as resolve9 } from "node:path";
2913
+ import { resolve as resolve8 } from "node:path";
3416
2914
  import { DataFactory as DataFactory4 } from "n3";
3417
2915
  function subjectsOfType(store, typeIri) {
3418
2916
  return store.getQuads(null, DataFactory4.namedNode(RDF_TYPE), DataFactory4.namedNode(typeIri), null).map((q) => q.subject.value).sort();
@@ -3433,7 +2931,7 @@ function runStats(opts = {}) {
3433
2931
  },
3434
2932
  cwd
3435
2933
  );
3436
- const store = loadGraph(resolve9(cwd, opts.graph ?? config.out));
2934
+ const store = loadGraph(resolve8(cwd, opts.graph ?? config.out));
3437
2935
  const top = opts.top ?? 5;
3438
2936
  const docIris = subjectsOfType(store, `${NS.graph}Document`);
3439
2937
  const docSet = new Set(docIris);
@@ -3538,8 +3036,18 @@ function runStats(opts = {}) {
3538
3036
  };
3539
3037
  })
3540
3038
  };
3039
+ const asked = /* @__PURE__ */ new Set();
3040
+ for (const q of store.getQuads(null, DataFactory4.namedNode(`${NS.graph}sampleQuestion`), null, null)) {
3041
+ if (!docSet.has(q.subject.value)) continue;
3042
+ for (const p of [`${NS.foaf}primaryTopic`, `${NS.dcterms}subject`]) {
3043
+ for (const t of store.getQuads(q.subject, DataFactory4.namedNode(p), null, null)) {
3044
+ asked.add(t.object.value);
3045
+ }
3046
+ }
3047
+ }
3048
+ const conceptsWithoutQuestions = subjectsOfType(store, `${NS.skos}Concept`).filter((c) => !asked.has(c)).sort(byCodeUnit);
3541
3049
  const uniform = opts.coverageThreshold;
3542
- const thresholds = uniform != null ? Object.fromEntries(coverage.map((c) => [c.field, uniform])) : config.stats.coverageThreshold;
3050
+ const thresholds = uniform != null ? Object.fromEntries(UNIFORM_COVERAGE_FIELD_NAMES.map((f) => [f, uniform])) : config.stats.coverageThreshold;
3543
3051
  const coverageFindings = coverage.flatMap((c) => {
3544
3052
  const threshold = thresholds[c.field];
3545
3053
  if (threshold === void 0) return [];
@@ -3571,13 +3079,21 @@ function runStats(opts = {}) {
3571
3079
  coverage,
3572
3080
  sectionCoverage,
3573
3081
  localization,
3082
+ conceptsWithoutQuestions,
3083
+ conceptLabels: Object.fromEntries(
3084
+ conceptsWithoutQuestions.map((c) => [c, conceptLabel(store, c)])
3085
+ ),
3574
3086
  coverageFindings,
3575
3087
  exitCode: failed ? 1 : 0
3576
3088
  };
3577
3089
  }
3090
+ function conceptLabel(store, iri2) {
3091
+ const labels = store.getQuads(DataFactory4.namedNode(iri2), DataFactory4.namedNode(`${NS.skos}prefLabel`), null, null).map((q) => q.object.value).sort(byCodeUnit);
3092
+ return labels[0] ?? compactIri(iri2);
3093
+ }
3578
3094
  function renderStats(report, format) {
3579
3095
  if (format === "json") {
3580
- const { exitCode: _exitCode, ...rest } = report;
3096
+ const { exitCode: _exitCode, conceptLabels: _labels, ...rest } = report;
3581
3097
  return JSON.stringify(rest, null, 2);
3582
3098
  }
3583
3099
  const lines = [
@@ -3639,16 +3155,22 @@ function renderStats(report, format) {
3639
3155
  }
3640
3156
  lines.push(` no language: ${report.localization.unlabelled}`);
3641
3157
  }
3158
+ if (report.concepts > 0) {
3159
+ const unasked = report.conceptsWithoutQuestions;
3160
+ lines.push("", `Concepts with no question (${String(unasked.length)}):`);
3161
+ for (const c of unasked) lines.push(` ${report.conceptLabels[c] ?? compactIri(c)}`);
3162
+ if (unasked.length === 0) lines.push(" (none)");
3163
+ }
3642
3164
  return lines.join("\n");
3643
3165
  }
3644
3166
 
3645
3167
  // src/graph/commands/traverse.ts
3646
- import { resolve as resolve10 } from "node:path";
3168
+ import { resolve as resolve9 } from "node:path";
3647
3169
 
3648
3170
  // src/graph/runtime/traverse.ts
3649
- var DCTERMS_TITLE3 = `${NS.dcterms}title`;
3171
+ var DCTERMS_TITLE = `${NS.dcterms}title`;
3650
3172
  var DCTERMS_REFERENCES = `${NS.dcterms}references`;
3651
- var DCTERMS_LANGUAGE2 = `${NS.dcterms}language`;
3173
+ var DCTERMS_LANGUAGE = `${NS.dcterms}language`;
3652
3174
  function documentOf(iri2) {
3653
3175
  const hash = iri2.indexOf("#");
3654
3176
  return hash === -1 ? iri2 : iri2.slice(0, hash);
@@ -3658,7 +3180,7 @@ function resolveVariant(graph, input) {
3658
3180
  const wanted = input.toLowerCase();
3659
3181
  const variants = graph.instancesOf(`${NS.iirds}ProductVariant`);
3660
3182
  for (const v of variants) {
3661
- if (graph.literal(v, DCTERMS_TITLE3)?.toLowerCase() === wanted) return v;
3183
+ if (graph.literal(v, DCTERMS_TITLE)?.toLowerCase() === wanted) return v;
3662
3184
  }
3663
3185
  for (const v of variants) {
3664
3186
  if (v.slice(v.lastIndexOf("/") + 1).toLowerCase() === wanted) return v;
@@ -3680,7 +3202,7 @@ function scopeExclusion(graph, iri2, scope) {
3680
3202
  "iri"
3681
3203
  ],
3682
3204
  [scope.subjectIri, IIRDS_HAS_SUBJECT, GRAPH_NOT_SOFTWARE_SUBJECT, "iri"],
3683
- [scope.language, DCTERMS_LANGUAGE2, void 0, "literal"]
3205
+ [scope.language, DCTERMS_LANGUAGE, void 0, "literal"]
3684
3206
  ];
3685
3207
  for (const [target, positive, negative, kind] of checks) {
3686
3208
  if (!target) continue;
@@ -3688,7 +3210,7 @@ function scopeExclusion(graph, iri2, scope) {
3688
3210
  const denied = graph.values(iri2, negative).some((v) => v.kind === kind && v.value === target);
3689
3211
  if (denied) return { node: iri2, rule: negative, value: target };
3690
3212
  }
3691
- const subject = positive === DCTERMS_LANGUAGE2 ? documentOf(iri2) : iri2;
3213
+ const subject = positive === DCTERMS_LANGUAGE ? documentOf(iri2) : iri2;
3692
3214
  const claimed = graph.values(subject, positive);
3693
3215
  if (claimed.length > 0) {
3694
3216
  const matches = claimed.some(
@@ -3807,7 +3329,7 @@ function runTraverse(opts) {
3807
3329
  },
3808
3330
  cwd
3809
3331
  );
3810
- const store = loadGraph(resolve10(cwd, opts.graph ?? config.out));
3332
+ const store = loadGraph(resolve9(cwd, opts.graph ?? config.out));
3811
3333
  const quads = storeToQuads(store);
3812
3334
  const graph = GraphIndex.fromQuads(quads, Object.fromEntries(PREFIXES));
3813
3335
  const node = expandTerm(opts.node);
@@ -3897,19 +3419,6 @@ export {
3897
3419
  emitRdfXml,
3898
3420
  writeZip,
3899
3421
  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
3422
  EXPORT_TARGETS,
3914
3423
  runExport,
3915
3424
  runQuery,
@@ -3926,27 +3435,9 @@ export {
3926
3435
  makeProvider,
3927
3436
  runFill,
3928
3437
  renderFill,
3929
- VectorIndexError,
3930
- normalize,
3931
- encodeVectorIndex,
3932
- decodeVectorIndex,
3933
- EmbedderUnavailableError,
3934
- createLocalEmbedder,
3935
- createMockEmbedder,
3936
3438
  embedText,
3937
3439
  runEmbed,
3938
3440
  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
3441
  runStats,
3951
3442
  renderStats,
3952
3443
  resolveVariant,
@@ -3958,4 +3449,4 @@ export {
3958
3449
  runTraverse,
3959
3450
  renderTraverse
3960
3451
  };
3961
- //# sourceMappingURL=chunk-2JV7C4YV.js.map
3452
+ //# sourceMappingURL=chunk-DDILEF5F.js.map