@iowarp/clio-coder 0.4.8 → 0.4.9

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 (513) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +7 -6
  3. package/dist/{acp-JWPQZM2D.js → acp-ZWZCPS2A.js} +13 -13
  4. package/dist/{agents-4GCDIRVX.js → agents-LYSOWZUE.js} +53 -52
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-SYAKTDQE.js → auth-WBQGKWQ5.js} +27 -27
  7. package/dist/{background-OW67WMYR.js → background-BUMOBAQN.js} +4 -4
  8. package/dist/{builtins-TKS63VNV.js → builtins-ODRJOEDI.js} +7 -7
  9. package/dist/{chunk-JLBVCROW.js → chunk-2DRRZ57Q.js} +33 -24
  10. package/dist/chunk-2JBZYUPY.js +105 -0
  11. package/dist/chunk-2KUBKL63.js +336 -0
  12. package/dist/{chunk-K45LHSCI.js → chunk-2VBWFXYE.js} +7 -3
  13. package/dist/{chunk-XTLJDC2X.js → chunk-2X2R3UTH.js} +23 -18
  14. package/dist/{chunk-5HULHKT2.js → chunk-37LW32LZ.js} +5 -5
  15. package/dist/{chunk-W7KNI3F5.js → chunk-3AND2EFU.js} +5 -5
  16. package/dist/{chunk-36XSBPXL.js → chunk-3RDHGNU7.js} +95 -27
  17. package/dist/{chunk-BIY22UUK.js → chunk-3SUDBGEI.js} +11 -10
  18. package/dist/{chunk-3IQOZRQ6.js → chunk-3TQN67I2.js} +7 -7
  19. package/dist/{chunk-KWJXCL2X.js → chunk-3U4BL2EV.js} +3 -3
  20. package/dist/{chunk-RBES4TCZ.js → chunk-3UQADNG2.js} +3 -3
  21. package/dist/{chunk-V4ZKOKRB.js → chunk-434LRQ6Z.js} +61 -53
  22. package/dist/{chunk-MWMW53WW.js → chunk-4ATBOFRP.js} +649 -193
  23. package/dist/{chunk-QGIC5SX5.js → chunk-4DNL43EZ.js} +3 -3
  24. package/dist/{chunk-GVU4CXPJ.js → chunk-4HFJKRGZ.js} +113 -54
  25. package/dist/{chunk-5A6XR3RJ.js → chunk-555E2WVV.js} +16 -2
  26. package/dist/{chunk-LZDLYSUN.js → chunk-5RTEFNBP.js} +3 -3
  27. package/dist/{chunk-HMGAKB7E.js → chunk-64XJW7NT.js} +15 -15
  28. package/dist/{chunk-EXKUF426.js → chunk-7FBCRWVT.js} +280 -280
  29. package/dist/{chunk-K3W6BF5C.js → chunk-7JSOSVLP.js} +2 -2
  30. package/dist/{chunk-WFTSK7N3.js → chunk-7N3OLLSL.js} +4 -4
  31. package/dist/{chunk-JRA6OBHY.js → chunk-A6DULIMH.js} +17 -5
  32. package/dist/{chunk-R5NRX5N4.js → chunk-AB3QXXBC.js} +2 -2
  33. package/dist/chunk-ACCECAH4.js +49 -0
  34. package/dist/{chunk-GJBJHJNY.js → chunk-AM6CEP2K.js} +5 -5
  35. package/dist/{chunk-MUXNDNMZ.js → chunk-AY4XE6GN.js} +19 -13
  36. package/dist/{chunk-MATCRH3M.js → chunk-BBA2TAWV.js} +9 -9
  37. package/dist/{chunk-PMUUAABD.js → chunk-BMNVVKEW.js} +9 -9
  38. package/dist/{chunk-TOPICHGR.js → chunk-CTGZCEI6.js} +2 -2
  39. package/dist/{chunk-L75H5B3B.js → chunk-DIAJYJYG.js} +2 -2
  40. package/dist/{chunk-JIMWEYWO.js → chunk-EHZRAB4U.js} +2 -2
  41. package/dist/{chunk-YXANQGCX.js → chunk-EM2DIQAJ.js} +6 -6
  42. package/dist/{chunk-JCVIL6TY.js → chunk-ETF4ROKH.js} +4 -4
  43. package/dist/{chunk-5W4FRA5Q.js → chunk-F2YM3JY4.js} +483 -253
  44. package/dist/{chunk-YGIV24MF.js → chunk-FEBQWHJT.js} +21 -16
  45. package/dist/{chunk-PGEFOTJB.js → chunk-FPS4R2RN.js} +5 -4
  46. package/dist/{chunk-XC5POIL4.js → chunk-G5R53COU.js} +1 -1
  47. package/dist/chunk-GHXGMFQ5.js +40 -0
  48. package/dist/{chunk-QLKCCC2J.js → chunk-GJDRMBDI.js} +14 -14
  49. package/dist/{chunk-2ZWLMA3T.js → chunk-GREYLTZ5.js} +7 -7
  50. package/dist/{chunk-YXFKLMX2.js → chunk-H2WSCZZT.js} +22 -6
  51. package/dist/{chunk-FNVPQQYT.js → chunk-H6IUCY6Q.js} +2 -2
  52. package/dist/{chunk-5C2OQHLR.js → chunk-H72DCH3E.js} +92 -203
  53. package/dist/{chunk-JYKUMHCQ.js → chunk-H7TXJR23.js} +21 -21
  54. package/dist/chunk-HBJQQTKM.js +40 -0
  55. package/dist/chunk-HCVKSGNZ.js +990 -0
  56. package/dist/{chunk-ARRUMZ7X.js → chunk-I47EJPYL.js} +3 -3
  57. package/dist/{chunk-YKIIWURD.js → chunk-IALGWTLO.js} +3 -3
  58. package/dist/{chunk-FB3KTE2J.js → chunk-IALZJE37.js} +4 -4
  59. package/dist/{chunk-RAUOKUZV.js → chunk-IC7JXLOM.js} +15 -10
  60. package/dist/{chunk-LLCGGVSW.js → chunk-IOSVTTCD.js} +7 -7
  61. package/dist/{chunk-DBREN25X.js → chunk-IPTPMLA6.js} +8 -3
  62. package/dist/{chunk-V552WL53.js → chunk-IZZI3SKQ.js} +3 -67
  63. package/dist/{chunk-UDAK6GR2.js → chunk-JIYWMQZ5.js} +2 -2
  64. package/dist/{chunk-O3HSB2YL.js → chunk-JKU34ACI.js} +2 -2
  65. package/dist/{chunk-6X7VOCDY.js → chunk-JTR3VSV3.js} +3 -3
  66. package/dist/{chunk-O2QJDRWP.js → chunk-JU6MAMRJ.js} +4 -4
  67. package/dist/chunk-JURMQWVJ.js +15 -0
  68. package/dist/{chunk-STC6CWUZ.js → chunk-K3XN4LZ4.js} +5 -5
  69. package/dist/{chunk-ATB3Q7YO.js → chunk-KIVFXGBM.js} +27 -2
  70. package/dist/{chunk-WJWNZXRM.js → chunk-KOAHT3N2.js} +2 -2
  71. package/dist/chunk-KP5BPXR3.js +73 -0
  72. package/dist/{chunk-K53E2PTW.js → chunk-KRJQG47M.js} +3 -3
  73. package/dist/{chunk-IILXVWJY.js → chunk-KW6OJ7IY.js} +19 -21
  74. package/dist/{chunk-A46ILRS2.js → chunk-L3NBWLSN.js} +4 -4
  75. package/dist/{chunk-2U2U5POA.js → chunk-L4RUTLRL.js} +5 -5
  76. package/dist/{chunk-QYSAZMSH.js → chunk-LGTVWPRN.js} +2 -2
  77. package/dist/{chunk-KGP3UQGJ.js → chunk-LSQW6KZO.js} +5 -5
  78. package/dist/{chunk-P4JS3MH5.js → chunk-LWJYOHT3.js} +8 -8
  79. package/dist/{chunk-U3TPUXOQ.js → chunk-M6YLF66Z.js} +74 -40
  80. package/dist/{chunk-5D2FNBMG.js → chunk-MJ4QEJVT.js} +2 -2
  81. package/dist/{chunk-RM3XJX7P.js → chunk-MO65K5CH.js} +5 -5
  82. package/dist/{chunk-H5DPSUBB.js → chunk-MTOZEQHL.js} +3 -3
  83. package/dist/{chunk-6R5MBXBJ.js → chunk-NBPA5QM6.js} +2 -2
  84. package/dist/{chunk-H77XBUBM.js → chunk-NMBEW4PP.js} +3 -3
  85. package/dist/{chunk-QWUSS2QP.js → chunk-NPLLK3HO.js} +67 -87
  86. package/dist/{chunk-K5UJD6MM.js → chunk-NSYGPIVW.js} +5 -5
  87. package/dist/{context-EZZPT2ZV.js → chunk-NXTV5OI5.js} +30 -84
  88. package/dist/{chunk-F7Y6QTPL.js → chunk-NYDG4QP4.js} +4 -4
  89. package/dist/{chunk-PFZJMXIY.js → chunk-OA2GDACN.js} +2 -2
  90. package/dist/{chunk-Q32C6AWI.js → chunk-PJI57EC6.js} +4 -4
  91. package/dist/{chunk-EFXMQYI6.js → chunk-PJJBUGMR.js} +52 -52
  92. package/dist/{chunk-ICYIUNO4.js → chunk-PNJE7JPS.js} +18 -3
  93. package/dist/chunk-PP23PZEV.js +1347 -0
  94. package/dist/chunk-PPIFDSBK.js +409 -0
  95. package/dist/{chunk-RFULVPBX.js → chunk-PTFISXQO.js} +6 -5
  96. package/dist/{chunk-JV25I7ID.js → chunk-QB33Z5S7.js} +5 -5
  97. package/dist/{chunk-D55WO5J7.js → chunk-QGNII5VC.js} +8 -8
  98. package/dist/{chunk-RKFNU5UA.js → chunk-QI72X67S.js} +104 -27
  99. package/dist/chunk-QKHZZFKH.js +98 -0
  100. package/dist/{chunk-5UG6EZI3.js → chunk-RDKX2PME.js} +4 -4
  101. package/dist/{chunk-B5ZTR6KZ.js → chunk-RL7LJYN4.js} +1 -1
  102. package/dist/{chunk-BE4I3UJ2.js → chunk-RQROVOV3.js} +15 -13
  103. package/dist/{chunk-TEHVMHOK.js → chunk-S34A7BSP.js} +3 -3
  104. package/dist/{chunk-4P6M2TTE.js → chunk-SJETVJHX.js} +2 -2
  105. package/dist/{chunk-OPC4DQD6.js → chunk-SJMNU4ZS.js} +2 -2
  106. package/dist/{chunk-2JSCCEKO.js → chunk-SLLG2G44.js} +2 -2
  107. package/dist/{chunk-EPYATQMG.js → chunk-SQAZN2V3.js} +42 -16
  108. package/dist/{chunk-E2NSPYLC.js → chunk-SQBT46OF.js} +5 -5
  109. package/dist/{chunk-WTZVSMQZ.js → chunk-TCUOGIL2.js} +5 -5
  110. package/dist/{chunk-RRZFRIJP.js → chunk-TIWPKCVO.js} +5 -5
  111. package/dist/{chunk-QLAGXTTV.js → chunk-UIU7MGZ3.js} +5 -1
  112. package/dist/{chunk-WLHX3MM5.js → chunk-V4XJWDHR.js} +21 -11
  113. package/dist/{chunk-PJ5H5B7A.js → chunk-VPWGADBG.js} +18 -18
  114. package/dist/{chunk-GEYX2NZ5.js → chunk-VWFCAK34.js} +6 -6
  115. package/dist/{chunk-44U2OUSM.js → chunk-W72LLZAV.js} +2 -2
  116. package/dist/{chunk-ORRNGQDV.js → chunk-WGRDABW7.js} +4 -4
  117. package/dist/{chunk-RNLAPBJL.js → chunk-XFLSL7BT.js} +2 -2
  118. package/dist/{chunk-2SXGG2IV.js → chunk-XJZEDCNT.js} +6 -6
  119. package/dist/{chunk-UB33F3YW.js → chunk-XLLEL3K7.js} +5 -5
  120. package/dist/{chunk-2GPGAE5W.js → chunk-XNIZBOFV.js} +15 -1
  121. package/dist/{chunk-WS4FOGLY.js → chunk-XNZWWDC3.js} +2 -2
  122. package/dist/{chunk-P7GTE4FX.js → chunk-XRECG65F.js} +2 -2
  123. package/dist/chunk-XTQ7MEJG.js +457 -0
  124. package/dist/{chunk-GEVQNST4.js → chunk-XZWSP67B.js} +2 -2
  125. package/dist/{chunk-OEMFEL26.js → chunk-Y3M2T5RO.js} +3 -3
  126. package/dist/{chunk-HP7KJ5O2.js → chunk-YPQOPFP7.js} +6 -6
  127. package/dist/{chunk-MEHBW2SP.js → chunk-YQYINEJI.js} +6 -6
  128. package/dist/{chunk-RXI7K4J5.js → chunk-ZF6CV4WZ.js} +1338 -217
  129. package/dist/{chunk-3XE4NRLF.js → chunk-ZKJU55MX.js} +4 -2
  130. package/dist/{chunk-526ABLHD.js → chunk-ZKQ63XZ5.js} +12 -35
  131. package/dist/{chunk-PAQVETLA.js → chunk-ZKVMDNAN.js} +5 -5
  132. package/dist/cli/index.js +47 -37
  133. package/dist/{clio-BPBAPZGA.js → clio-JUDCLNHC.js} +7 -7
  134. package/dist/clio-context-tools-FT672MNZ.js +136 -0
  135. package/dist/{code-nav-KBQ3RIFK.js → code-nav-7OVCTP2Z.js} +28 -26
  136. package/dist/{components-6DKAXWFN.js → components-OZXZMDWU.js} +5 -5
  137. package/dist/{config-FWL5JK2H.js → config-P3ZQVF63.js} +80 -75
  138. package/dist/{config-graph-ACDB26L2.js → config-graph-GKAOBAS5.js} +74 -70
  139. package/dist/{configure-ZIZ4YV62.js → configure-25LRYZJG.js} +45 -44
  140. package/dist/context-72VQV7JX.js +73 -0
  141. package/dist/{context-VIL5MFRH.js → context-COWRWXP3.js} +74 -70
  142. package/dist/{context-Z3GOBHDK.js → context-WB3D474G.js} +12 -12
  143. package/dist/{context-clear-ZPB3L7MN.js → context-clear-Y4LYMWC6.js} +70 -66
  144. package/dist/{context-working-set-2GLBVF7L.js → context-working-set-TMZYVC7A.js} +15 -14
  145. package/dist/data-tool-KMBWKKWR.js +2987 -0
  146. package/dist/{detail-VSBWYSGZ.js → detail-4GLROGY5.js} +71 -67
  147. package/dist/{dispatch-runner-EEEHGKX4.js → dispatch-runner-TR3LBP5P.js} +92 -88
  148. package/dist/{docs-VMMWFVHL.js → docs-ZK777W2G.js} +3 -3
  149. package/dist/{doctor-V5L4IQGP.js → doctor-WC4OV2OB.js} +61 -59
  150. package/dist/{eval-VJYJ2BV3.js → eval-FUNWJNUJ.js} +45 -45
  151. package/dist/{evals-J2H5V6DD.js → evals-L73EBHSU.js} +4 -4
  152. package/dist/{evidence-Y35YSXH6.js → evidence-AVJ2RTHX.js} +80 -76
  153. package/dist/{evidence-POWAEBEW.js → evidence-P4NZOAK7.js} +70 -66
  154. package/dist/{evidence-XCCCLS36.js → evidence-Y55WKYHH.js} +79 -75
  155. package/dist/{evolve-GPG72VB6.js → evolve-MAWKRYWF.js} +75 -71
  156. package/dist/{extensions-YIZ7W3DA.js → extensions-PYQH3H47.js} +8 -8
  157. package/dist/{fleet-2MDCQ2VK.js → fleet-M4H2QBWT.js} +84 -80
  158. package/dist/{fleet-W2AF3TOJ.js → fleet-ULBXXRDR.js} +123 -119
  159. package/dist/{fleet-commands-WS56G26H.js → fleet-commands-7NNC4JZU.js} +17 -16
  160. package/dist/{fleet-decisions-MYGZ3JW7.js → fleet-decisions-EHEY7KPU.js} +9 -9
  161. package/dist/{fleet-graph-ELTBXBAV.js → fleet-graph-LQ2AHZT3.js} +15 -15
  162. package/dist/{fleet-inspect-45IK73L4.js → fleet-inspect-5IGBIIYZ.js} +76 -72
  163. package/dist/{fleet-preflight-T5NCZKHE.js → fleet-preflight-UTTNOKMG.js} +7 -2
  164. package/dist/{fleet-validate-VG4JBLA4.js → fleet-validate-YGILJKA6.js} +20 -20
  165. package/dist/{fleet-verify-VTSABAYO.js → fleet-verify-AUWTCDKR.js} +70 -66
  166. package/dist/{fleet-view-XC7WMKSS.js → fleet-view-TGUFCY5A.js} +73 -69
  167. package/dist/{init-VTJNNTIP.js → init-GFN2Z7NF.js} +94 -90
  168. package/dist/{interop-IU6BNA7C.js → interop-EF5JUWWK.js} +21 -21
  169. package/dist/{inventory-QPQUTUUZ.js → inventory-I6VJDDYF.js} +73 -69
  170. package/dist/{library-Q3NWL5CM.js → library-3J5IO2OJ.js} +13 -13
  171. package/dist/{library-LF7DLU6S.js → library-IJTOFTDS.js} +16 -15
  172. package/dist/{library-HUTAS57T.js → library-Z3KZSHPE.js} +27 -27
  173. package/dist/{library-import-PPW4YUC5.js → library-import-IMKFLDHX.js} +21 -21
  174. package/dist/{library-inventory-DIRVFRZU.js → library-inventory-CQP5D6GU.js} +13 -13
  175. package/dist/{library-validation-XHUZ3INE.js → library-validation-33UTX7AI.js} +11 -11
  176. package/dist/mcp-5FWZKUS6.js +26 -0
  177. package/dist/{memory-Z2LSKNV6.js → memory-36CWN67Y.js} +75 -71
  178. package/dist/{models-3FTREYPY.js → models-KQ6PQJJQ.js} +49 -49
  179. package/dist/{monitor-HF2MQRB3.js → monitor-XX6ZD6KC.js} +83 -79
  180. package/dist/{orchestrator-UYPA6W4S.js → orchestrator-SK6HDAOP.js} +1337 -772
  181. package/dist/{panes-736XB73K.js → panes-L27QRXUH.js} +12 -12
  182. package/dist/{panes-C4P3PIWT.js → panes-LXZIWNOZ.js} +4 -4
  183. package/dist/{paths-JQ4QYIYB.js → paths-7DJDU4P4.js} +5 -5
  184. package/dist/{preload-XSLRYH3Y.js → preload-K2TDYI4Y.js} +70 -66
  185. package/dist/{reset-V7QSPZYE.js → reset-73VU3XNT.js} +10 -10
  186. package/dist/{resources-OJGDFHYT.js → resources-3BYJYVYL.js} +16 -16
  187. package/dist/{run-T354SMDW.js → run-AR7PY6GW.js} +132 -128
  188. package/dist/run-script-H2KUDIII.js +41 -0
  189. package/dist/{settings-MWZXO6RV.js → settings-3VMBMWID.js} +4 -4
  190. package/dist/{share-Y5ZKJPND.js → share-HVWH4FPF.js} +21 -21
  191. package/dist/{skills-4GLZC4LN.js → skills-5TYEY75Q.js} +22 -22
  192. package/dist/{skills-eval-L5T7GODM.js → skills-eval-CK3MD63Z.js} +96 -87
  193. package/dist/{skills-inventory-LASKXEZI.js → skills-inventory-KSFYG3I6.js} +16 -16
  194. package/dist/{slash-commands-OIMRU36D.js → slash-commands-DMVHML7P.js} +47 -44
  195. package/dist/{steer-KMAM2Y2C.js → steer-XCD6V3EW.js} +4 -4
  196. package/dist/{support-AWC3XKPX.js → support-JAZL6VBR.js} +7 -7
  197. package/dist/{system-CD75NYBU.js → system-3TDQRMZZ.js} +26 -26
  198. package/dist/{targets-VSZX2J2S.js → targets-EZKHAYRN.js} +66 -65
  199. package/dist/{tasks-AN567LYH.js → tasks-SNEFHWYW.js} +12 -12
  200. package/dist/{terminal-lease-6KTWNHVY.js → terminal-lease-BNLFA4OF.js} +8 -7
  201. package/dist/{tools-AGRK3V5J.js → tools-7IM6BDCE.js} +12 -12
  202. package/dist/{trace-P4MQZD33.js → trace-C3MBGD4S.js} +8 -8
  203. package/dist/{tui-primitives-A6MUW32V.js → tui-primitives-36BY2774.js} +2 -2
  204. package/dist/{uninstall-BRUTEPIC.js → uninstall-WWLOSZ42.js} +10 -10
  205. package/dist/{upgrade-SBCGHQGJ.js → upgrade-ETK2JNSJ.js} +37 -47
  206. package/dist/{usage-BIDVTO6V.js → usage-S2HOB7BZ.js} +93 -89
  207. package/dist/{verifiers-LTGCAMXS.js → verifiers-QDTEVH7R.js} +24 -22
  208. package/dist/{verify-OZU4LLNP.js → verify-SUKUK6XM.js} +15 -14
  209. package/dist/web/client/assets/{abnfDiagram-VCTEODGH-C5wfoHm3.js → abnfDiagram-VCTEODGH-BznNOe_I.js} +1 -1
  210. package/dist/web/client/assets/{arc-oH91sfLl.js → arc-Pry-7G1_.js} +1 -1
  211. package/dist/web/client/assets/architecture-7GRP2DOG-BQO9DL_y.js +1 -0
  212. package/dist/web/client/assets/architectureDiagram-5GKGNRK7-CKPbdvyx.js +36 -0
  213. package/dist/web/client/assets/{blockDiagram-I7D4REHJ-wi0_CYHL.js → blockDiagram-I7D4REHJ-prINbpbM.js} +1 -1
  214. package/dist/web/client/assets/{c4Diagram-7LVT6UL2-DV7NYc2W.js → c4Diagram-7LVT6UL2-DkVRKtyU.js} +1 -1
  215. package/dist/web/client/assets/channel-c4EeUUqA.js +1 -0
  216. package/dist/web/client/assets/{chunk-4HAMMTFA-Bhsegh3F.js → chunk-4HAMMTFA-CV0Z1ApC.js} +1 -1
  217. package/dist/web/client/assets/{chunk-75Z2AOVW-RQCckcQG.js → chunk-75Z2AOVW-D34Nohxq.js} +1 -1
  218. package/dist/web/client/assets/chunk-DU6HZSFF-Das_cS-O.js +125 -0
  219. package/dist/web/client/assets/{chunk-F27PBJKO-BcNqv2x1.js → chunk-F27PBJKO-CmRojxkG.js} +1 -1
  220. package/dist/web/client/assets/{chunk-GMAD6QVW-Bq8JWiZT.js → chunk-GMAD6QVW-ClYZmc73.js} +1 -1
  221. package/dist/web/client/assets/{chunk-GVQU2GXP-DbchrAeJ.js → chunk-GVQU2GXP-B2BiHFt-.js} +1 -1
  222. package/dist/web/client/assets/{chunk-IMKFNOWR-DrpvjocK.js → chunk-IMKFNOWR-BGApZ8nP.js} +1 -1
  223. package/dist/web/client/assets/{chunk-L3NEJ4N5-DHLmTnCe.js → chunk-L3NEJ4N5-DIzCgpkY.js} +1 -1
  224. package/dist/web/client/assets/{chunk-OSK3NFVY-CsRbtN9n.js → chunk-OSK3NFVY-BPT-yePv.js} +1 -1
  225. package/dist/web/client/assets/{chunk-P2QGCYS3-aqPbxoMh.js → chunk-P2QGCYS3-BC1WTc2t.js} +1 -1
  226. package/dist/web/client/assets/{chunk-POPQ4Y6H-Dl5vw0h0.js → chunk-POPQ4Y6H-BSjzBmZU.js} +1 -1
  227. package/dist/web/client/assets/{chunk-PWAF6VOD-D7OBRSdO.js → chunk-PWAF6VOD-BIjLs86Z.js} +1 -1
  228. package/dist/web/client/assets/{chunk-SHT3W25Y-CdKCIwuB.js → chunk-SHT3W25Y-CduDz2cm.js} +1 -1
  229. package/dist/web/client/assets/{chunk-SVP7TREG-B94Nu_1-.js → chunk-SVP7TREG-XUn4pPAP.js} +1 -1
  230. package/dist/web/client/assets/{chunk-TICWLB2K-jjRQI_hk.js → chunk-TICWLB2K-DcBy2DKy.js} +1 -1
  231. package/dist/web/client/assets/{chunk-XXDRQBXY-azkbKE37.js → chunk-XXDRQBXY-DqAYeljN.js} +1 -1
  232. package/dist/web/client/assets/classDiagram-ZZMXUADV-C0HR7YKN.js +1 -0
  233. package/dist/web/client/assets/classDiagram-v2-VYDZK3BY-C0HR7YKN.js +1 -0
  234. package/dist/web/client/assets/cose-bilkent-JH36ORCC-CVh1icwb.js +1 -0
  235. package/dist/web/client/assets/{cynefin-OW5HDTMX--i9RDU5f.js → cynefin-OW5HDTMX-BecPbyM6.js} +1 -1
  236. package/dist/web/client/assets/{cynefinDiagram-5FMLGOSQ-cRcvyBdp.js → cynefinDiagram-5FMLGOSQ-BzMzFwyT.js} +1 -1
  237. package/dist/web/client/assets/{dagre-GXQ25YYZ-C9mZmAPe.js → dagre-GXQ25YYZ-CAWJmRMG.js} +1 -1
  238. package/dist/web/client/assets/{diagram-S7CK7UJ4-Oj_zlLDY.js → diagram-S7CK7UJ4--lp0ALmn.js} +1 -1
  239. package/dist/web/client/assets/{diagram-UQ7AKVKN-BWKbbfcR.js → diagram-UQ7AKVKN-gKUnsjcj.js} +1 -1
  240. package/dist/web/client/assets/{diagram-VSXAHHWV-C3N3f7DO.js → diagram-VSXAHHWV-BoN5D1Gh.js} +1 -1
  241. package/dist/web/client/assets/{diagram-VX7I27RA-CtDKDAWe.js → diagram-VX7I27RA-BMtuswQ6.js} +1 -1
  242. package/dist/web/client/assets/{diagram-Z3DM3KII-B8bkBRCz.js → diagram-Z3DM3KII-DtU8LXrA.js} +1 -1
  243. package/dist/web/client/assets/dist-v5Q1xZ2K.js +1 -0
  244. package/dist/web/client/assets/{ebnfDiagram-PWID7BFC-CskW-Whx.js → ebnfDiagram-PWID7BFC-DDcnv9BH.js} +1 -1
  245. package/dist/web/client/assets/{erDiagram-RLTQ6QDP-C-83cgCW.js → erDiagram-RLTQ6QDP-DvRb4iDL.js} +1 -1
  246. package/dist/web/client/assets/eventmodeling-NTZA5JFV-01h4SbTc.js +1 -0
  247. package/dist/web/client/assets/flowDiagram-HODETNUW-Bll_YZ6T.js +1 -0
  248. package/dist/web/client/assets/framework-YGoR34M4.js +9 -0
  249. package/dist/web/client/assets/ganttDiagram-EL5Y4UJY-DdFZ9hvH.js +292 -0
  250. package/dist/web/client/assets/{gitGraph-4MIJSDKK-B40cQtBE.js → gitGraph-4MIJSDKK-DjD_AAMj.js} +1 -1
  251. package/dist/web/client/assets/{gitGraphDiagram-WWUBYQGX-CH-7P7Di.js → gitGraphDiagram-WWUBYQGX-CDyN9n2u.js} +1 -1
  252. package/dist/web/client/assets/index-CVcy0FAn.js +75 -0
  253. package/dist/web/client/assets/{info-A6RAGUB7-eCCE9D_Q.js → info-A6RAGUB7-C_XK8Hq0.js} +1 -1
  254. package/dist/web/client/assets/{infoDiagram-27XIBGKW-DRnvqMjM.js → infoDiagram-27XIBGKW-DIe4tJ1i.js} +1 -1
  255. package/dist/web/client/assets/{ishikawaDiagram-5VMMS53U-DBA_55wp.js → ishikawaDiagram-5VMMS53U-CVl5unKI.js} +1 -1
  256. package/dist/web/client/assets/{journeyDiagram-3NMN7TZE-C-ewpNl7.js → journeyDiagram-3NMN7TZE-CPEB1Vt8.js} +1 -1
  257. package/dist/web/client/assets/{kanban-definition-UXKFOSKX-B1taGsu0.js → kanban-definition-UXKFOSKX-j-9WXjYQ.js} +1 -1
  258. package/dist/web/client/assets/{line-lZ0kyxDI.js → line-BIDAcEtX.js} +1 -1
  259. package/dist/web/client/assets/{linear-DIc8FgZj.js → linear-SzVmGSw4.js} +1 -1
  260. package/dist/web/client/assets/{mermaid-parser.core-SXolxpXw.js → mermaid-parser.core-Ux4vssh0.js} +3 -3
  261. package/dist/web/client/assets/{mermaid.core-DXgQtkNg.js → mermaid.core-CDuwVKeh.js} +4 -4
  262. package/dist/web/client/assets/{mindmap-definition-YA3MSWOX-DXJas9K7.js → mindmap-definition-YA3MSWOX-5MlQEeQ6.js} +1 -1
  263. package/dist/web/client/assets/{packet-AYTQ26CC-OqVl_07o.js → packet-AYTQ26CC-DRO66c9Q.js} +1 -1
  264. package/dist/web/client/assets/{pegDiagram-XKGWAZYB-CPE_zdfN.js → pegDiagram-XKGWAZYB-BakZnSjM.js} +1 -1
  265. package/dist/web/client/assets/{pie-WAS4IAKB-DAxnMBLe.js → pie-WAS4IAKB-BEzcmhz8.js} +1 -1
  266. package/dist/web/client/assets/{pieDiagram-E7YTZNPT-Uwjpufxv.js → pieDiagram-E7YTZNPT-DpmqgoPt.js} +1 -1
  267. package/dist/web/client/assets/prism-core-BtsZdCS6.js +1 -0
  268. package/dist/web/client/assets/{quadrantDiagram-AXDQQJYC-Bjs4yjgs.js → quadrantDiagram-AXDQQJYC-DuMPwwV2.js} +1 -1
  269. package/dist/web/client/assets/{radar-RG4KPBEZ-CuCKjHM3.js → radar-RG4KPBEZ-Djm3oXl5.js} +1 -1
  270. package/dist/web/client/assets/{railroad-74A4TZTK-CnQK-Cc1.js → railroad-74A4TZTK-Cpvkwsao.js} +1 -1
  271. package/dist/web/client/assets/railroad-abnf-HS5TGJTU-CpRQ3ruG.js +1 -0
  272. package/dist/web/client/assets/railroad-ebnf-LZEXJU2U-1Rm5a5eJ.js +1 -0
  273. package/dist/web/client/assets/railroad-peg-WCYAUIDC-C8344cTh.js +1 -0
  274. package/dist/web/client/assets/{railroadDiagram-O6MQD6OU-BDDIPNLL.js → railroadDiagram-O6MQD6OU-CgMS4B6R.js} +1 -1
  275. package/dist/web/client/assets/{requirementDiagram-BXWQKSXE-B5Ydyxc1.js → requirementDiagram-BXWQKSXE-CX5eoNdN.js} +1 -1
  276. package/dist/web/client/assets/rolldown-runtime-hePW80VL.js +1 -0
  277. package/dist/web/client/assets/{sankeyDiagram-P5KCCOFB-Bw2Qym75.js → sankeyDiagram-P5KCCOFB-CODBX2N3.js} +1 -1
  278. package/dist/web/client/assets/{sequenceDiagram-WJ2MYXX4-BZf36nO0.js → sequenceDiagram-WJ2MYXX4-eUhhF8OR.js} +1 -1
  279. package/dist/web/client/assets/src-B6xuSHsQ.js +1 -0
  280. package/dist/web/client/assets/{stateDiagram-D77RDMKH-DnH6JRkp.js → stateDiagram-D77RDMKH-vR6l0NC2.js} +1 -1
  281. package/dist/web/client/assets/stateDiagram-v2-MP3YSRHH-BYUm8jLY.js +1 -0
  282. package/dist/web/client/assets/{swimlanes-42K2YHIH-D9PZmtZg.js → swimlanes-42K2YHIH-v9Tw_zX7.js} +1 -1
  283. package/dist/web/client/assets/swimlanesDiagram-VR7AAH4N-B2jdCvhD.js +8 -0
  284. package/dist/web/client/assets/{timeline-definition-24CTP7MA-CrkZZBo7.js → timeline-definition-24CTP7MA-DxianbhU.js} +1 -1
  285. package/dist/web/client/assets/{treeView-Q6P3EWNA-C9zDShoC.js → treeView-Q6P3EWNA-DLxnZb9R.js} +1 -1
  286. package/dist/web/client/assets/{treemap-WGGIJYW6-B-plj9yK.js → treemap-WGGIJYW6-I7UnZ6xJ.js} +1 -1
  287. package/dist/web/client/assets/{vennDiagram-4TSXK5OY-VeOdaphN.js → vennDiagram-4TSXK5OY-Ar7M8Mrj.js} +1 -1
  288. package/dist/web/client/assets/{wardley-WFR3VGLG-Wl1P_m6L.js → wardley-WFR3VGLG-B8ZSHeDu.js} +1 -1
  289. package/dist/web/client/assets/{wardleyDiagram-VM6X3IG4-B85-R9t2.js → wardleyDiagram-VM6X3IG4-Qks2FmIn.js} +1 -1
  290. package/dist/web/client/assets/{xychartDiagram-S5SC5T6Z-BrD8OW8m.js → xychartDiagram-S5SC5T6Z-BBBjRtiE.js} +1 -1
  291. package/dist/web/client/index.html +3 -1
  292. package/dist/web/reads-worker.js +17 -17
  293. package/dist/web/server.js +25 -25
  294. package/dist/{web-EU6ATJWA.js → web-EMGUGGEI.js} +3 -3
  295. package/dist/{web-fetch-U22Q7HV4.js → web-fetch-6BRRVQON.js} +22 -5
  296. package/dist/{wiki-generate-LZRLFG6I.js → wiki-generate-XHSJTDOP.js} +104 -100
  297. package/dist/{with-panes-X5CZOGML.js → with-panes-Z6BTEHMZ.js} +11 -11
  298. package/dist/worker/entry.js +101 -87
  299. package/docs/README.md +1 -1
  300. package/docs/architecture/artifact-placement.md +12 -1
  301. package/docs/architecture/library.md +1 -1
  302. package/docs/architecture/prompt-envelope-and-tools.md +37 -27
  303. package/docs/architecture/safety-model.md +19 -6
  304. package/docs/architecture/session-lifecycle.md +10 -4
  305. package/docs/architecture/tui-design.md +37 -14
  306. package/docs/guide/commands-and-modes.md +39 -19
  307. package/docs/guide/configuration-and-targets.md +7 -5
  308. package/docs/guide/configuration-reference.md +116 -13
  309. package/docs/guide/environment-variables.md +2 -5
  310. package/docs/guide/fleet-dispatch.md +3 -2
  311. package/docs/guide/glossary.md +1 -1
  312. package/docs/guide/installation-and-lifecycle.md +2 -2
  313. package/docs/guide/resource-library.md +1 -1
  314. package/docs/guide/tool-usage.md +166 -64
  315. package/docs/guide/troubleshooting.md +1 -1
  316. package/docs/process/documentation-guide.md +1 -0
  317. package/docs/process/fleet-demo-runbook.md +6 -6
  318. package/docs/process/scientific-validation.md +22 -2
  319. package/docs/process/tool-audit-v0.4.9.md +357 -0
  320. package/package.json +1 -1
  321. package/src/cli/argv.ts +31 -0
  322. package/src/cli/clio.ts +17 -13
  323. package/src/cli/config.ts +4 -0
  324. package/src/cli/doctor-panes.ts +1 -1
  325. package/src/cli/doctor.ts +3 -1
  326. package/src/cli/index.ts +14 -1
  327. package/src/cli/mcp.ts +106 -0
  328. package/src/cli/skills-eval.ts +8 -2
  329. package/src/cli/upgrade.ts +34 -38
  330. package/src/cli/verifiers.ts +1 -1
  331. package/src/core/bash-exec.ts +27 -4
  332. package/src/core/config.ts +2 -1
  333. package/src/core/defaults.ts +10 -1
  334. package/src/core/event-bus.ts +1 -1
  335. package/src/core/run-records.ts +438 -0
  336. package/src/core/safe-exec.ts +506 -56
  337. package/src/core/skill-activation.ts +5 -3
  338. package/src/core/termination.ts +3 -3
  339. package/src/core/tool-names.ts +33 -12
  340. package/src/domains/context/clio-md.ts +1 -1
  341. package/src/domains/context/worker/pressure.ts +6 -2
  342. package/src/domains/context/working-set/path-index.ts +7 -2
  343. package/src/domains/dispatch/code-step.ts +7 -2
  344. package/src/domains/dispatch/contract.ts +7 -0
  345. package/src/domains/dispatch/execution-role.ts +3 -3
  346. package/src/domains/dispatch/execution-scheduler.ts +204 -79
  347. package/src/domains/dispatch/extension.ts +131 -14
  348. package/src/domains/dispatch/fleet-preflight.ts +18 -3
  349. package/src/domains/dispatch/fleet-run.ts +1 -0
  350. package/src/domains/dispatch/gate-decisions.ts +3 -3
  351. package/src/domains/dispatch/heartbeat.ts +1 -1
  352. package/src/domains/dispatch/host-verification.ts +16 -14
  353. package/src/domains/dispatch/receipt-findings.ts +3 -4
  354. package/src/domains/dispatch/reservation-store.ts +7 -9
  355. package/src/domains/dispatch/route-facts.ts +1 -1
  356. package/src/domains/dispatch/state.ts +3 -3
  357. package/src/domains/dispatch/types.ts +1 -1
  358. package/src/domains/evidence/build.ts +35 -7
  359. package/src/domains/gateway/mcp/client.ts +923 -0
  360. package/src/domains/gateway/mcp/config.ts +456 -0
  361. package/src/domains/gateway/mcp/index.ts +88 -0
  362. package/src/domains/gateway/mcp/protocol.ts +273 -0
  363. package/src/domains/gateway/mcp/trust.ts +263 -0
  364. package/src/domains/lifecycle/doctor.ts +17 -3
  365. package/src/domains/mux/contract.ts +5 -9
  366. package/src/domains/mux/detect.ts +1 -1
  367. package/src/domains/mux/extension.ts +1 -1
  368. package/src/domains/mux/protocol.ts +5 -7
  369. package/src/domains/mux/socket-client.ts +3 -3
  370. package/src/domains/observability/accountability.ts +2 -2
  371. package/src/domains/observability/contract.ts +7 -3
  372. package/src/domains/observability/evidence-index.ts +2 -2
  373. package/src/domains/observability/projection.ts +10 -0
  374. package/src/domains/prompts/compiler.ts +41 -23
  375. package/src/domains/prompts/fragments/identity/docs-routing.md +2 -2
  376. package/src/domains/prompts/fragments/operating/skills.md +12 -10
  377. package/src/domains/providers/index.ts +5 -1
  378. package/src/domains/providers/models/local-models/clio-coder-local-coding-targets.yaml +42 -10
  379. package/src/domains/providers/probe/http.ts +44 -59
  380. package/src/domains/providers/probe/reasoning.ts +2 -0
  381. package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +20 -31
  382. package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +9 -4
  383. package/src/domains/safety/action-classifier.ts +17 -1
  384. package/src/domains/safety/call-target.ts +14 -0
  385. package/src/domains/safety/contract.ts +1 -1
  386. package/src/domains/safety/finish-contract.ts +9 -3
  387. package/src/domains/safety/loop-detector.ts +1 -1
  388. package/src/domains/safety/policy-engine.ts +7 -0
  389. package/src/domains/safety/protected-artifacts.ts +3 -4
  390. package/src/domains/safety/rejection-feedback.ts +1 -1
  391. package/src/domains/safety/scope.ts +2 -3
  392. package/src/domains/safety/validation-contract.ts +3 -2
  393. package/src/domains/scheduling/budget.ts +5 -3
  394. package/src/domains/scheduling/contract.ts +2 -1
  395. package/src/domains/scheduling/extension.ts +3 -3
  396. package/src/domains/session/compaction/branch-summary.ts +1 -1
  397. package/src/domains/session/compaction/compact.ts +25 -2
  398. package/src/domains/session/compaction/cut-point.ts +1 -1
  399. package/src/domains/session/compaction/tokens.ts +5 -10
  400. package/src/domains/session/contract.ts +1 -1
  401. package/src/domains/session/cwd-fallback.ts +2 -2
  402. package/src/domains/session/handoff.ts +5 -1
  403. package/src/domains/session/history.ts +6 -0
  404. package/src/domains/session/manager.ts +14 -7
  405. package/src/domains/session/retry.ts +2 -1
  406. package/src/domains/session/session-artifacts.ts +12 -7
  407. package/src/domains/session/task-board.ts +2 -3
  408. package/src/engine/apis/openai-completions.ts +8 -0
  409. package/src/engine/loop-guard.ts +7 -0
  410. package/src/engine/provider-error-body.ts +65 -0
  411. package/src/engine/session.ts +118 -29
  412. package/src/engine/worker-runtime.ts +3 -0
  413. package/src/entry/boot-options.ts +2 -0
  414. package/src/entry/orchestrator.ts +18 -2
  415. package/src/interactive/chat-loop-policy.ts +1 -0
  416. package/src/interactive/chat-loop.ts +8 -4
  417. package/src/interactive/chat-panel.ts +23 -9
  418. package/src/interactive/chat-renderer.ts +33 -11
  419. package/src/interactive/clio-editor.ts +13 -5
  420. package/src/interactive/dispatch-board.ts +70 -30
  421. package/src/interactive/editor-submit.ts +30 -25
  422. package/src/interactive/footer/widgets.ts +12 -9
  423. package/src/interactive/interactive-presentation.ts +2 -3
  424. package/src/interactive/mux-bridge.ts +3 -7
  425. package/src/interactive/overlay-general-openers.ts +14 -7
  426. package/src/interactive/overlays/cwd-fallback.ts +1 -1
  427. package/src/interactive/overlays/library-model.ts +114 -35
  428. package/src/interactive/overlays/library.ts +52 -5
  429. package/src/interactive/overlays/list-overlay.ts +4 -1
  430. package/src/interactive/overlays/session-selector.ts +28 -23
  431. package/src/interactive/overlays/settings.ts +20 -1
  432. package/src/interactive/overlays/tree-selector.ts +47 -10
  433. package/src/interactive/panes-runtime.ts +1 -1
  434. package/src/interactive/renderers/compaction-summary.ts +1 -1
  435. package/src/interactive/renderers/provider-error.ts +118 -0
  436. package/src/interactive/renderers/retry-status.ts +59 -23
  437. package/src/interactive/renderers/tool-execution.ts +67 -7
  438. package/src/interactive/renderers/worker-answer.ts +198 -0
  439. package/src/interactive/renderers/worker-entry.ts +57 -163
  440. package/src/interactive/slash-commands.ts +26 -1
  441. package/src/interactive/slash-spec.ts +2 -2
  442. package/src/interactive/theme/labels.ts +66 -30
  443. package/src/interactive/turn-persistence.ts +6 -1
  444. package/src/interactive/turn-recovery.ts +2 -1
  445. package/src/interactive/turn-runtime.ts +22 -5
  446. package/src/interactive/view/artifacts.ts +3 -2
  447. package/src/interactive/view/view-overlay.ts +96 -20
  448. package/src/tools/agent-tools.ts +21 -7
  449. package/src/tools/artifact.ts +16 -8
  450. package/src/tools/bash.ts +90 -11
  451. package/src/tools/bootstrap.ts +42 -4
  452. package/src/tools/builtin-tool-catalog.ts +72 -7
  453. package/src/tools/context/index.ts +27 -31
  454. package/src/tools/context/library.ts +3 -1
  455. package/src/tools/context/surface.ts +12 -26
  456. package/src/tools/core-bootstrap.ts +72 -1
  457. package/src/tools/data/csv.ts +1189 -0
  458. package/src/tools/data/index.ts +308 -0
  459. package/src/tools/data/json.ts +1799 -0
  460. package/src/tools/data/jsonl.ts +561 -0
  461. package/src/tools/data/shared.ts +452 -0
  462. package/src/tools/edit-diff.ts +0 -7
  463. package/src/tools/edit.ts +115 -18
  464. package/src/tools/file-mutation-queue.ts +114 -4
  465. package/src/tools/find.ts +108 -29
  466. package/src/tools/gateway/caps.ts +11 -0
  467. package/src/tools/gateway/clio-context-surface.ts +41 -0
  468. package/src/tools/gateway/clio-context-tools.ts +77 -0
  469. package/src/tools/gateway/data-surface.ts +138 -0
  470. package/src/tools/gateway/data-tool.ts +201 -0
  471. package/src/tools/gateway/index.ts +342 -0
  472. package/src/tools/gateway/mcp-capabilities.ts +469 -0
  473. package/src/tools/grep.ts +88 -33
  474. package/src/tools/harness-extensions.ts +3 -0
  475. package/src/tools/ignore-policy.ts +7 -5
  476. package/src/tools/ls.ts +111 -20
  477. package/src/tools/policy.ts +35 -13
  478. package/src/tools/presentation.ts +6 -0
  479. package/src/tools/read.ts +634 -156
  480. package/src/tools/registry.ts +94 -6
  481. package/src/tools/run-script.ts +997 -0
  482. package/src/tools/spawn-hygiene.ts +157 -5
  483. package/src/tools/surface.ts +163 -0
  484. package/src/tools/verify/authoring.ts +8 -3
  485. package/src/tools/verify/catalog.ts +14 -1
  486. package/src/tools/verify/numeric.ts +367 -59
  487. package/src/tools/verify/perf.ts +171 -9
  488. package/src/tools/verify/scripts.ts +178 -26
  489. package/src/tools/web-fetch-surface.ts +22 -0
  490. package/src/tools/web-fetch.ts +26 -1
  491. package/src/tools/write.ts +45 -19
  492. package/dist/chunk-B6UM3OZC.js +0 -15
  493. package/dist/chunk-GJ24ODAX.js +0 -196
  494. package/dist/chunk-LZCCBLRC.js +0 -57
  495. package/dist/web/client/assets/architecture-7GRP2DOG-EGozsYEO.js +0 -1
  496. package/dist/web/client/assets/architectureDiagram-5GKGNRK7-z9FokbJl.js +0 -36
  497. package/dist/web/client/assets/channel-CiUWbHN9.js +0 -1
  498. package/dist/web/client/assets/chunk-DU6HZSFF-C5USiOAy.js +0 -125
  499. package/dist/web/client/assets/classDiagram-ZZMXUADV-CMODalmo.js +0 -1
  500. package/dist/web/client/assets/classDiagram-v2-VYDZK3BY-CMODalmo.js +0 -1
  501. package/dist/web/client/assets/cose-bilkent-JH36ORCC-RbQL23qS.js +0 -1
  502. package/dist/web/client/assets/dist-D0_q-A2b.js +0 -1
  503. package/dist/web/client/assets/eventmodeling-NTZA5JFV-DjVq8wKU.js +0 -1
  504. package/dist/web/client/assets/flowDiagram-HODETNUW-BUiCBGXt.js +0 -1
  505. package/dist/web/client/assets/ganttDiagram-EL5Y4UJY-B2lb8lvF.js +0 -292
  506. package/dist/web/client/assets/index-FSIC94pi.js +0 -83
  507. package/dist/web/client/assets/prism-core-BngLtbUd.js +0 -1
  508. package/dist/web/client/assets/railroad-abnf-HS5TGJTU-BcW01NZG.js +0 -1
  509. package/dist/web/client/assets/railroad-ebnf-LZEXJU2U-B7YCEDDd.js +0 -1
  510. package/dist/web/client/assets/railroad-peg-WCYAUIDC-DjNZbSTZ.js +0 -1
  511. package/dist/web/client/assets/src-yeptRXpt.js +0 -1
  512. package/dist/web/client/assets/stateDiagram-v2-MP3YSRHH-C7QtHeK4.js +0 -1
  513. package/dist/web/client/assets/swimlanesDiagram-VR7AAH4N-CBZlPFky.js +0 -8
