@youweichen/pi-harness 0.0.0-stage → 1.0.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 (455) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -2
  3. package/bin/pi-harness.mjs +1819 -0
  4. package/deploy/com.youweichen.pi-harness.plist +48 -0
  5. package/deploy/nginx-subpath.conf +88 -0
  6. package/deploy/pi-harness-task.xml +71 -0
  7. package/deploy/pi-harness.service +31 -0
  8. package/dist/server/agent-service.js +3051 -0
  9. package/dist/server/app-version.js +19 -0
  10. package/dist/server/attachments.js +526 -0
  11. package/dist/server/auth-file.js +61 -0
  12. package/dist/server/bg-servers.js +154 -0
  13. package/dist/server/client-eviction.js +19 -0
  14. package/dist/server/client-id.js +9 -0
  15. package/dist/server/client-state.js +202 -0
  16. package/dist/server/codemode-image-routes.js +36 -0
  17. package/dist/server/codemode-presentation.js +19 -0
  18. package/dist/server/component-updates.js +135 -0
  19. package/dist/server/context-breakdown.js +56 -0
  20. package/dist/server/control-socket.js +177 -0
  21. package/dist/server/conversation.js +65 -0
  22. package/dist/server/dir-browser.js +77 -0
  23. package/dist/server/editor-snapshot.js +38 -0
  24. package/dist/server/ensure-bash.js +80 -0
  25. package/dist/server/extension-display.js +23 -0
  26. package/dist/server/extensions-catalog.js +64 -0
  27. package/dist/server/extensions-model.js +46 -0
  28. package/dist/server/extensions-routes.js +255 -0
  29. package/dist/server/extensions-worker.js +187 -0
  30. package/dist/server/files-service.js +795 -0
  31. package/dist/server/index.js +1029 -0
  32. package/dist/server/markdown-images.js +42 -0
  33. package/dist/server/model-admin.js +801 -0
  34. package/dist/server/model-config-merge.js +32 -0
  35. package/dist/server/native-mcp-config.js +103 -0
  36. package/dist/server/native-mcp-presentation.js +12 -0
  37. package/dist/server/native-prompt-template.js +12 -0
  38. package/dist/server/native-tools.js +32 -0
  39. package/dist/server/node-sources.js +142 -0
  40. package/dist/server/node-workbench.js +668 -0
  41. package/dist/server/origin-policy.js +50 -0
  42. package/dist/server/patch-node-pty.js +96 -0
  43. package/dist/server/pi-environment.js +203 -0
  44. package/dist/server/plan/extension.js +74 -0
  45. package/dist/server/plan/progress.js +90 -0
  46. package/dist/server/plan/settings.js +41 -0
  47. package/dist/server/plan/state.js +113 -0
  48. package/dist/server/plugin-facilities.js +304 -0
  49. package/dist/server/plugin-updater.js +245 -0
  50. package/dist/server/plugins.js +712 -0
  51. package/dist/server/private-file.js +28 -0
  52. package/dist/server/process-utils.js +112 -0
  53. package/dist/server/prompt-delivery.js +80 -0
  54. package/dist/server/protocol-version.js +11 -0
  55. package/dist/server/protocol.js +6 -0
  56. package/dist/server/provider-auth.js +99 -0
  57. package/dist/server/query-cache.js +45 -0
  58. package/dist/server/recovery-state.js +37 -0
  59. package/dist/server/scm.js +459 -0
  60. package/dist/server/security-headers.js +24 -0
  61. package/dist/server/serialize.js +254 -0
  62. package/dist/server/session-file-read.js +225 -0
  63. package/dist/server/session-record-worker.js +21 -0
  64. package/dist/server/session-reload.js +13 -0
  65. package/dist/server/session-tree-controller.js +250 -0
  66. package/dist/server/session-tree.js +191 -0
  67. package/dist/server/settings-service.js +53 -0
  68. package/dist/server/skills-service.js +87 -0
  69. package/dist/server/slash-commands.js +280 -0
  70. package/dist/server/sqlite-preview.js +58 -0
  71. package/dist/server/sqlite-query.js +67 -0
  72. package/dist/server/sqlite-worker.js +12 -0
  73. package/dist/server/system-prompt-files.js +158 -0
  74. package/dist/server/system-prompt-routes.js +40 -0
  75. package/dist/server/system-prompt-view.js +49 -0
  76. package/dist/server/task-progress.js +233 -0
  77. package/dist/server/terminals.js +833 -0
  78. package/dist/server/text-sniff.js +269 -0
  79. package/dist/server/thinking-timing.js +91 -0
  80. package/dist/server/token-auth.js +37 -0
  81. package/dist/server/tool-output.js +97 -0
  82. package/dist/server/tool-text-incidents.js +82 -0
  83. package/dist/server/tool-text.js +67 -0
  84. package/dist/server/uploads.js +112 -0
  85. package/dist/server/user-attachments.js +96 -0
  86. package/dist/server/webui-context.js +257 -0
  87. package/dist/server/wiki-links.js +136 -0
  88. package/dist/server/wiki-pdf-worker.js +23 -0
  89. package/dist/server/wiki-pdf.js +36 -0
  90. package/dist/server/wiki-routes.js +177 -0
  91. package/dist/server/wiki-service.js +511 -0
  92. package/electron/main.mjs +607 -0
  93. package/extensions/webui.ts +192 -0
  94. package/package.json +122 -4
  95. package/web/dist/apple-touch-icon.png +0 -0
  96. package/web/dist/assets/NodeWorkbench-C4U1PldE.js +3 -0
  97. package/web/dist/assets/TerminalPanel-CWh6vw_N.js +2 -0
  98. package/web/dist/assets/abnfDiagram-VCTEODGH-D5Ti0Kpa.js +1 -0
  99. package/web/dist/assets/arc-DKJilLd6.js +1 -0
  100. package/web/dist/assets/architectureDiagram-5GKGNRK7-Cf0w38g-.js +36 -0
  101. package/web/dist/assets/blockDiagram-I7D4REHJ-BOTLWLIL.js +129 -0
  102. package/web/dist/assets/c4Diagram-7LVT6UL2-CLCCoJs3.js +38 -0
  103. package/web/dist/assets/channel-D8Hd7T4P.js +1 -0
  104. package/web/dist/assets/chunk-2Q5K7J3B-BHjga2Ge.js +1 -0
  105. package/web/dist/assets/chunk-5VM5RSS4-CQJ5VfDZ.js +15 -0
  106. package/web/dist/assets/chunk-F27PBJKO-C67qoPJH.js +1 -0
  107. package/web/dist/assets/chunk-IMKFNOWR-CgXXGwI8.js +231 -0
  108. package/web/dist/assets/chunk-JWPE2WC7-D0RPyByP.js +1 -0
  109. package/web/dist/assets/chunk-POPQ4Y6H-BeYC9s9x.js +1 -0
  110. package/web/dist/assets/chunk-SVP7TREG-eQPl4UBQ.js +88 -0
  111. package/web/dist/assets/chunk-TICWLB2K-DT73arPU.js +206 -0
  112. package/web/dist/assets/chunk-XXDRQBXY-B4FwQcP-.js +1 -0
  113. package/web/dist/assets/classDiagram-ZZMXUADV-B_btgYRB.js +1 -0
  114. package/web/dist/assets/classDiagram-v2-VYDZK3BY-B_btgYRB.js +1 -0
  115. package/web/dist/assets/cose-bilkent-JH36ORCC-Bnn5nlQN.js +1 -0
  116. package/web/dist/assets/cynefin-OW5HDTMX-CStWX9kE.js +178 -0
  117. package/web/dist/assets/cynefinDiagram-5FMLGOSQ-BMVHC03f.js +62 -0
  118. package/web/dist/assets/cytoscape.esm-Bch-eiPH.js +331 -0
  119. package/web/dist/assets/dagre-GXQ25YYZ-ClSaO4ml.js +4 -0
  120. package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  121. package/web/dist/assets/diagram-S7CK7UJ4-C43LL08C.js +30 -0
  122. package/web/dist/assets/diagram-UQ7AKVKN-BPVn_ySW.js +41 -0
  123. package/web/dist/assets/diagram-VSXAHHWV-C34FFhRi.js +3 -0
  124. package/web/dist/assets/diagram-VX7I27RA-BRk4bncf.js +24 -0
  125. package/web/dist/assets/diagram-Z3DM3KII-O5hYPLEd.js +24 -0
  126. package/web/dist/assets/ebnfDiagram-PWID7BFC-DVGEBm99.js +1 -0
  127. package/web/dist/assets/erDiagram-RLTQ6QDP-M-BLpTEV.js +99 -0
  128. package/web/dist/assets/flowDiagram-HODETNUW-BwdLopEx.js +168 -0
  129. package/web/dist/assets/ganttDiagram-EL5Y4UJY-DYxx2ppA.js +292 -0
  130. package/web/dist/assets/gitGraphDiagram-WWUBYQGX-DUU0wMYB.js +106 -0
  131. package/web/dist/assets/index-CI2KfVSr.css +10 -0
  132. package/web/dist/assets/index-DQcpggFU.js +275 -0
  133. package/web/dist/assets/infoDiagram-27XIBGKW-CFaW5C0C.js +2 -0
  134. package/web/dist/assets/init-Gi6I4Gst.js +1 -0
  135. package/web/dist/assets/ishikawaDiagram-5VMMS53U--ENP1MTB.js +70 -0
  136. package/web/dist/assets/journeyDiagram-3NMN7TZE-DgnAzSTj.js +139 -0
  137. package/web/dist/assets/kanban-definition-UXKFOSKX-_STZN1vU.js +89 -0
  138. package/web/dist/assets/katex-C5jXJg4s.js +257 -0
  139. package/web/dist/assets/layout-CkUClkXt.js +1 -0
  140. package/web/dist/assets/linear-sYHiUOxa.js +1 -0
  141. package/web/dist/assets/markdown-vNTCZJ6d.js +51 -0
  142. package/web/dist/assets/mermaid.core-B8fKGWsc.js +312 -0
  143. package/web/dist/assets/mindmap-definition-YA3MSWOX-DP0BYOEx.js +96 -0
  144. package/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  145. package/web/dist/assets/pegDiagram-XKGWAZYB-DkWTmZre.js +1 -0
  146. package/web/dist/assets/pieDiagram-E7YTZNPT-DfZgMV4u.js +39 -0
  147. package/web/dist/assets/quadrantDiagram-AXDQQJYC-Djl4TT7q.js +7 -0
  148. package/web/dist/assets/railroadDiagram-O6MQD6OU-DbKF6zXn.js +1 -0
  149. package/web/dist/assets/react-BNj3n7jX.js +24 -0
  150. package/web/dist/assets/requirementDiagram-BXWQKSXE-idola5qb.js +84 -0
  151. package/web/dist/assets/sankeyDiagram-P5KCCOFB-KA0hQOle.js +40 -0
  152. package/web/dist/assets/sequenceDiagram-WJ2MYXX4-BXSKvxzP.js +162 -0
  153. package/web/dist/assets/sizeCapture-INFHLROL-C_Ewv3S5.js +1 -0
  154. package/web/dist/assets/stateDiagram-D77RDMKH-DIf5X8oo.js +1 -0
  155. package/web/dist/assets/stateDiagram-v2-MP3YSRHH-DYwe_2mB.js +1 -0
  156. package/web/dist/assets/swimlanes-42K2YHIH-Cx_Rc8M2.js +1 -0
  157. package/web/dist/assets/swimlanesDiagram-VR7AAH4N-52Y8tgRV.js +8 -0
  158. package/web/dist/assets/timeline-definition-24CTP7MA-BRrDhWVz.js +120 -0
  159. package/web/dist/assets/vennDiagram-4TSXK5OY-Ad6G0YwD.js +34 -0
  160. package/web/dist/assets/wardleyDiagram-VM6X3IG4-X6-ENpSi.js +78 -0
  161. package/web/dist/assets/xterm-6GBZ9nXN.css +32 -0
  162. package/web/dist/assets/xterm-D1D2FVe3.js +38 -0
  163. package/web/dist/assets/xychartDiagram-S5SC5T6Z-DhegNS6d.js +7 -0
  164. package/web/dist/brand-mark.svg +5 -0
  165. package/web/dist/favicon-32.png +0 -0
  166. package/web/dist/favicon.svg +5 -0
  167. package/web/dist/fonts/WIKI-FONTS.md +5 -0
  168. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  169. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  170. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  171. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  172. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  173. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-600-normal.woff2 +0 -0
  174. package/web/dist/fonts/ibm-plex-sans/LICENSE +93 -0
  175. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  176. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  177. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  178. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
  179. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
  180. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
  181. package/web/dist/fonts/inter/inter-latin-400-normal.woff2 +0 -0
  182. package/web/dist/fonts/inter/inter-latin-500-normal.woff2 +0 -0
  183. package/web/dist/fonts/inter/inter-latin-600-normal.woff2 +0 -0
  184. package/web/dist/fonts/inter/inter-latin-700-normal.woff2 +0 -0
  185. package/web/dist/fonts/inter/inter-latin-800-normal.woff2 +0 -0
  186. package/web/dist/fonts/inter/inter-latin-ext-400-normal.woff2 +0 -0
  187. package/web/dist/fonts/inter/inter-latin-ext-500-normal.woff2 +0 -0
  188. package/web/dist/fonts/inter/inter-latin-ext-600-normal.woff2 +0 -0
  189. package/web/dist/fonts/inter/inter-latin-ext-700-normal.woff2 +0 -0
  190. package/web/dist/fonts/inter/inter-latin-ext-800-normal.woff2 +0 -0
  191. package/web/dist/fonts/jetbrains-mono/LICENSE +93 -0
  192. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-400-normal.woff2 +0 -0
  193. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-500-normal.woff2 +0 -0
  194. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-600-normal.woff2 +0 -0
  195. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
  196. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
  197. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-600-normal.woff2 +0 -0
  198. package/web/dist/fonts/noto-serif-sc/LICENSE +94 -0
  199. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-0.woff2 +0 -0
  200. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-1.woff2 +0 -0
  201. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-10.woff2 +0 -0
  202. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-100.woff2 +0 -0
  203. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-11.woff2 +0 -0
  204. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-12.woff2 +0 -0
  205. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-13.woff2 +0 -0
  206. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-14.woff2 +0 -0
  207. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-15.woff2 +0 -0
  208. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-16.woff2 +0 -0
  209. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-17.woff2 +0 -0
  210. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-18.woff2 +0 -0
  211. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-19.woff2 +0 -0
  212. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-2.woff2 +0 -0
  213. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-20.woff2 +0 -0
  214. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-21.woff2 +0 -0
  215. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-22.woff2 +0 -0
  216. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-23.woff2 +0 -0
  217. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-24.woff2 +0 -0
  218. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-25.woff2 +0 -0
  219. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-26.woff2 +0 -0
  220. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-27.woff2 +0 -0
  221. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-28.woff2 +0 -0
  222. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-29.woff2 +0 -0
  223. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-3.woff2 +0 -0
  224. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-30.woff2 +0 -0
  225. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-31.woff2 +0 -0
  226. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-32.woff2 +0 -0
  227. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-33.woff2 +0 -0
  228. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-34.woff2 +0 -0
  229. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-35.woff2 +0 -0
  230. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-36.woff2 +0 -0
  231. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-37.woff2 +0 -0
  232. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-38.woff2 +0 -0
  233. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-39.woff2 +0 -0
  234. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-4.woff2 +0 -0
  235. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-40.woff2 +0 -0
  236. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-41.woff2 +0 -0
  237. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-42.woff2 +0 -0
  238. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-43.woff2 +0 -0
  239. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-44.woff2 +0 -0
  240. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-45.woff2 +0 -0
  241. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-46.woff2 +0 -0
  242. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-47.woff2 +0 -0
  243. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-48.woff2 +0 -0
  244. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-49.woff2 +0 -0
  245. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-5.woff2 +0 -0
  246. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-50.woff2 +0 -0
  247. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-51.woff2 +0 -0
  248. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-52.woff2 +0 -0
  249. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-53.woff2 +0 -0
  250. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-54.woff2 +0 -0
  251. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-55.woff2 +0 -0
  252. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-56.woff2 +0 -0
  253. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-57.woff2 +0 -0
  254. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-58.woff2 +0 -0
  255. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-59.woff2 +0 -0
  256. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-6.woff2 +0 -0
  257. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-60.woff2 +0 -0
  258. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-61.woff2 +0 -0
  259. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-62.woff2 +0 -0
  260. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-63.woff2 +0 -0
  261. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-64.woff2 +0 -0
  262. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-65.woff2 +0 -0
  263. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-66.woff2 +0 -0
  264. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-67.woff2 +0 -0
  265. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-68.woff2 +0 -0
  266. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-69.woff2 +0 -0
  267. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-7.woff2 +0 -0
  268. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-70.woff2 +0 -0
  269. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-71.woff2 +0 -0
  270. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-72.woff2 +0 -0
  271. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-73.woff2 +0 -0
  272. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-74.woff2 +0 -0
  273. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-75.woff2 +0 -0
  274. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-76.woff2 +0 -0
  275. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-77.woff2 +0 -0
  276. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-78.woff2 +0 -0
  277. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-79.woff2 +0 -0
  278. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-8.woff2 +0 -0
  279. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-80.woff2 +0 -0
  280. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-81.woff2 +0 -0
  281. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-82.woff2 +0 -0
  282. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-83.woff2 +0 -0
  283. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-84.woff2 +0 -0
  284. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-85.woff2 +0 -0
  285. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-86.woff2 +0 -0
  286. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-87.woff2 +0 -0
  287. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-88.woff2 +0 -0
  288. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-89.woff2 +0 -0
  289. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-9.woff2 +0 -0
  290. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-90.woff2 +0 -0
  291. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-91.woff2 +0 -0
  292. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-92.woff2 +0 -0
  293. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-93.woff2 +0 -0
  294. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-94.woff2 +0 -0
  295. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-95.woff2 +0 -0
  296. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-96.woff2 +0 -0
  297. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-97.woff2 +0 -0
  298. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-98.woff2 +0 -0
  299. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-99.woff2 +0 -0
  300. package/web/dist/fonts/source-serif-4/LICENSE +93 -0
  301. package/web/dist/fonts/source-serif-4/source-serif-4-500-0.woff2 +0 -0
  302. package/web/dist/fonts/source-serif-4/source-serif-4-500-1.woff2 +0 -0
  303. package/web/dist/fonts/source-serif-4/source-serif-4-500-2.woff2 +0 -0
  304. package/web/dist/fonts/source-serif-4/source-serif-4-500-3.woff2 +0 -0
  305. package/web/dist/fonts/source-serif-4/source-serif-4-500-4.woff2 +0 -0
  306. package/web/dist/fonts/source-serif-4/source-serif-4-500-5.woff2 +0 -0
  307. package/web/dist/icon/1a-mark.svg +1 -0
  308. package/web/dist/icon.ico +0 -0
  309. package/web/dist/index.html +18 -0
  310. package/web/public/apple-touch-icon.png +0 -0
  311. package/web/public/brand-mark.svg +5 -0
  312. package/web/public/favicon-32.png +0 -0
  313. package/web/public/favicon.svg +5 -0
  314. package/web/public/fonts/WIKI-FONTS.md +5 -0
  315. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  316. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  317. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  318. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  319. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  320. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-600-normal.woff2 +0 -0
  321. package/web/public/fonts/ibm-plex-sans/LICENSE +93 -0
  322. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  323. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  324. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  325. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
  326. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
  327. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
  328. package/web/public/fonts/inter/inter-latin-400-normal.woff2 +0 -0
  329. package/web/public/fonts/inter/inter-latin-500-normal.woff2 +0 -0
  330. package/web/public/fonts/inter/inter-latin-600-normal.woff2 +0 -0
  331. package/web/public/fonts/inter/inter-latin-700-normal.woff2 +0 -0
  332. package/web/public/fonts/inter/inter-latin-800-normal.woff2 +0 -0
  333. package/web/public/fonts/inter/inter-latin-ext-400-normal.woff2 +0 -0
  334. package/web/public/fonts/inter/inter-latin-ext-500-normal.woff2 +0 -0
  335. package/web/public/fonts/inter/inter-latin-ext-600-normal.woff2 +0 -0
  336. package/web/public/fonts/inter/inter-latin-ext-700-normal.woff2 +0 -0
  337. package/web/public/fonts/inter/inter-latin-ext-800-normal.woff2 +0 -0
  338. package/web/public/fonts/jetbrains-mono/LICENSE +93 -0
  339. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-400-normal.woff2 +0 -0
  340. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-500-normal.woff2 +0 -0
  341. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-600-normal.woff2 +0 -0
  342. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
  343. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
  344. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-600-normal.woff2 +0 -0
  345. package/web/public/fonts/noto-serif-sc/LICENSE +94 -0
  346. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-0.woff2 +0 -0
  347. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-1.woff2 +0 -0
  348. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-10.woff2 +0 -0
  349. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-100.woff2 +0 -0
  350. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-11.woff2 +0 -0
  351. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-12.woff2 +0 -0
  352. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-13.woff2 +0 -0
  353. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-14.woff2 +0 -0
  354. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-15.woff2 +0 -0
  355. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-16.woff2 +0 -0
  356. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-17.woff2 +0 -0
  357. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-18.woff2 +0 -0
  358. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-19.woff2 +0 -0
  359. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-2.woff2 +0 -0
  360. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-20.woff2 +0 -0
  361. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-21.woff2 +0 -0
  362. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-22.woff2 +0 -0
  363. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-23.woff2 +0 -0
  364. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-24.woff2 +0 -0
  365. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-25.woff2 +0 -0
  366. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-26.woff2 +0 -0
  367. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-27.woff2 +0 -0
  368. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-28.woff2 +0 -0
  369. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-29.woff2 +0 -0
  370. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-3.woff2 +0 -0
  371. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-30.woff2 +0 -0
  372. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-31.woff2 +0 -0
  373. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-32.woff2 +0 -0
  374. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-33.woff2 +0 -0
  375. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-34.woff2 +0 -0
  376. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-35.woff2 +0 -0
  377. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-36.woff2 +0 -0
  378. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-37.woff2 +0 -0
  379. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-38.woff2 +0 -0
  380. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-39.woff2 +0 -0
  381. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-4.woff2 +0 -0
  382. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-40.woff2 +0 -0
  383. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-41.woff2 +0 -0
  384. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-42.woff2 +0 -0
  385. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-43.woff2 +0 -0
  386. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-44.woff2 +0 -0
  387. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-45.woff2 +0 -0
  388. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-46.woff2 +0 -0
  389. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-47.woff2 +0 -0
  390. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-48.woff2 +0 -0
  391. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-49.woff2 +0 -0
  392. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-5.woff2 +0 -0
  393. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-50.woff2 +0 -0
  394. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-51.woff2 +0 -0
  395. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-52.woff2 +0 -0
  396. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-53.woff2 +0 -0
  397. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-54.woff2 +0 -0
  398. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-55.woff2 +0 -0
  399. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-56.woff2 +0 -0
  400. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-57.woff2 +0 -0
  401. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-58.woff2 +0 -0
  402. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-59.woff2 +0 -0
  403. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-6.woff2 +0 -0
  404. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-60.woff2 +0 -0
  405. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-61.woff2 +0 -0
  406. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-62.woff2 +0 -0
  407. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-63.woff2 +0 -0
  408. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-64.woff2 +0 -0
  409. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-65.woff2 +0 -0
  410. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-66.woff2 +0 -0
  411. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-67.woff2 +0 -0
  412. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-68.woff2 +0 -0
  413. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-69.woff2 +0 -0
  414. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-7.woff2 +0 -0
  415. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-70.woff2 +0 -0
  416. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-71.woff2 +0 -0
  417. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-72.woff2 +0 -0
  418. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-73.woff2 +0 -0
  419. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-74.woff2 +0 -0
  420. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-75.woff2 +0 -0
  421. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-76.woff2 +0 -0
  422. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-77.woff2 +0 -0
  423. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-78.woff2 +0 -0
  424. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-79.woff2 +0 -0
  425. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-8.woff2 +0 -0
  426. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-80.woff2 +0 -0
  427. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-81.woff2 +0 -0
  428. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-82.woff2 +0 -0
  429. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-83.woff2 +0 -0
  430. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-84.woff2 +0 -0
  431. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-85.woff2 +0 -0
  432. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-86.woff2 +0 -0
  433. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-87.woff2 +0 -0
  434. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-88.woff2 +0 -0
  435. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-89.woff2 +0 -0
  436. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-9.woff2 +0 -0
  437. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-90.woff2 +0 -0
  438. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-91.woff2 +0 -0
  439. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-92.woff2 +0 -0
  440. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-93.woff2 +0 -0
  441. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-94.woff2 +0 -0
  442. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-95.woff2 +0 -0
  443. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-96.woff2 +0 -0
  444. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-97.woff2 +0 -0
  445. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-98.woff2 +0 -0
  446. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-99.woff2 +0 -0
  447. package/web/public/fonts/source-serif-4/LICENSE +93 -0
  448. package/web/public/fonts/source-serif-4/source-serif-4-500-0.woff2 +0 -0
  449. package/web/public/fonts/source-serif-4/source-serif-4-500-1.woff2 +0 -0
  450. package/web/public/fonts/source-serif-4/source-serif-4-500-2.woff2 +0 -0
  451. package/web/public/fonts/source-serif-4/source-serif-4-500-3.woff2 +0 -0
  452. package/web/public/fonts/source-serif-4/source-serif-4-500-4.woff2 +0 -0
  453. package/web/public/fonts/source-serif-4/source-serif-4-500-5.woff2 +0 -0
  454. package/web/public/icon/1a-mark.svg +1 -0
  455. package/web/public/icon.ico +0 -0