@@ -0,0 +1,1189 @@
1
+ import {
2
+ type CellClass,
3
+ type ColumnType,
4
+ capText,
5
+ classifyCell,
6
+ type DataRefusal,
7
+ type DataViewFlags,
8
+ emptyCellHistogram,
9
+ exactView,
10
+ histogramRecord,
11
+ inferColumnType,
12
+ numberLiteralPrecision,
13
+ type PrecisionIssue,
14
+ type PrecisionKind,
15
+ refusal,
16
+ refusalFromError,
17
+ resolveMaxRows,
18
+ sampledNotice,
19
+ sampledView,
20
+ statDataFile,
21
+ streamTextChunks,
22
+ type TextStreamStats,
23
+ throwIfAborted,
24
+ } from "./shared.js";
25
+
26
+ /**
27
+ * Streaming CSV/TSV reader. An RFC 4180 state machine consumes decoded text
28
+ * chunks and emits one record at a time, so memory holds the current record,
29
+ * the bounded sample, and per-column counters, never the file. Cells stay
30
+ * source text: inspection classifies them, selection returns them verbatim,
31
+ * and nothing is ever coerced to a number or a date.
32
+ */
33
+
34
+ export type CsvIssueKind =
35
+ | "bare-quote"
36
+ | "text-after-quote"
37
+ | "unterminated-quote"
38
+ | "field-too-large"
39
+ | "record-too-large";
40
+
41
+ export interface CsvIssue {
42
+ kind: CsvIssueKind;
43
+ /** 1-based record number, header included when one exists. */
44
+ record: number;
45
+ /** 1-based physical line the record starts on. */
46
+ line: number;
47
+ message: string;
48
+ }
49
+
50
+ export interface CsvParserCallbacks {
51
+ /** One complete record. Return false to stop parsing. */
52
+ onRecord(fields: string[], record: number, line: number): boolean;
53
+ onIssue(issue: CsvIssue): void;
54
+ }
55
+
56
+ enum CsvState {
57
+ FieldStart = 0,
58
+ Unquoted = 1,
59
+ Quoted = 2,
60
+ QuoteInQuoted = 3,
61
+ /** A cap was hit: the rest of the record is dropped up to the next line break. */
62
+ Overflow = 4,
63
+ }
64
+
65
+ const CHAR_QUOTE = 0x22;
66
+ const CHAR_LF = 0x0a;
67
+ const CHAR_CR = 0x0d;
68
+
69
+ /** Longest field kept, in characters; a longer one is cut here and reported. */
70
+ export const CSV_MAX_FIELD_CHARS = 1_048_576;
71
+ /** Longest record kept, counting every field's characters plus one per field. */
72
+ export const CSV_MAX_RECORD_CHARS = 16 * 1_048_576;
73
+
74
+ /**
75
+ * Incremental RFC 4180 parser. Quoted fields may hold the delimiter, quotes
76
+ * doubled, and line breaks; CRLF, LF, and a lone CR each end a record; a line
77
+ * with no content at all is a blank line, not a one-field record. Recovery is
78
+ * lenient and reported: a bare quote inside an unquoted field is literal text,
79
+ * text after a closing quote joins the field, and an unterminated quote ends at
80
+ * end of input, each with an issue naming the record.
81
+ *
82
+ * Memory is bounded whatever the input: a field is cut at CSV_MAX_FIELD_CHARS
83
+ * and a record at CSV_MAX_RECORD_CHARS, after which the record closes at the
84
+ * next line break. A stray opening quote therefore costs one oversized,
85
+ * reported record instead of swallowing the rest of the file into one field.
86
+ */
87
+ export class CsvParser {
88
+ private state = CsvState.FieldStart;
89
+ private fields: string[] = [];
90
+ private current = "";
91
+ private recordChars = 0;
92
+ private hasContent = false;
93
+ private skipLf = false;
94
+ private line = 1;
95
+ private recordLine = 1;
96
+ private records = 0;
97
+ private stopped = false;
98
+ private issueThisRecord: CsvIssueKind | null = null;
99
+ blankLines = 0;
100
+ crlf = 0;
101
+ lf = 0;
102
+ cr = 0;
103
+
104
+ constructor(
105
+ private readonly delimiter: number,
106
+ private readonly callbacks: CsvParserCallbacks,
107
+ ) {}
108
+
109
+ get isStopped(): boolean {
110
+ return this.stopped;
111
+ }
112
+
113
+ get recordCount(): number {
114
+ return this.records;
115
+ }
116
+
117
+ /** Feed one chunk. Returns false once the record sink asked to stop. */
118
+ push(text: string): boolean {
119
+ if (this.stopped) return false;
120
+ const length = text.length;
121
+ // A field that continues from the previous chunk starts its run at 0.
122
+ let runStart = this.state === CsvState.Unquoted || this.state === CsvState.Quoted ? 0 : -1;
123
+ for (let index = 0; index < length; index += 1) {
124
+ const code = text.charCodeAt(index);
125
+ if (this.skipLf) {
126
+ this.skipLf = false;
127
+ if (code === CHAR_LF) {
128
+ this.crlf += 1;
129
+ this.cr -= 1;
130
+ continue;
131
+ }
132
+ }
133
+ switch (this.state) {
134
+ case CsvState.FieldStart:
135
+ if (code === CHAR_QUOTE) {
136
+ this.state = CsvState.Quoted;
137
+ this.hasContent = true;
138
+ runStart = index + 1;
139
+ } else if (code === this.delimiter) {
140
+ this.hasContent = true;
141
+ this.pushField("");
142
+ } else if (code === CHAR_LF || code === CHAR_CR) {
143
+ if (this.hasContent) this.pushField("");
144
+ this.newline(code);
145
+ this.endRecord();
146
+ } else {
147
+ this.state = CsvState.Unquoted;
148
+ this.hasContent = true;
149
+ runStart = index;
150
+ }
151
+ break;
152
+ case CsvState.Unquoted:
153
+ if (code === this.delimiter) {
154
+ const fits = this.appendRun(text, runStart, index);
155
+ runStart = -1;
156
+ if (!fits) break;
157
+ this.pushField(this.current);
158
+ this.current = "";
159
+ if (this.state === CsvState.Unquoted) this.state = CsvState.FieldStart;
160
+ } else if (code === CHAR_LF || code === CHAR_CR) {
161
+ const fits = this.appendRun(text, runStart, index);
162
+ runStart = -1;
163
+ if (!fits) {
164
+ this.closeOverflow(code);
165
+ break;
166
+ }
167
+ this.pushField(this.current);
168
+ this.current = "";
169
+ this.state = CsvState.FieldStart;
170
+ this.newline(code);
171
+ this.endRecord();
172
+ } else if (code === CHAR_QUOTE) {
173
+ this.issue("bare-quote", "a quote inside an unquoted field was read as literal text");
174
+ }
175
+ break;
176
+ case CsvState.Quoted:
177
+ if (code === CHAR_QUOTE) {
178
+ const fits = this.appendRun(text, runStart, index);
179
+ runStart = -1;
180
+ if (fits) this.state = CsvState.QuoteInQuoted;
181
+ } else if (code === CHAR_LF || code === CHAR_CR) {
182
+ // A line break inside quotes is where an oversized field gets closed,
183
+ // so the caps are checked here whatever the chunk boundaries were.
184
+ const pending = index - runStart;
185
+ if (this.current.length + pending > CSV_MAX_FIELD_CHARS || this.recordChars + pending > CSV_MAX_RECORD_CHARS) {
186
+ this.appendRun(text, runStart, index);
187
+ runStart = -1;
188
+ this.closeOverflow(code);
189
+ } else if (code === CHAR_LF) {
190
+ this.line += 1;
191
+ this.lf += 1;
192
+ } else {
193
+ // The LF that may follow is field content; only the line counter pairs them.
194
+ this.line += 1;
195
+ this.cr += 1;
196
+ this.skipLf = true;
197
+ }
198
+ }
199
+ break;
200
+ case CsvState.QuoteInQuoted:
201
+ if (code === CHAR_QUOTE) {
202
+ if (this.appendRun('"', 0, 1)) {
203
+ this.state = CsvState.Quoted;
204
+ runStart = index + 1;
205
+ }
206
+ } else if (code === this.delimiter) {
207
+ this.pushField(this.current);
208
+ this.current = "";
209
+ if (this.state === CsvState.QuoteInQuoted) this.state = CsvState.FieldStart;
210
+ } else if (code === CHAR_LF || code === CHAR_CR) {
211
+ this.pushField(this.current);
212
+ this.current = "";
213
+ this.state = CsvState.FieldStart;
214
+ this.newline(code);
215
+ this.endRecord();
216
+ } else {
217
+ this.issue("text-after-quote", "text after a closing quote was joined to the field");
218
+ this.state = CsvState.Unquoted;
219
+ runStart = index;
220
+ }
221
+ break;
222
+ case CsvState.Overflow:
223
+ if (code === CHAR_LF || code === CHAR_CR) this.closeOverflow(code);
224
+ break;
225
+ }
226
+ if (this.stopped) return false;
227
+ }
228
+ if (runStart >= 0 && (this.state === CsvState.Unquoted || this.state === CsvState.Quoted)) {
229
+ this.appendRun(text, runStart, length);
230
+ }
231
+ return true;
232
+ }
233
+
234
+ end(): void {
235
+ if (this.stopped) return;
236
+ switch (this.state) {
237
+ case CsvState.Quoted:
238
+ this.issue("unterminated-quote", "the file ended inside a quoted field");
239
+ this.fields.push(this.current);
240
+ this.endRecord();
241
+ break;
242
+ case CsvState.QuoteInQuoted:
243
+ case CsvState.Unquoted:
244
+ case CsvState.Overflow:
245
+ this.fields.push(this.current);
246
+ this.endRecord();
247
+ break;
248
+ default:
249
+ if (this.hasContent) {
250
+ this.fields.push("");
251
+ this.endRecord();
252
+ }
253
+ break;
254
+ }
255
+ this.current = "";
256
+ this.state = CsvState.FieldStart;
257
+ }
258
+
259
+ /**
260
+ * Append text[from, to) to the current field within the caps. Returns false
261
+ * when a cap was hit: the field keeps what fit and the parser is in the
262
+ * Overflow state, dropping input until the record's next line break.
263
+ */
264
+ private appendRun(text: string, from: number, to: number): boolean {
265
+ if (to <= from) return true;
266
+ const run = to - from;
267
+ const fieldRoom = CSV_MAX_FIELD_CHARS - this.current.length;
268
+ const recordRoom = CSV_MAX_RECORD_CHARS - this.recordChars;
269
+ const room = Math.min(fieldRoom, recordRoom);
270
+ if (run <= room) {
271
+ this.current += text.slice(from, to);
272
+ this.recordChars += run;
273
+ return true;
274
+ }
275
+ if (room > 0) {
276
+ this.current += text.slice(from, from + room);
277
+ this.recordChars += room;
278
+ }
279
+ this.overflow(fieldRoom <= recordRoom ? "field-too-large" : "record-too-large");
280
+ return false;
281
+ }
282
+
283
+ /** Complete a field; each field also costs one character of the record cap. */
284
+ private pushField(value: string): void {
285
+ this.fields.push(value);
286
+ this.recordChars += 1;
287
+ if (this.recordChars > CSV_MAX_RECORD_CHARS) this.overflow("record-too-large");
288
+ }
289
+
290
+ private overflow(kind: "field-too-large" | "record-too-large"): void {
291
+ const message =
292
+ kind === "field-too-large"
293
+ ? `a field exceeded ${CSV_MAX_FIELD_CHARS} characters${
294
+ this.state === CsvState.Quoted ? " inside quotes, likely an unterminated quote" : ""
295
+ }; it was cut at the cap and the record was closed at the next line break`
296
+ : `a record exceeded ${CSV_MAX_RECORD_CHARS} characters; it was cut at the cap and closed at the next line break`;
297
+ this.issue(kind, message);
298
+ this.state = CsvState.Overflow;
299
+ }
300
+
301
+ private closeOverflow(code: number): void {
302
+ this.fields.push(this.current);
303
+ this.current = "";
304
+ this.state = CsvState.FieldStart;
305
+ this.newline(code);
306
+ this.endRecord();
307
+ }
308
+
309
+ private newline(code: number): void {
310
+ this.line += 1;
311
+ if (code === CHAR_CR) {
312
+ this.cr += 1;
313
+ this.skipLf = true;
314
+ } else {
315
+ this.lf += 1;
316
+ }
317
+ }
318
+
319
+ private endRecord(): void {
320
+ const startLine = this.recordLine;
321
+ this.recordLine = this.line;
322
+ this.recordChars = 0;
323
+ if (!this.hasContent && this.fields.length === 0) {
324
+ this.blankLines += 1;
325
+ return;
326
+ }
327
+ this.records += 1;
328
+ const fields = this.fields;
329
+ this.fields = [];
330
+ this.hasContent = false;
331
+ this.issueThisRecord = null;
332
+ if (!this.callbacks.onRecord(fields, this.records, startLine)) this.stopped = true;
333
+ }
334
+
335
+ private issue(kind: CsvIssueKind, message: string): void {
336
+ if (this.issueThisRecord === kind) return;
337
+ this.issueThisRecord = kind;
338
+ this.callbacks.onIssue({ kind, record: this.records + 1, line: this.recordLine, message });
339
+ }
340
+ }
341
+
342
+ export const CSV_DELIMITER_CANDIDATES = [",", "\t", ";", "|"] as const;
343
+ const DELIMITER_SAMPLE_CHARS = 64 * 1024;
344
+ const DELIMITER_SAMPLE_RECORDS = 200;
345
+
346
+ export interface CsvDelimiterDetection {
347
+ delimiter: string;
348
+ /** Share of sampled records whose field count equals the modal count; 0 when nothing split. */
349
+ confidence: number;
350
+ fieldCount: number;
351
+ }
352
+
353
+ /**
354
+ * Pick the delimiter that splits the sample most consistently: parse the first
355
+ * records under each candidate, take the modal field count, and score by how
356
+ * many records share it. A candidate that never produces two fields is out.
357
+ * Ties fall to candidate order (comma first). A file that no candidate splits
358
+ * is a one-column file read with the comma.
359
+ */
360
+ export function detectCsvDelimiter(sample: string, sampleComplete: boolean): CsvDelimiterDetection {
361
+ let best: CsvDelimiterDetection = { delimiter: ",", confidence: 0, fieldCount: 1 };
362
+ for (const candidate of CSV_DELIMITER_CANDIDATES) {
363
+ const counts: number[] = [];
364
+ const parser = new CsvParser(candidate.charCodeAt(0), {
365
+ onRecord(fields) {
366
+ counts.push(fields.length);
367
+ return counts.length < DELIMITER_SAMPLE_RECORDS;
368
+ },
369
+ onIssue() {},
370
+ });
371
+ parser.push(sample);
372
+ if (sampleComplete) parser.end();
373
+ // The sample's last record may be cut mid-line; only a complete sample keeps it.
374
+ const usable = sampleComplete || parser.isStopped ? counts : counts.slice(0, -1);
375
+ if (usable.length === 0) continue;
376
+ const tally = new Map<number, number>();
377
+ for (const count of usable) tally.set(count, (tally.get(count) ?? 0) + 1);
378
+ let modal = 1;
379
+ let modalHits = 0;
380
+ for (const [count, hits] of tally) {
381
+ if (hits > modalHits || (hits === modalHits && count > modal)) {
382
+ modal = count;
383
+ modalHits = hits;
384
+ }
385
+ }
386
+ if (modal < 2) continue;
387
+ const confidence = modalHits / usable.length;
388
+ if (confidence > best.confidence) best = { delimiter: candidate, confidence, fieldCount: modal };
389
+ }
390
+ return best;
391
+ }
392
+
393
+ export type CsvHeaderOption = "auto" | boolean;
394
+
395
+ /** A first record reads as a header when every cell is non-numeric text and a second record exists. */
396
+ export function headerLooksLikeNames(record: ReadonlyArray<string>): boolean {
397
+ return record.length > 0 && record.every((cell) => cell.trim().length > 0 && classifyCell(cell.trim()) === "string");
398
+ }
399
+
400
+ function defaultColumnName(index: number): string {
401
+ return `column_${index + 1}`;
402
+ }
403
+
404
+ export interface CsvColumnReport {
405
+ index: number;
406
+ name: string;
407
+ inferredType: ColumnType;
408
+ nonEmpty: number;
409
+ empty: number;
410
+ /** Missing-value tokens seen in this column, by token. Counted, never converted. */
411
+ sentinels: Record<string, number>;
412
+ /** Cell classes over the scanned rows. */
413
+ types: Record<CellClass, number>;
414
+ /** Up to five distinct values in first-seen order, each capped for the report. */
415
+ sampleValues: string[];
416
+ minLength: number;
417
+ maxLength: number;
418
+ /** Extremes of the numeric cells as their source text; approximate when a literal exceeded double precision. */
419
+ numericRange?: { min: string; max: string; approximate: boolean };
420
+ /** Numeric cells whose double representation would misstate the text. */
421
+ precisionIssues: number;
422
+ }
423
+
424
+ export interface CsvRaggedRow {
425
+ /** 1-based data row (header excluded). */
426
+ row: number;
427
+ line: number;
428
+ fields: number;
429
+ }
430
+
431
+ export interface CsvInspectResult {
432
+ ok: true;
433
+ format: "csv" | "tsv";
434
+ path: string;
435
+ bytes: number;
436
+ bytesScanned: number;
437
+ view: DataViewFlags;
438
+ /** Data rows parsed, header excluded. */
439
+ rowsScanned: number;
440
+ /** Data rows in the file; null when the scan stopped at maxRows. */
441
+ rowCount: number | null;
442
+ delimiter: string;
443
+ delimiterSource: "explicit" | "detected" | "default";
444
+ header: string[] | null;
445
+ headerSource: "explicit" | "detected" | "none";
446
+ bom: boolean;
447
+ lineEnding: "lf" | "crlf" | "cr" | "mixed" | "none";
448
+ /** Field count the header (or first record) established. */
449
+ fieldCount: number;
450
+ columns: CsvColumnReport[];
451
+ /** True when ragged rows introduced more columns than the report tracks. */
452
+ columnsTruncated: boolean;
453
+ raggedRows: { count: number; first: CsvRaggedRow[] };
454
+ quotingIssues: { count: number; first: CsvIssue[] };
455
+ /** Numeric cells whose double representation would misstate the text, with row and column. */
456
+ precision: { count: number; first: PrecisionIssue[] };
457
+ blankLines: number;
458
+ /** First data rows as source text, each cell capped at SAMPLE_TEXT_CAP. */
459
+ sample: string[][];
460
+ sampleTruncatedCells: number;
461
+ notes: string[];
462
+ }
463
+
464
+ export interface CsvInspectOptions {
465
+ format?: "csv" | "tsv";
466
+ delimiter?: string;
467
+ header?: CsvHeaderOption;
468
+ sampleRows?: number;
469
+ maxRows?: number | null;
470
+ signal?: AbortSignal | undefined;
471
+ }
472
+
473
+ export const CSV_DEFAULT_SAMPLE_ROWS = 10;
474
+ export const CSV_DEFAULT_MAX_ROWS = 100_000;
475
+ const COLUMN_TRACK_CAP = 10_000;
476
+ const FIRST_ISSUES_CAP = 10;
477
+ const SAMPLE_VALUES_CAP = 5;
478
+
479
+ interface ColumnAccumulator {
480
+ index: number;
481
+ name: string;
482
+ nonEmpty: number;
483
+ empty: number;
484
+ sentinels: Map<string, number>;
485
+ types: Record<CellClass, number>;
486
+ samples: string[];
487
+ minLength: number;
488
+ maxLength: number;
489
+ intMin: bigint | null;
490
+ intMax: bigint | null;
491
+ intMinText: string;
492
+ intMaxText: string;
493
+ floatMin: number;
494
+ floatMax: number;
495
+ floatMinText: string;
496
+ floatMaxText: string;
497
+ floatApproximate: boolean;
498
+ precisionIssues: number;
499
+ }
500
+
501
+ function newColumn(index: number, name: string): ColumnAccumulator {
502
+ return {
503
+ index,
504
+ name,
505
+ nonEmpty: 0,
506
+ empty: 0,
507
+ sentinels: new Map(),
508
+ types: emptyCellHistogram(),
509
+ samples: [],
510
+ minLength: Number.POSITIVE_INFINITY,
511
+ maxLength: 0,
512
+ intMin: null,
513
+ intMax: null,
514
+ intMinText: "",
515
+ intMaxText: "",
516
+ floatMin: Number.POSITIVE_INFINITY,
517
+ floatMax: Number.NEGATIVE_INFINITY,
518
+ floatMinText: "",
519
+ floatMaxText: "",
520
+ floatApproximate: false,
521
+ precisionIssues: 0,
522
+ };
523
+ }
524
+
525
+ /** Fold one cell into its column; returns the precision kind when the cell is a number a double would misstate. */
526
+ function accumulateCell(column: ColumnAccumulator, cell: string): PrecisionKind | null {
527
+ const cls = classifyCell(cell);
528
+ column.types[cls] += 1;
529
+ if (cls === "empty") {
530
+ column.empty += 1;
531
+ column.minLength = 0;
532
+ return null;
533
+ }
534
+ column.nonEmpty += 1;
535
+ column.minLength = Math.min(column.minLength, cell.length);
536
+ column.maxLength = Math.max(column.maxLength, cell.length);
537
+ if (cls === "sentinel") {
538
+ column.sentinels.set(cell, (column.sentinels.get(cell) ?? 0) + 1);
539
+ return null;
540
+ }
541
+ if (column.samples.length < SAMPLE_VALUES_CAP && !column.samples.includes(cell)) column.samples.push(cell);
542
+ if (cls === "integer") {
543
+ const value = BigInt(cell);
544
+ if (column.intMin === null || value < column.intMin) {
545
+ column.intMin = value;
546
+ column.intMinText = cell;
547
+ }
548
+ if (column.intMax === null || value > column.intMax) {
549
+ column.intMax = value;
550
+ column.intMaxText = cell;
551
+ }
552
+ const precision = numberLiteralPrecision(cell);
553
+ if (precision !== null) column.precisionIssues += 1;
554
+ return precision;
555
+ }
556
+ if (cls === "float") {
557
+ const value = Number(cell);
558
+ const precision = numberLiteralPrecision(cell);
559
+ if (precision !== null) {
560
+ column.precisionIssues += 1;
561
+ column.floatApproximate = true;
562
+ }
563
+ if (Number.isFinite(value)) {
564
+ if (value < column.floatMin) {
565
+ column.floatMin = value;
566
+ column.floatMinText = cell;
567
+ }
568
+ if (value > column.floatMax) {
569
+ column.floatMax = value;
570
+ column.floatMaxText = cell;
571
+ }
572
+ }
573
+ return precision;
574
+ }
575
+ return null;
576
+ }
577
+
578
+ function numericRange(column: ColumnAccumulator): CsvColumnReport["numericRange"] {
579
+ const hasInt = column.intMin !== null && column.intMax !== null;
580
+ const hasFloat = Number.isFinite(column.floatMin);
581
+ if (!hasInt && !hasFloat) return undefined;
582
+ if (hasInt && !hasFloat) return { min: column.intMinText, max: column.intMaxText, approximate: false };
583
+ if (!hasInt) return { min: column.floatMinText, max: column.floatMaxText, approximate: column.floatApproximate };
584
+ // Mixed integers and floats compare through doubles, so the range is
585
+ // approximate whenever an integer left the exactly representable range.
586
+ const intMinNumber = Number(column.intMin);
587
+ const intMaxNumber = Number(column.intMax);
588
+ const approximate =
589
+ column.floatApproximate || !Number.isSafeInteger(intMinNumber) || !Number.isSafeInteger(intMaxNumber);
590
+ const min = intMinNumber <= column.floatMin ? column.intMinText : column.floatMinText;
591
+ const max = intMaxNumber >= column.floatMax ? column.intMaxText : column.floatMaxText;
592
+ return { min, max, approximate };
593
+ }
594
+
595
+ function columnReport(column: ColumnAccumulator): CsvColumnReport {
596
+ const range = numericRange(column);
597
+ return {
598
+ index: column.index,
599
+ name: column.name,
600
+ inferredType: inferColumnType(column.types),
601
+ nonEmpty: column.nonEmpty,
602
+ empty: column.empty,
603
+ sentinels: histogramRecord(column.sentinels),
604
+ types: { ...column.types },
605
+ sampleValues: column.samples.map((value) => capText(value).text),
606
+ minLength: Number.isFinite(column.minLength) ? column.minLength : 0,
607
+ maxLength: column.maxLength,
608
+ ...(range !== undefined ? { numericRange: range } : {}),
609
+ precisionIssues: column.precisionIssues,
610
+ };
611
+ }
612
+
613
+ function lineEndingOf(parser: CsvParser): CsvInspectResult["lineEnding"] {
614
+ const kinds = [parser.lf > 0, parser.crlf > 0, parser.cr > 0].filter(Boolean).length;
615
+ if (kinds === 0) return "none";
616
+ if (kinds > 1) return "mixed";
617
+ if (parser.crlf > 0) return "crlf";
618
+ if (parser.cr > 0) return "cr";
619
+ return "lf";
620
+ }
621
+
622
+ function resolveDelimiter(explicit: string | undefined, format: "csv" | "tsv"): string | Error | null {
623
+ if (explicit === undefined) return format === "tsv" ? "\t" : null;
624
+ if (explicit === "\\t") return "\t";
625
+ if (explicit.length !== 1) return new Error("delimiter must be one character (or \\t)");
626
+ if (explicit === '"' || explicit === "\n" || explicit === "\r") {
627
+ return new Error("delimiter cannot be a quote or a line break");
628
+ }
629
+ return explicit;
630
+ }
631
+
632
+ interface CsvScan {
633
+ parser: CsvParser;
634
+ delimiter: string;
635
+ delimiterSource: CsvInspectResult["delimiterSource"];
636
+ }
637
+
638
+ /**
639
+ * Drive the parser over the file. The first 64 KiB of text is held back for
640
+ * delimiter detection when none was given, then fed through the same parser
641
+ * so the detection sample is never parsed twice against the chosen delimiter.
642
+ */
643
+ async function scanCsv(
644
+ path: string,
645
+ explicitDelimiter: string | null,
646
+ signal: AbortSignal | undefined,
647
+ stats: TextStreamStats,
648
+ callbacks: CsvParserCallbacks,
649
+ ): Promise<CsvScan> {
650
+ let parser: CsvParser | null = null;
651
+ let delimiter = explicitDelimiter;
652
+ let delimiterSource: CsvInspectResult["delimiterSource"] = delimiter === null ? "detected" : "explicit";
653
+ let sample = "";
654
+ for await (const chunk of streamTextChunks(path, { signal, stats })) {
655
+ if (parser === null) {
656
+ if (delimiter === null) {
657
+ sample += chunk.text;
658
+ if (sample.length < DELIMITER_SAMPLE_CHARS) continue;
659
+ const detected = detectCsvDelimiter(sample.slice(0, DELIMITER_SAMPLE_CHARS), false);
660
+ delimiter = detected.delimiter;
661
+ if (detected.confidence === 0) delimiterSource = "default";
662
+ parser = new CsvParser(delimiter.charCodeAt(0), callbacks);
663
+ const keepGoing = parser.push(sample);
664
+ sample = "";
665
+ if (!keepGoing) break;
666
+ continue;
667
+ }
668
+ parser = new CsvParser(delimiter.charCodeAt(0), callbacks);
669
+ }
670
+ if (!parser.push(chunk.text)) break;
671
+ }
672
+ throwIfAborted(signal, path);
673
+ if (parser === null) {
674
+ // The whole file fit inside the detection sample (or it is empty).
675
+ if (delimiter === null) {
676
+ const detected = detectCsvDelimiter(sample, true);
677
+ delimiter = detected.delimiter;
678
+ if (detected.confidence === 0) delimiterSource = "default";
679
+ }
680
+ parser = new CsvParser(delimiter.charCodeAt(0), callbacks);
681
+ parser.push(sample);
682
+ }
683
+ if (!parser.isStopped) parser.end();
684
+ return { parser, delimiter: delimiter as string, delimiterSource };
685
+ }
686
+
687
+ interface FirstRecord {
688
+ fields: string[];
689
+ line: number;
690
+ }
691
+
692
+ /**
693
+ * Header resolution shared by the three readers. Under `auto` the first
694
+ * record waits for the second: it is a header only when every cell is a
695
+ * non-numeric name and another record exists, so a one-record file under
696
+ * auto is one data row.
697
+ */
698
+ class HeaderResolver {
699
+ header: string[] | null = null;
700
+ source: CsvInspectResult["headerSource"] = "none";
701
+ fieldCount = 0;
702
+ settled = false;
703
+ pending: FirstRecord | null = null;
704
+
705
+ constructor(private readonly option: CsvHeaderOption) {}
706
+
707
+ /** Returns the data rows this record releases, in order. */
708
+ feed(fields: string[], line: number): FirstRecord[] {
709
+ if (this.settled) return [{ fields, line }];
710
+ if (this.option === true) {
711
+ this.settle(fields, "explicit", fields.length);
712
+ return [];
713
+ }
714
+ if (this.option === false) {
715
+ this.settle(null, "none", fields.length);
716
+ return [{ fields, line }];
717
+ }
718
+ if (this.pending === null) {
719
+ this.pending = { fields, line };
720
+ return [];
721
+ }
722
+ const first = this.pending;
723
+ this.pending = null;
724
+ if (headerLooksLikeNames(first.fields)) {
725
+ this.settle(first.fields, "detected", first.fields.length);
726
+ return [{ fields, line }];
727
+ }
728
+ this.settle(null, "none", first.fields.length);
729
+ return [first, { fields, line }];
730
+ }
731
+
732
+ /** End of input: a lone pending record is data. */
733
+ finish(): FirstRecord[] {
734
+ if (this.settled) return [];
735
+ const first = this.pending;
736
+ this.pending = null;
737
+ if (first === null) {
738
+ this.settle(this.option === true ? [] : null, this.option === true ? "explicit" : "none", 0);
739
+ return [];
740
+ }
741
+ this.settle(null, "none", first.fields.length);
742
+ return [first];
743
+ }
744
+
745
+ private settle(header: string[] | null, source: CsvInspectResult["headerSource"], fieldCount: number): void {
746
+ this.settled = true;
747
+ this.header = header;
748
+ this.source = source;
749
+ this.fieldCount = fieldCount;
750
+ }
751
+ }
752
+
753
+ export async function inspectCsv(
754
+ path: string,
755
+ options: CsvInspectOptions = {},
756
+ ): Promise<CsvInspectResult | DataRefusal> {
757
+ const stat = statDataFile(path);
758
+ if ("ok" in stat) return stat;
759
+ const format = options.format ?? "csv";
760
+ const explicitDelimiter = resolveDelimiter(options.delimiter, format);
761
+ if (explicitDelimiter instanceof Error) return refusal("invalid-argument", explicitDelimiter.message, { path });
762
+ const sampleRows = Math.max(0, Math.min(1000, Math.floor(options.sampleRows ?? CSV_DEFAULT_SAMPLE_ROWS)));
763
+ const maxRows = resolveMaxRows(options.maxRows, CSV_DEFAULT_MAX_ROWS);
764
+ const stats: TextStreamStats = { bytesRead: 0, bom: false };
765
+ const headers = new HeaderResolver(options.header ?? "auto");
766
+
767
+ const state = {
768
+ columns: [] as ColumnAccumulator[],
769
+ columnsTruncated: false,
770
+ columnsEstablished: false,
771
+ dataRows: 0,
772
+ stoppedAtRows: false,
773
+ raggedCount: 0,
774
+ raggedFirst: [] as CsvRaggedRow[],
775
+ issueCount: 0,
776
+ issueFirst: [] as CsvIssue[],
777
+ precisionCount: 0,
778
+ precisionFirst: [] as PrecisionIssue[],
779
+ sample: [] as string[][],
780
+ sampleTruncatedCells: 0,
781
+ };
782
+
783
+ const establishColumns = (): void => {
784
+ if (state.columnsEstablished) return;
785
+ state.columnsEstablished = true;
786
+ const names = headers.header ?? Array.from({ length: headers.fieldCount }, (_, index) => defaultColumnName(index));
787
+ for (const [index, name] of names.entries()) {
788
+ if (state.columns.length >= COLUMN_TRACK_CAP) {
789
+ state.columnsTruncated = true;
790
+ break;
791
+ }
792
+ state.columns.push(newColumn(index, name));
793
+ }
794
+ };
795
+
796
+ const consumeDataRow = (fields: string[], line: number): boolean => {
797
+ establishColumns();
798
+ if (state.dataRows >= maxRows) {
799
+ state.stoppedAtRows = true;
800
+ return false;
801
+ }
802
+ state.dataRows += 1;
803
+ if (fields.length !== headers.fieldCount) {
804
+ state.raggedCount += 1;
805
+ if (state.raggedFirst.length < FIRST_ISSUES_CAP) {
806
+ state.raggedFirst.push({ row: state.dataRows, line, fields: fields.length });
807
+ }
808
+ }
809
+ for (const [index, cell] of fields.entries()) {
810
+ let column = state.columns[index];
811
+ if (column === undefined) {
812
+ if (state.columns.length >= COLUMN_TRACK_CAP) {
813
+ state.columnsTruncated = true;
814
+ break;
815
+ }
816
+ column = newColumn(index, defaultColumnName(index));
817
+ state.columns.push(column);
818
+ }
819
+ const precision = accumulateCell(column, cell);
820
+ if (precision !== null) {
821
+ state.precisionCount += 1;
822
+ if (state.precisionFirst.length < FIRST_ISSUES_CAP) {
823
+ state.precisionFirst.push({ kind: precision, literal: cell, row: state.dataRows, column: index });
824
+ }
825
+ }
826
+ }
827
+ if (state.sample.length < sampleRows) {
828
+ state.sample.push(
829
+ fields.map((cell) => {
830
+ const capped = capText(cell);
831
+ if (capped.truncated) state.sampleTruncatedCells += 1;
832
+ return capped.text;
833
+ }),
834
+ );
835
+ }
836
+ return true;
837
+ };
838
+
839
+ const callbacks: CsvParserCallbacks = {
840
+ onRecord(fields, _record, line) {
841
+ for (const row of headers.feed(fields, line)) {
842
+ if (!consumeDataRow(row.fields, row.line)) return false;
843
+ }
844
+ return true;
845
+ },
846
+ onIssue(issue) {
847
+ state.issueCount += 1;
848
+ if (state.issueFirst.length < FIRST_ISSUES_CAP) state.issueFirst.push(issue);
849
+ },
850
+ };
851
+
852
+ let scan: CsvScan;
853
+ try {
854
+ scan = await scanCsv(path, explicitDelimiter, options.signal, stats, callbacks);
855
+ } catch (error) {
856
+ return refusalFromError(error, path, options.signal);
857
+ }
858
+ if (!scan.parser.isStopped) {
859
+ for (const row of headers.finish()) consumeDataRow(row.fields, row.line);
860
+ establishColumns();
861
+ }
862
+ const sampled = state.stoppedAtRows;
863
+ const notes: string[] = [];
864
+ if (stats.bom) notes.push("a UTF-8 byte-order mark was stripped");
865
+ if (sampled) notes.push(sampledNotice(state.dataRows, "rows"));
866
+ if (scan.delimiterSource === "default") {
867
+ notes.push("no delimiter split the sample consistently; read as one column with the comma");
868
+ }
869
+ if (state.columnsTruncated) notes.push(`column tracking capped at ${COLUMN_TRACK_CAP} columns`);
870
+ if (state.raggedCount > 0) {
871
+ notes.push(`${state.raggedCount} row(s) have a field count other than ${headers.fieldCount}`);
872
+ }
873
+ if (state.issueCount > 0) notes.push(`${state.issueCount} quoting issue(s) were recovered leniently and are listed`);
874
+ if (state.precisionCount > 0) {
875
+ notes.push(`${state.precisionCount} numeric cell(s) would lose precision as doubles; cells stay source text`);
876
+ }
877
+ return {
878
+ ok: true,
879
+ format,
880
+ path,
881
+ bytes: stat.size,
882
+ bytesScanned: stats.bytesRead,
883
+ view: sampled ? sampledView() : exactView(),
884
+ rowsScanned: state.dataRows,
885
+ rowCount: sampled ? null : state.dataRows,
886
+ delimiter: scan.delimiter,
887
+ delimiterSource: scan.delimiterSource,
888
+ header: headers.header,
889
+ headerSource: headers.source,
890
+ bom: stats.bom,
891
+ lineEnding: lineEndingOf(scan.parser),
892
+ fieldCount: headers.fieldCount,
893
+ columns: state.columns.map(columnReport),
894
+ columnsTruncated: state.columnsTruncated,
895
+ raggedRows: { count: state.raggedCount, first: state.raggedFirst },
896
+ quotingIssues: { count: state.issueCount, first: state.issueFirst },
897
+ precision: { count: state.precisionCount, first: state.precisionFirst },
898
+ blankLines: scan.parser.blankLines,
899
+ sample: state.sample,
900
+ sampleTruncatedCells: state.sampleTruncatedCells,
901
+ notes,
902
+ };
903
+ }
904
+
905
+ export interface CsvSelectOptions {
906
+ format?: "csv" | "tsv";
907
+ delimiter?: string;
908
+ header?: CsvHeaderOption;
909
+ /** 0-based first data row to return. */
910
+ offset?: number;
911
+ limit?: number;
912
+ /** Column names (from the header) or 0-based indices to project; omit for every column. */
913
+ columns?: ReadonlyArray<string | number>;
914
+ signal?: AbortSignal | undefined;
915
+ }
916
+
917
+ export const CSV_SELECT_DEFAULT_LIMIT = 50;
918
+ export const CSV_SELECT_MAX_LIMIT = 1000;
919
+
920
+ export interface CsvSelectResult {
921
+ ok: true;
922
+ format: "csv" | "tsv";
923
+ path: string;
924
+ bytes: number;
925
+ bytesScanned: number;
926
+ view: DataViewFlags;
927
+ delimiter: string;
928
+ /** Projected header names, or null when the file has no header. */
929
+ header: string[] | null;
930
+ /** Column indices the rows carry, in output order; empty when no projection and no header fixed a width. */
931
+ columnIndexes: number[];
932
+ offset: number;
933
+ limit: number;
934
+ returned: number;
935
+ /** Whether a data row exists past the returned window. */
936
+ hasMore: boolean;
937
+ /** Data rows parsed to produce the window, skipped prefix included. */
938
+ rowsScanned: number;
939
+ /** Rows as source text; a projected index past a short row is null. */
940
+ rows: Array<Array<string | null>>;
941
+ notes: string[];
942
+ }
943
+
944
+ export async function selectCsv(path: string, options: CsvSelectOptions = {}): Promise<CsvSelectResult | DataRefusal> {
945
+ const stat = statDataFile(path);
946
+ if ("ok" in stat) return stat;
947
+ const format = options.format ?? "csv";
948
+ const explicitDelimiter = resolveDelimiter(options.delimiter, format);
949
+ if (explicitDelimiter instanceof Error) return refusal("invalid-argument", explicitDelimiter.message, { path });
950
+ const offset = Math.max(0, Math.floor(options.offset ?? 0));
951
+ const limit = Math.max(1, Math.min(CSV_SELECT_MAX_LIMIT, Math.floor(options.limit ?? CSV_SELECT_DEFAULT_LIMIT)));
952
+ const stats: TextStreamStats = { bytesRead: 0, bom: false };
953
+ const headers = new HeaderResolver(options.header ?? "auto");
954
+ const state = {
955
+ projection: null as number[] | null,
956
+ projectionResolved: false,
957
+ projectionError: null as DataRefusal | null,
958
+ dataRows: 0,
959
+ hasMore: false,
960
+ rows: [] as Array<Array<string | null>>,
961
+ };
962
+
963
+ const resolveProjection = (): boolean => {
964
+ if (state.projectionResolved) return state.projectionError === null;
965
+ state.projectionResolved = true;
966
+ if (options.columns === undefined) return true;
967
+ const names = headers.header;
968
+ const indexes: number[] = [];
969
+ for (const column of options.columns) {
970
+ if (typeof column === "number") {
971
+ if (!Number.isInteger(column) || column < 0) {
972
+ state.projectionError = refusal("invalid-argument", `column index ${column} is not a non-negative integer`, {
973
+ path,
974
+ });
975
+ return false;
976
+ }
977
+ indexes.push(column);
978
+ continue;
979
+ }
980
+ const index = names?.indexOf(column) ?? -1;
981
+ if (index === -1) {
982
+ state.projectionError = refusal(
983
+ "unknown-column",
984
+ names === null
985
+ ? `column "${column}" cannot be resolved: the file has no header; use a 0-based index`
986
+ : `unknown column "${column}"; header columns are ${names.map((name) => JSON.stringify(name)).join(", ")}`,
987
+ { path },
988
+ );
989
+ return false;
990
+ }
991
+ indexes.push(index);
992
+ }
993
+ state.projection = indexes;
994
+ return true;
995
+ };
996
+
997
+ const consumeDataRow = (fields: string[]): boolean => {
998
+ if (!resolveProjection()) return false;
999
+ const index = state.dataRows;
1000
+ state.dataRows += 1;
1001
+ if (index < offset) return true;
1002
+ if (state.rows.length >= limit) {
1003
+ state.hasMore = true;
1004
+ return false;
1005
+ }
1006
+ const projection = state.projection;
1007
+ state.rows.push(projection === null ? fields : projection.map((column) => fields[column] ?? null));
1008
+ return true;
1009
+ };
1010
+
1011
+ const callbacks: CsvParserCallbacks = {
1012
+ onRecord(fields, _record, line) {
1013
+ for (const row of headers.feed(fields, line)) {
1014
+ if (!consumeDataRow(row.fields)) return false;
1015
+ }
1016
+ if (headers.settled && !resolveProjection()) return false;
1017
+ return true;
1018
+ },
1019
+ onIssue() {},
1020
+ };
1021
+
1022
+ let scan: CsvScan;
1023
+ try {
1024
+ scan = await scanCsv(path, explicitDelimiter, options.signal, stats, callbacks);
1025
+ } catch (error) {
1026
+ return refusalFromError(error, path, options.signal);
1027
+ }
1028
+ if (state.projectionError !== null) return state.projectionError;
1029
+ if (!scan.parser.isStopped) {
1030
+ for (const row of headers.finish()) consumeDataRow(row.fields);
1031
+ if (!resolveProjection()) return state.projectionError ?? refusal("invalid-argument", "bad projection", { path });
1032
+ }
1033
+ const header = headers.header;
1034
+ const projection = state.projection;
1035
+ const indexes =
1036
+ projection ??
1037
+ (header !== null
1038
+ ? header.map((_, index) => index)
1039
+ : Array.from({ length: state.rows.reduce((max, row) => Math.max(max, row.length), 0) }, (_, index) => index));
1040
+ const notes: string[] = [];
1041
+ if (stats.bom) notes.push("a UTF-8 byte-order mark was stripped");
1042
+ if (scan.delimiterSource === "default") {
1043
+ notes.push("no delimiter split the sample consistently; read as one column with the comma");
1044
+ }
1045
+ return {
1046
+ ok: true,
1047
+ format,
1048
+ path,
1049
+ bytes: stat.size,
1050
+ bytesScanned: stats.bytesRead,
1051
+ view: exactView(),
1052
+ delimiter: scan.delimiter,
1053
+ header:
1054
+ header === null
1055
+ ? null
1056
+ : projection === null
1057
+ ? header
1058
+ : projection.map((column) => header[column] ?? defaultColumnName(column)),
1059
+ columnIndexes: indexes,
1060
+ offset,
1061
+ limit,
1062
+ returned: state.rows.length,
1063
+ hasMore: state.hasMore,
1064
+ rowsScanned: state.dataRows,
1065
+ rows: state.rows,
1066
+ notes,
1067
+ };
1068
+ }
1069
+
1070
+ export interface CsvValidateOptions {
1071
+ format?: "csv" | "tsv";
1072
+ delimiter?: string;
1073
+ header?: CsvHeaderOption;
1074
+ maxRows?: number | null;
1075
+ signal?: AbortSignal | undefined;
1076
+ }
1077
+
1078
+ export interface CsvValidateResult {
1079
+ ok: true;
1080
+ format: "csv" | "tsv";
1081
+ path: string;
1082
+ bytes: number;
1083
+ bytesScanned: number;
1084
+ view: DataViewFlags;
1085
+ /** True when every scanned row is well formed and the scan reached the end; null when it stopped at maxRows without a fault. */
1086
+ valid: boolean | null;
1087
+ complete: boolean;
1088
+ rowsScanned: number;
1089
+ rowCount: number | null;
1090
+ delimiter: string;
1091
+ fieldCount: number;
1092
+ header: string[] | null;
1093
+ raggedRows: { count: number; first: CsvRaggedRow[] };
1094
+ quotingIssues: { count: number; first: CsvIssue[] };
1095
+ blankLines: number;
1096
+ notes: string[];
1097
+ }
1098
+
1099
+ export async function validateCsv(
1100
+ path: string,
1101
+ options: CsvValidateOptions = {},
1102
+ ): Promise<CsvValidateResult | DataRefusal> {
1103
+ const stat = statDataFile(path);
1104
+ if ("ok" in stat) return stat;
1105
+ const format = options.format ?? "csv";
1106
+ const explicitDelimiter = resolveDelimiter(options.delimiter, format);
1107
+ if (explicitDelimiter instanceof Error) return refusal("invalid-argument", explicitDelimiter.message, { path });
1108
+ const maxRows = resolveMaxRows(options.maxRows, CSV_DEFAULT_MAX_ROWS);
1109
+ const stats: TextStreamStats = { bytesRead: 0, bom: false };
1110
+ const headers = new HeaderResolver(options.header ?? "auto");
1111
+ const state = {
1112
+ dataRows: 0,
1113
+ stoppedAtRows: false,
1114
+ raggedCount: 0,
1115
+ raggedFirst: [] as CsvRaggedRow[],
1116
+ issueCount: 0,
1117
+ issueFirst: [] as CsvIssue[],
1118
+ };
1119
+
1120
+ const consumeDataRow = (fields: string[], line: number): boolean => {
1121
+ if (state.dataRows >= maxRows) {
1122
+ state.stoppedAtRows = true;
1123
+ return false;
1124
+ }
1125
+ state.dataRows += 1;
1126
+ if (fields.length !== headers.fieldCount) {
1127
+ state.raggedCount += 1;
1128
+ if (state.raggedFirst.length < FIRST_ISSUES_CAP) {
1129
+ state.raggedFirst.push({ row: state.dataRows, line, fields: fields.length });
1130
+ }
1131
+ }
1132
+ return true;
1133
+ };
1134
+
1135
+ const callbacks: CsvParserCallbacks = {
1136
+ onRecord(fields, _record, line) {
1137
+ for (const row of headers.feed(fields, line)) {
1138
+ if (!consumeDataRow(row.fields, row.line)) return false;
1139
+ }
1140
+ return true;
1141
+ },
1142
+ onIssue(issue) {
1143
+ state.issueCount += 1;
1144
+ if (state.issueFirst.length < FIRST_ISSUES_CAP) state.issueFirst.push(issue);
1145
+ },
1146
+ };
1147
+
1148
+ let scan: CsvScan;
1149
+ try {
1150
+ scan = await scanCsv(path, explicitDelimiter, options.signal, stats, callbacks);
1151
+ } catch (error) {
1152
+ return refusalFromError(error, path, options.signal);
1153
+ }
1154
+ if (!scan.parser.isStopped) {
1155
+ for (const row of headers.finish()) consumeDataRow(row.fields, row.line);
1156
+ }
1157
+ const complete = !state.stoppedAtRows;
1158
+ const faults = state.raggedCount + state.issueCount;
1159
+ const notes: string[] = [];
1160
+ if (stats.bom) notes.push("a UTF-8 byte-order mark was stripped");
1161
+ if (!complete) {
1162
+ notes.push(
1163
+ `validation incomplete: stopped after ${state.dataRows} rows; pass maxRows null to validate the whole file`,
1164
+ );
1165
+ }
1166
+ if (state.raggedCount > 0) {
1167
+ notes.push(`${state.raggedCount} row(s) have a field count other than ${headers.fieldCount}`);
1168
+ }
1169
+ if (state.issueCount > 0) notes.push(`${state.issueCount} quoting issue(s)`);
1170
+ return {
1171
+ ok: true,
1172
+ format,
1173
+ path,
1174
+ bytes: stat.size,
1175
+ bytesScanned: stats.bytesRead,
1176
+ view: complete ? exactView() : sampledView(),
1177
+ valid: faults > 0 ? false : complete ? true : null,
1178
+ complete,
1179
+ rowsScanned: state.dataRows,
1180
+ rowCount: complete ? state.dataRows : null,
1181
+ delimiter: scan.delimiter,
1182
+ fieldCount: headers.fieldCount,
1183
+ header: headers.header,
1184
+ raggedRows: { count: state.raggedCount, first: state.raggedFirst },
1185
+ quotingIssues: { count: state.issueCount, first: state.issueFirst },
1186
+ blankLines: scan.parser.blankLines,
1187
+ notes,
1188
+ };
1189
+ }