@@ -0,0 +1,511 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { readdir, readFile, stat } from "node:fs/promises";
3
+ import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
+ import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
5
+ import { decodeText, looksLikeText, previewKind } from "./text-sniff.js";
6
+ import { wikiMetadata, wikiReferences, resolveWikiLink, wikiLinkIndex } from "./wiki-links.js";
7
+ import { readWikiPdf } from "./wiki-pdf.js";
8
+ const MAX_FILE = 2 * 1024 * 1024;
9
+ const MAX_SNAPSHOT = 64 * 1024 * 1024;
10
+ const IGNORED = new Set([".git", "node_modules", ".pi-web", ".DS_Store"]);
11
+ // Generated dependencies and duplicate checkouts must not consume the main
12
+ // workspace's index or undo budget. Explicit directory/file requests stay usable.
13
+ const TRAVERSAL_IGNORED_DIRS = new Set([".venv", "__pycache__", ".pytest_cache", ".mypy_cache", ".ruff_cache"]);
14
+ function skipTraversalDirectory(parent, name) {
15
+ return TRAVERSAL_IGNORED_DIRS.has(name) || (name === "worktrees" && basename(parent) === ".claude");
16
+ }
17
+ const hash = (data) => createHash("sha256").update(data).digest("hex");
18
+ export class WikiConflictError extends Error {
19
+ }
20
+ /** Resolve real paths, including every existing parent of a new file. */
21
+ export function wikiPath(cwd, path) {
22
+ if (typeof path !== "string" || path.includes("\0") || path.length > 4096)
23
+ throw new Error("Invalid path");
24
+ const root = realpathSync(cwd), absolute = resolve(root, path);
25
+ const inside = (p) => { const r = relative(root, p); return r !== ".." && !r.startsWith(`..${sep}`) && !/^[A-Za-z]:/.test(r) && !r.startsWith(sep); };
26
+ if (!inside(absolute))
27
+ throw new Error("Path outside workspace");
28
+ let p = absolute;
29
+ while (p !== root) {
30
+ if (existsSync(p)) {
31
+ if (!inside(realpathSync(p)))
32
+ throw new Error("Path outside workspace");
33
+ break;
34
+ }
35
+ // A dangling symlink must never be treated as an absent regular file.
36
+ try {
37
+ if (lstatSync(p).isSymbolicLink())
38
+ throw new Error("Invalid symbolic link");
39
+ }
40
+ catch (e) {
41
+ if (e.code !== "ENOENT")
42
+ throw e;
43
+ }
44
+ p = dirname(p);
45
+ }
46
+ return absolute;
47
+ }
48
+ function kindOf(path, text) {
49
+ if (/\.(md|markdown|txt)$/i.test(path))
50
+ return "document";
51
+ if (/\.pdf$/i.test(path))
52
+ return "pdf";
53
+ if (previewKind(path) === "image")
54
+ return "image";
55
+ return text || previewKind(path) === "text" ? "code" : "other";
56
+ }
57
+ export class WikiService {
58
+ dataDir;
59
+ active = new Set();
60
+ cache = new Map();
61
+ scans = new Map();
62
+ generations = new Map();
63
+ /** Markdown parse results by content hash. The index is rebuilt after every save and
64
+ * Wiki request; only changed documents are parsed again. */
65
+ parsed = new Map();
66
+ constructor(dataDir) {
67
+ this.dataDir = dataDir;
68
+ mkdirSync(dataDir, { recursive: true });
69
+ }
70
+ key(cwd) { return hash(realpathSync(cwd)); }
71
+ folder(cwd) { const dir = join(this.dataDir, this.key(cwd)); mkdirSync(join(dir, "blobs"), { recursive: true }); return dir; }
72
+ busy(cwd) { return this.active.has(this.key(cwd)); }
73
+ invalidate(cwd) { const key = this.key(cwd); this.cache.delete(key); this.generations.set(key, (this.generations.get(key) ?? 0) + 1); }
74
+ history(cwd) {
75
+ const file = join(this.folder(cwd), "history.json");
76
+ return existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : [];
77
+ }
78
+ persist(cwd, records) {
79
+ records = records.slice(-100);
80
+ let total = 0;
81
+ for (let i = records.length - 1; i >= 0; i--) {
82
+ const bytes = Object.values(records[i].blobs).flatMap(b => [b.before, b.after]).reduce((sum, id) => sum + (id ? statSync(join(this.folder(cwd), "blobs", id)).size : 0), 0);
83
+ if (i < records.length - 1 && total + bytes > 128 * 1024 * 1024) {
84
+ records = records.slice(i + 1);
85
+ break;
86
+ }
87
+ total += bytes;
88
+ }
89
+ const file = join(this.folder(cwd), "history.json"), temporary = file + ".tmp";
90
+ writeFileSync(temporary, JSON.stringify(records.slice(-100)), { mode: 0o600 });
91
+ renameSync(temporary, file);
92
+ // Retain only blobs referenced by the latest 100 requests.
93
+ const retained = new Set(records.slice(-100).flatMap(r => Object.values(r.blobs).flatMap(b => [b.before, b.after]).filter(Boolean)));
94
+ for (const name of readdirSync(join(this.folder(cwd), "blobs")))
95
+ if (!retained.has(name))
96
+ rmSync(join(this.folder(cwd), "blobs", name), { force: true });
97
+ }
98
+ async index(cwd, fresh = false) {
99
+ const key = this.key(cwd), cached = this.cache.get(key);
100
+ if (!fresh && cached) {
101
+ if (Date.now() - cached.at >= 30000)
102
+ void this.scan(cwd).catch(() => { });
103
+ return cached;
104
+ }
105
+ if (!fresh && this.scans.has(key)) {
106
+ const result = await this.scans.get(key);
107
+ return this.cache.has(key) ? result : this.scan(cwd);
108
+ }
109
+ // Forced snapshots must observe changes after any already running scan.
110
+ if (fresh && this.scans.has(key))
111
+ await this.scans.get(key);
112
+ return this.scan(cwd);
113
+ }
114
+ scan(cwd) {
115
+ const key = this.key(cwd), pending = this.scans.get(key);
116
+ if (pending)
117
+ return pending;
118
+ const generation = this.generations.get(key) ?? 0;
119
+ const task = this.buildIndex(cwd).then(result => {
120
+ if ((this.generations.get(key) ?? 0) === generation) {
121
+ if (this.cache.size >= 8)
122
+ this.cache.delete(this.cache.keys().next().value);
123
+ this.cache.set(key, result);
124
+ }
125
+ return result;
126
+ }).finally(() => this.scans.delete(key));
127
+ this.scans.set(key, task);
128
+ return task;
129
+ }
130
+ async buildIndex(cwd) {
131
+ const entries = [], texts = new Map();
132
+ const previous = this.parsed.get(this.key(cwd)), parsed = new Map(), documents = new Map();
133
+ const parse = (data, text) => {
134
+ const id = hash(data), known = previous?.get(id) ?? parsed.get(id);
135
+ if (known) {
136
+ parsed.set(id, known);
137
+ return known;
138
+ }
139
+ const metadata = wikiMetadata(text), result = { tags: metadata.tags, title: metadata.title, references: wikiReferences(text) };
140
+ parsed.set(id, result);
141
+ return result;
142
+ };
143
+ let limited = false, bytes = 0, visited = 0;
144
+ const status = { indexed: 0, total: 0, totalIsLowerBound: false, issues: [] };
145
+ const walk = async (path, depth) => {
146
+ if (depth > 32) {
147
+ limited = true;
148
+ status.totalIsLowerBound = true;
149
+ status.issues.push({ path, reason: "depth-limit", subtree: true });
150
+ return;
151
+ }
152
+ let children;
153
+ try {
154
+ children = await readdir(wikiPath(cwd, path), { withFileTypes: true });
155
+ }
156
+ catch {
157
+ limited = true;
158
+ status.totalIsLowerBound = true;
159
+ status.issues.push({ path, reason: "unreadable", subtree: true });
160
+ return;
161
+ }
162
+ children.sort((a, b) => Number(b.isDirectory()) - Number(a.isDirectory()) || a.name.localeCompare(b.name));
163
+ for (const child of children) {
164
+ if (IGNORED.has(child.name) || child.isDirectory() && skipTraversalDirectory(path, child.name))
165
+ continue;
166
+ if (visited >= 20000) {
167
+ limited = true;
168
+ status.totalIsLowerBound = true;
169
+ status.issues.push({ path: path || ".", reason: "entry-limit", subtree: true });
170
+ break;
171
+ }
172
+ visited++;
173
+ const p = path ? `${path}/${child.name}` : child.name;
174
+ let size, counted = false;
175
+ try {
176
+ const abs = wikiPath(cwd, p);
177
+ if (realpathSync(abs) === realpathSync(this.dataDir))
178
+ continue;
179
+ const info = await stat(abs);
180
+ if (info.isDirectory()) {
181
+ entries.push({ path: p, name: child.name, kind: "directory", size: 0, modified: info.mtimeMs, tags: [], symlink: child.isSymbolicLink() });
182
+ if (!child.isSymbolicLink())
183
+ await walk(p, depth + 1);
184
+ continue;
185
+ }
186
+ if (!info.isFile())
187
+ continue;
188
+ status.total++;
189
+ counted = true;
190
+ size = info.size;
191
+ let text, document;
192
+ if (info.size <= MAX_FILE && bytes + info.size <= MAX_SNAPSHOT) {
193
+ const data = await readFile(abs);
194
+ bytes += data.length;
195
+ status.indexed++;
196
+ if (!/\.pdf$/i.test(p) && previewKind(p) !== "image" && looksLikeText(data)) {
197
+ text = decodeText(data);
198
+ texts.set(p, text);
199
+ // Tags, titles and links only exist in documents; parsing source
200
+ // files as Markdown made indexing a code project take minutes.
201
+ if (kindOf(p, true) === "document") {
202
+ document = parse(data, text);
203
+ documents.set(p, document);
204
+ }
205
+ }
206
+ }
207
+ else {
208
+ limited = true;
209
+ status.issues.push({ path: p, size: info.size, reason: info.size > MAX_FILE ? "file-size" : "byte-budget" });
210
+ }
211
+ entries.push({ path: p, name: child.name, kind: kindOf(p, text !== undefined), size: info.size, modified: info.mtimeMs, tags: document?.tags ?? [], title: document?.title });
212
+ }
213
+ catch {
214
+ limited = true;
215
+ if (!counted && !child.isDirectory())
216
+ status.total++;
217
+ if (child.isDirectory())
218
+ status.totalIsLowerBound = true;
219
+ status.issues.push({ path: p, size, reason: "unreadable", subtree: child.isDirectory() });
220
+ }
221
+ }
222
+ };
223
+ await walk("", 0);
224
+ const paths = wikiLinkIndex(entries.map(e => e.path)), references = new Map(), backlinks = new Map();
225
+ this.parsed.delete(this.key(cwd));
226
+ this.parsed.set(this.key(cwd), parsed);
227
+ if (this.parsed.size > 8)
228
+ this.parsed.delete(this.parsed.keys().next().value);
229
+ for (const [source, { references: refs }] of documents) {
230
+ references.set(source, refs);
231
+ for (const ref of refs) {
232
+ const target = resolveWikiLink(source, ref.target, paths);
233
+ if (!target || target === source)
234
+ continue;
235
+ const links = backlinks.get(target) ?? [];
236
+ links.push({ path: source, snippet: ref.snippet, line: ref.line });
237
+ backlinks.set(target, links);
238
+ }
239
+ // Let foreground document requests run between files.
240
+ await new Promise(resolve => setImmediate(resolve));
241
+ }
242
+ return { at: Date.now(), entries, texts, limited, status, references, backlinks };
243
+ }
244
+ async directory(cwd, path, offset = 0) {
245
+ if (!Number.isSafeInteger(offset) || offset < 0 || offset > 1_000_000)
246
+ throw new Error("Invalid directory offset");
247
+ const children = (await readdir(wikiPath(cwd, path), { withFileTypes: true })).filter(e => !IGNORED.has(e.name));
248
+ children.sort((a, b) => Number(b.isDirectory()) - Number(a.isDirectory()) || a.name.localeCompare(b.name));
249
+ const known = new Map(this.cache.get(this.key(cwd))?.entries.map(e => [e.path, e]));
250
+ const entries = [];
251
+ for (const child of children.slice(offset, offset + 500)) {
252
+ const p = path ? `${path}/${child.name}` : child.name;
253
+ try {
254
+ const absolute = wikiPath(cwd, p);
255
+ if (realpathSync(absolute) === realpathSync(this.dataDir))
256
+ continue;
257
+ const info = await stat(absolute);
258
+ entries.push(known.get(p) ?? { path: p, name: child.name, kind: info.isDirectory() ? "directory" : kindOf(p, false), size: info.size, modified: info.mtimeMs, tags: [], symlink: child.isSymbolicLink() });
259
+ }
260
+ catch { /* Unreadable or outside-workspace symlinks are omitted. */ }
261
+ }
262
+ return { path, entries, ...(offset + 500 < children.length ? { nextOffset: offset + 500 } : {}) };
263
+ }
264
+ async state(cwd, fresh = false) {
265
+ const index = await this.index(cwd, fresh), counts = new Map();
266
+ for (const entry of index.entries)
267
+ for (const tag of entry.tags)
268
+ counts.set(tag, (counts.get(tag) ?? 0) + 1);
269
+ return { entries: index.entries, index: index.status, tags: [...counts].map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)), revisions: this.history(cwd).reverse().map(({ blobs: _blobs, ...r }) => r), running: this.busy(cwd), limited: index.limited };
270
+ }
271
+ async document(cwd, path) {
272
+ return { ...await this.documentContent(cwd, path), ...await this.documentReferences(cwd, path) };
273
+ }
274
+ async documentReferences(cwd, path) {
275
+ wikiPath(cwd, path);
276
+ return { backlinks: (await this.index(cwd)).backlinks.get(path) ?? [] };
277
+ }
278
+ async documentContent(cwd, path) {
279
+ const abs = wikiPath(cwd, path), info = statSync(abs), size = info.size;
280
+ if (!info.isFile())
281
+ throw new Error("Not a file");
282
+ const data = size <= MAX_FILE ? readFileSync(abs) : undefined;
283
+ const text = data && !/\.pdf$/i.test(path) && previewKind(path) !== "image" && looksLikeText(data) ? decodeText(data) : undefined;
284
+ const metadata = wikiMetadata(text ?? "");
285
+ return { entry: { path, name: basename(path), size, modified: info.mtimeMs, kind: kindOf(path, text !== undefined), tags: metadata.tags, title: metadata.title }, text, version: data ? hash(data) : "", editable: text !== undefined && Buffer.from(text).equals(data) };
286
+ }
287
+ async search(cwd, query) {
288
+ const index = await this.index(cwd), q = query.trim().toLocaleLowerCase().slice(0, 200);
289
+ if (!q)
290
+ return { results: [], limited: index.limited };
291
+ const results = [];
292
+ let limited = index.limited, pdfs = 0;
293
+ for (const entry of index.entries) {
294
+ if (entry.kind === "directory")
295
+ continue;
296
+ if (results.length >= 100) {
297
+ limited = true;
298
+ break;
299
+ }
300
+ const body = index.texts.get(entry.path);
301
+ const lines = body?.split(/\r?\n/) ?? [];
302
+ let matches = 0;
303
+ for (let line = 0; line < lines.length && matches < 3; line++) {
304
+ const at = lines[line].toLocaleLowerCase().indexOf(q);
305
+ if (at < 0)
306
+ continue;
307
+ results.push({ path: entry.path, kind: entry.kind, line: line + 1, snippet: lines[line].slice(Math.max(0, at - 65), at + q.length + 100) });
308
+ matches++;
309
+ }
310
+ if (!matches && entry.path.toLocaleLowerCase().includes(q)) {
311
+ results.push({ path: entry.path, kind: entry.kind, line: 1, snippet: lines.find(l => l.trim())?.slice(0, 160) ?? entry.name });
312
+ matches++;
313
+ }
314
+ if (entry.kind === "pdf" && !matches) {
315
+ if (++pdfs > 8 || entry.size > 20 * 1024 * 1024) {
316
+ limited = true;
317
+ continue;
318
+ }
319
+ try {
320
+ const pages = await readWikiPdf(wikiPath(cwd, entry.path));
321
+ for (let i = 0; i < pages.length; i++) {
322
+ const at = pages[i].toLocaleLowerCase().indexOf(q);
323
+ if (at >= 0) {
324
+ results.push({ path: entry.path, kind: "pdf", line: 1, page: i + 1, snippet: pages[i].slice(Math.max(0, at - 65), at + q.length + 100) });
325
+ break;
326
+ }
327
+ }
328
+ }
329
+ catch {
330
+ limited = true;
331
+ }
332
+ }
333
+ }
334
+ return { results: results.slice(0, 100), limited };
335
+ }
336
+ async snapshot(cwd) {
337
+ // Undo needs fresh bytes, not Markdown metadata, backlinks or a cached index.
338
+ // Walk independently so an in-flight search/index cannot hold up sending.
339
+ const files = new Map(), skipped = [];
340
+ const dataRoot = realpathSync(this.dataDir);
341
+ let size = 0, visited = 0, limited = false;
342
+ const skip = (path) => { skipped.push(path || "."); limited = true; };
343
+ const walk = async (path, depth) => {
344
+ if (depth > 32) {
345
+ skip(path);
346
+ return;
347
+ }
348
+ let children;
349
+ try {
350
+ children = await readdir(wikiPath(cwd, path), { withFileTypes: true });
351
+ }
352
+ catch {
353
+ skip(path);
354
+ return;
355
+ }
356
+ children.sort((a, b) => Number(b.isDirectory()) - Number(a.isDirectory()) || a.name.localeCompare(b.name));
357
+ for (const child of children) {
358
+ if (IGNORED.has(child.name) || child.isDirectory() && skipTraversalDirectory(path, child.name))
359
+ continue;
360
+ if (visited++ >= 20000) {
361
+ skip(path);
362
+ break;
363
+ }
364
+ const p = path ? `${path}/${child.name}` : child.name;
365
+ try {
366
+ if (child.isSymbolicLink()) {
367
+ skipped.push(p);
368
+ continue;
369
+ }
370
+ const absolute = wikiPath(cwd, p);
371
+ if (realpathSync(absolute) === dataRoot)
372
+ continue;
373
+ const info = lstatSync(absolute);
374
+ if (info.isSymbolicLink()) {
375
+ skipped.push(p);
376
+ continue;
377
+ }
378
+ if (info.isDirectory()) {
379
+ await walk(p, depth + 1);
380
+ continue;
381
+ }
382
+ if (!info.isFile())
383
+ continue;
384
+ if (info.size > MAX_FILE || size + info.size > MAX_SNAPSHOT) {
385
+ skip(p);
386
+ continue;
387
+ }
388
+ const data = await readFile(absolute);
389
+ if (data.length > MAX_FILE || size + data.length > MAX_SNAPSHOT) {
390
+ skip(p);
391
+ continue;
392
+ }
393
+ files.set(p, data);
394
+ size += data.length;
395
+ }
396
+ catch {
397
+ skip(p);
398
+ }
399
+ }
400
+ };
401
+ await walk("", 0);
402
+ return { files, skipped, limited };
403
+ }
404
+ record(cwd, before, after, author, title, attribution = {}) {
405
+ const blobs = {}, changes = [], skipped = [...new Set([...before.skipped, ...after.skipped])];
406
+ const put = (data) => { if (!data)
407
+ return null; const id = hash(data); writeFileSync(join(this.folder(cwd), "blobs", id), data, { mode: 0o600 }); return id; };
408
+ for (const path of new Set([...before.files.keys(), ...after.files.keys()])) {
409
+ if (skipped.includes(path))
410
+ continue;
411
+ const a = before.files.get(path), b = after.files.get(path);
412
+ if ((!a && before.limited) || (!b && existsSync(wikiPath(cwd, path)))) {
413
+ skipped.push(path);
414
+ continue;
415
+ }
416
+ if (a?.equals(b ?? Buffer.alloc(0)) && b !== undefined)
417
+ continue;
418
+ const binary = /\.pdf$/i.test(path) || previewKind(path) === "image" || !!((a && !looksLikeText(a)) || (b && !looksLikeText(b)));
419
+ const old = a ? decodeText(a) : null, next = b ? decodeText(b) : null;
420
+ const oldLines = new Set(old?.split("\n") ?? []), newLines = new Set(next?.split("\n") ?? []);
421
+ changes.push({ path, before: binary ? null : old?.slice(0, 4000) ?? null, after: binary ? null : next?.slice(0, 4000) ?? null, truncated: (old?.length ?? 0) > 4000 || (next?.length ?? 0) > 4000, binary, undone: false, additions: [...newLines].filter(l => !oldLines.has(l)).length, deletions: [...oldLines].filter(l => !newLines.has(l)).length });
422
+ blobs[path] = { before: put(a), after: put(b) };
423
+ }
424
+ if (changes.length || skipped.length)
425
+ this.persist(cwd, [...this.history(cwd), { id: randomUUID(), at: Date.now(), author, ...attribution, title: title.slice(0, 300), changes, blobs, skipped }]);
426
+ this.invalidate(cwd);
427
+ }
428
+ async begin(cwd) {
429
+ const key = this.key(cwd);
430
+ if (this.busy(cwd))
431
+ throw new Error("Wait for the current Wiki request to finish");
432
+ this.active.add(key);
433
+ try {
434
+ return await this.snapshot(cwd);
435
+ }
436
+ catch (e) {
437
+ this.active.delete(key);
438
+ throw e;
439
+ }
440
+ }
441
+ async finish(cwd, before, title, attribution = {}) {
442
+ try {
443
+ this.record(cwd, before, await this.snapshot(cwd), "pi", title, attribution);
444
+ }
445
+ finally {
446
+ this.active.delete(this.key(cwd));
447
+ }
448
+ }
449
+ cancel(cwd) { this.active.delete(this.key(cwd)); }
450
+ write(cwd, path, text, version) {
451
+ if (this.busy(cwd))
452
+ throw new Error("Wait for the current Wiki request to finish");
453
+ const abs = wikiPath(cwd, path), data = readFileSync(abs), next = Buffer.from(text);
454
+ if (data.length > MAX_FILE || next.length > MAX_FILE || !looksLikeText(data) || !Buffer.from(decodeText(data)).equals(data))
455
+ throw new Error("File is read-only");
456
+ if (hash(data) !== version)
457
+ throw new WikiConflictError("File changed on disk; reload before saving");
458
+ writeFileSync(abs, next);
459
+ this.record(cwd, { files: new Map([[path, data]]), skipped: [] }, { files: new Map([[path, next]]), skipped: [] }, "user", path);
460
+ }
461
+ change(cwd, id, path) {
462
+ const revision = this.history(cwd).find(r => r.id === id), change = revision?.changes.find(c => c.path === path);
463
+ if (!revision || !change)
464
+ throw new Error("Change record not found");
465
+ const blobs = revision.blobs[path];
466
+ return { ...change, before: !change.binary && blobs.before ? decodeText(readFileSync(join(this.folder(cwd), "blobs", blobs.before))) : null, after: !change.binary && blobs.after ? decodeText(readFileSync(join(this.folder(cwd), "blobs", blobs.after))) : null, truncated: false };
467
+ }
468
+ restore(cwd, id, undo, path) {
469
+ if (this.busy(cwd))
470
+ throw new Error("Wait for the current Wiki request to finish");
471
+ const records = this.history(cwd), revision = records.find(r => r.id === id);
472
+ if (!revision)
473
+ throw new Error("Change record not found");
474
+ const changes = revision.changes.filter(c => (!path || c.path === path) && c.undone !== undo);
475
+ const writes = changes.map(c => {
476
+ const abs = wikiPath(cwd, c.path), blob = revision.blobs[c.path];
477
+ const current = existsSync(abs) ? readFileSync(abs) : null;
478
+ if ((current ? hash(current) : null) !== (undo ? blob.after : blob.before))
479
+ throw new Error(`File changed since this request: ${c.path}`);
480
+ const target = undo ? blob.before : blob.after;
481
+ return { abs, current, next: target ? readFileSync(join(this.folder(cwd), "blobs", target)) : null, c };
482
+ });
483
+ const done = [];
484
+ try {
485
+ for (const w of writes) {
486
+ if (w.next === null)
487
+ rmSync(w.abs);
488
+ else {
489
+ mkdirSync(dirname(w.abs), { recursive: true });
490
+ writeFileSync(w.abs, w.next);
491
+ }
492
+ done.push(w);
493
+ }
494
+ for (const w of writes)
495
+ w.c.undone = undo;
496
+ this.persist(cwd, records);
497
+ }
498
+ catch (error) {
499
+ for (const w of done.reverse()) {
500
+ if (w.current === null)
501
+ rmSync(w.abs, { force: true });
502
+ else
503
+ writeFileSync(w.abs, w.current);
504
+ }
505
+ throw error;
506
+ }
507
+ finally {
508
+ this.invalidate(cwd);
509
+ }
510
+ }
511
+ }