pim-agent 0.13.0 → 0.14.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 (253) hide show
  1. package/package.json +3 -3
  2. package/packages/core/src/attachments/AttachmentStore.ts +8 -13
  3. package/packages/core/src/attachments/Attachments.ts +2 -4
  4. package/packages/core/src/extensions/CoreExtensions.ts +1 -1
  5. package/packages/core/src/extensions/apply-patch/coordinator.ts +1 -1
  6. package/packages/core/src/extensions/apply-patch/executor.ts +21 -25
  7. package/packages/core/src/extensions/apply-patch/matcher.ts +1 -1
  8. package/packages/core/src/extensions/apply-patch/model.ts +2 -6
  9. package/packages/core/src/extensions/apply-patch/parser.ts +60 -162
  10. package/packages/core/src/extensions/apply-patch/schema.ts +0 -1
  11. package/packages/core/src/extensions/bash/MemoryCap.ts +5 -4
  12. package/packages/core/src/extensions/bash/capture.ts +16 -23
  13. package/packages/core/src/extensions/bash/format.ts +2 -2
  14. package/packages/core/src/extensions/bash/image.ts +4 -8
  15. package/packages/core/src/extensions/bash/index.ts +1 -1
  16. package/packages/core/src/extensions/bash/render.ts +1 -1
  17. package/packages/core/src/extensions/bash/run.ts +9 -15
  18. package/packages/core/src/extensions/bash/schema.ts +4 -4
  19. package/packages/core/src/extensions/edit/edit.ts +14 -33
  20. package/packages/core/src/extensions/glob/glob.ts +1 -3
  21. package/packages/core/src/extensions/glob/index.ts +7 -9
  22. package/packages/core/src/extensions/glob/render.ts +2 -4
  23. package/packages/core/src/extensions/glob/schema.ts +1 -1
  24. package/packages/core/src/extensions/grep/grep.ts +7 -7
  25. package/packages/core/src/extensions/grep/render.ts +5 -6
  26. package/packages/core/src/extensions/grep/schema.ts +3 -7
  27. package/packages/core/src/extensions/read/ImageMemory.ts +2 -8
  28. package/packages/core/src/extensions/read/image.ts +3 -3
  29. package/packages/core/src/extensions/read/index.ts +2 -14
  30. package/packages/core/src/extensions/read/read.ts +5 -6
  31. package/packages/core/src/extensions/read/render.ts +1 -1
  32. package/packages/core/src/extensions/read/schema.ts +2 -2
  33. package/packages/core/src/extensions/subagent/render.ts +2 -2
  34. package/packages/core/src/extensions/subagent/subagent.ts +10 -21
  35. package/packages/core/src/extensions/system-prompt/index.ts +1 -2
  36. package/packages/core/src/extensions/system-prompt/prompt.ts +2 -4
  37. package/packages/core/src/extensions/todo/render.ts +1 -4
  38. package/packages/core/src/extensions/todo/todo.ts +16 -23
  39. package/packages/core/src/extensions/web-fetch/JinaReaderClient.ts +3 -11
  40. package/packages/core/src/extensions/web-fetch/WebViewFetchClient.ts +1 -3
  41. package/packages/core/src/extensions/web-fetch/fetch.ts +4 -12
  42. package/packages/core/src/extensions/web-fetch/image.ts +3 -4
  43. package/packages/core/src/extensions/web-fetch/index.ts +2 -13
  44. package/packages/core/src/extensions/web-fetch/render.ts +3 -4
  45. package/packages/core/src/extensions/web-fetch/schema.ts +3 -4
  46. package/packages/core/src/extensions/web-search/ExaMcpClient.ts +23 -31
  47. package/packages/core/src/extensions/web-search/SearchBreaker.ts +1 -1
  48. package/packages/core/src/extensions/web-search/SearchChain.ts +6 -5
  49. package/packages/core/src/extensions/web-search/providers/DuckDuckGoProvider.ts +2 -2
  50. package/packages/core/src/extensions/web-search/providers/FirecrawlProvider.ts +1 -2
  51. package/packages/core/src/extensions/web-search/providers/SearchProvider.ts +3 -3
  52. package/packages/core/src/extensions/write/write.ts +1 -1
  53. package/packages/core/src/picker/InProcessFilePickerSuggestionEngine.ts +1 -3
  54. package/packages/core/src/picker/PickerItem.ts +1 -1
  55. package/packages/core/src/picker/PickerService.ts +3 -7
  56. package/packages/core/src/picker/RemoteFilePickerSuggestionEngine.ts +1 -2
  57. package/packages/core/src/picker/catalog.ts +7 -15
  58. package/packages/core/src/picker/filePickerWorker.ts +1 -3
  59. package/packages/core/src/picker/ranker.ts +1 -1
  60. package/packages/core/src/picker/token.ts +3 -4
  61. package/packages/core/src/session/AgentRuntime.ts +5 -8
  62. package/packages/core/src/session/EventLog.ts +6 -12
  63. package/packages/core/src/session/ReadCursors.ts +3 -4
  64. package/packages/core/src/session/SearchIndex.ts +49 -86
  65. package/packages/core/src/session/SearchTokens.ts +4 -7
  66. package/packages/core/src/session/SessionCache.ts +8 -6
  67. package/packages/core/src/session/SessionDigest.ts +10 -19
  68. package/packages/core/src/session/SessionHost.ts +53 -83
  69. package/packages/core/src/session/SessionLease.ts +12 -15
  70. package/packages/core/src/session/SessionMeta.ts +16 -32
  71. package/packages/core/src/session/SessionName.ts +3 -7
  72. package/packages/core/src/session/SessionRegistry.ts +13 -29
  73. package/packages/core/src/session/SessionUi.ts +12 -16
  74. package/packages/core/src/session/WriteMark.ts +7 -16
  75. package/packages/core/src/shared/Cli.ts +1 -1
  76. package/packages/core/src/shared/DaemonUnit.ts +1 -2
  77. package/packages/core/src/shared/DiffLines.ts +16 -34
  78. package/packages/core/src/shared/DiffRenderer.ts +8 -27
  79. package/packages/core/src/shared/Directories.ts +4 -4
  80. package/packages/core/src/shared/EditMatcher.ts +18 -50
  81. package/packages/core/src/shared/ExtensionToggles.ts +3 -6
  82. package/packages/core/src/shared/FileEnumerator.ts +73 -57
  83. package/packages/core/src/shared/FileScanner.ts +5 -13
  84. package/packages/core/src/shared/FileWatch.ts +13 -17
  85. package/packages/core/src/shared/Format.ts +2 -3
  86. package/packages/core/src/shared/FuzzyMatcher.ts +18 -19
  87. package/packages/core/src/shared/Git.ts +22 -47
  88. package/packages/core/src/shared/GitMonitor.ts +12 -23
  89. package/packages/core/src/shared/GlobExclusions.ts +0 -4
  90. package/packages/core/src/shared/Http.ts +1 -1
  91. package/packages/core/src/shared/ImageMime.ts +3 -5
  92. package/packages/core/src/shared/Images.ts +17 -27
  93. package/packages/core/src/shared/Languages.ts +2 -2
  94. package/packages/core/src/shared/Levenshtein.ts +1 -6
  95. package/packages/core/src/shared/Lines.ts +1 -1
  96. package/packages/core/src/shared/McpClient.ts +2 -2
  97. package/packages/core/src/shared/PatchSummary.ts +1 -1
  98. package/packages/core/src/shared/Paths.ts +4 -1
  99. package/packages/core/src/shared/PiExtensions.ts +5 -12
  100. package/packages/core/src/shared/PimSettings.ts +14 -15
  101. package/packages/core/src/shared/Proc.ts +2 -3
  102. package/packages/core/src/shared/RateLimiter.ts +1 -1
  103. package/packages/core/src/shared/Renderer.ts +1 -3
  104. package/packages/core/src/shared/RepoDiff.ts +24 -46
  105. package/packages/core/src/shared/SearchRender.ts +3 -4
  106. package/packages/core/src/shared/SpillCache.ts +2 -3
  107. package/packages/core/src/shared/SubagentLogs.ts +4 -6
  108. package/packages/core/src/shared/Supervisor.ts +19 -32
  109. package/packages/core/src/shared/Surface.ts +1 -1
  110. package/packages/core/src/shared/Sweeper.ts +1 -1
  111. package/packages/core/src/shared/Tools.ts +17 -30
  112. package/packages/core/src/shared/Updater.ts +15 -15
  113. package/packages/core/src/view/BodyRenderer.ts +1 -1
  114. package/packages/core/src/view/DiffBlocks.ts +0 -1
  115. package/packages/core/src/view/DiffExpand.ts +10 -25
  116. package/packages/core/src/view/DiffLayout.ts +2 -2
  117. package/packages/core/src/view/DiffPairs.ts +2 -2
  118. package/packages/core/src/view/MarkdownPainter.ts +2 -19
  119. package/packages/core/src/view/MovePath.ts +4 -17
  120. package/packages/core/src/view/Painting.ts +4 -4
  121. package/packages/core/src/view/ViewBlock.ts +11 -12
  122. package/packages/daemon/src/Daemon.ts +2 -7
  123. package/packages/daemon/src/DaemonInstall.ts +5 -12
  124. package/packages/daemon/src/Surfaces.ts +3 -12
  125. package/packages/daemon/src/TelegramSurface.ts +0 -1
  126. package/packages/daemon/src/index.ts +0 -1
  127. package/packages/protocol/src/Command.ts +33 -41
  128. package/packages/protocol/src/Diff.ts +0 -5
  129. package/packages/protocol/src/ImageRoute.ts +2 -5
  130. package/packages/protocol/src/ServerEvent.ts +68 -105
  131. package/packages/server/src/AttachmentEndpoint.ts +45 -19
  132. package/packages/server/src/ClientConnection.ts +11 -12
  133. package/packages/server/src/ImageEndpoint.ts +3 -4
  134. package/packages/server/src/ProbeClient.ts +26 -59
  135. package/packages/server/src/Reloader.ts +4 -7
  136. package/packages/server/src/SendFileTool.ts +1 -1
  137. package/packages/server/src/SessionCatalogue.ts +35 -47
  138. package/packages/server/src/SessionProjection.ts +7 -12
  139. package/packages/server/src/SessionStream.ts +33 -80
  140. package/packages/server/src/StaticClient.ts +7 -8
  141. package/packages/server/src/WebOptions.ts +2 -6
  142. package/packages/server/src/WsGateway.ts +35 -96
  143. package/packages/server/src/probe.ts +6 -18
  144. package/packages/telegram/src/Bot.ts +2 -2
  145. package/packages/telegram/src/Commands.ts +108 -65
  146. package/packages/telegram/src/Config.ts +1 -6
  147. package/packages/telegram/src/Markdown.ts +4 -13
  148. package/packages/telegram/src/Renderer.ts +1 -1
  149. package/packages/telegram/src/Session.ts +3 -10
  150. package/packages/telegram/src/TaskScheduler.ts +31 -10
  151. package/packages/telegram/src/TaskStore.ts +11 -1
  152. package/packages/telegram/src/TaskTool.ts +1 -1
  153. package/packages/telegram/src/UpdateConfirm.ts +2 -2
  154. package/packages/tui/src/extensions/_init/index.ts +2 -5
  155. package/packages/tui/src/extensions/file-picker/index.ts +2 -6
  156. package/packages/tui/src/extensions/footer/index.ts +0 -1
  157. package/packages/tui/src/extensions/footer/powerline.ts +16 -28
  158. package/packages/tui/src/extensions/pim/index.ts +1 -1
  159. package/packages/tui/src/extensions/session-lease/index.ts +5 -6
  160. package/packages/tui/src/extensions/working-indicator/index.ts +1 -2
  161. package/packages/tui/src/themes/themeCliArgs.ts +1 -1
  162. package/packages/web/dist/client/assets/{abnfDiagram-O67JEVCF-CZ7V84ot.js → abnfDiagram-O67JEVCF-WweOU5DK.js} +1 -1
  163. package/packages/web/dist/client/assets/{arc-DiHGkUvc.js → arc-O2PmZXWt.js} +1 -1
  164. package/packages/web/dist/client/assets/architecture-7GRP2DOG-COBxVilU.js +1 -0
  165. package/packages/web/dist/client/assets/{architectureDiagram-NJMV4G6O-XS5DhfJB.js → architectureDiagram-NJMV4G6O-DwYq1YaM.js} +1 -1
  166. package/packages/web/dist/client/assets/{blockDiagram-BEXU5L5S-BCsgLvy1.js → blockDiagram-BEXU5L5S-CVab041n.js} +1 -1
  167. package/packages/web/dist/client/assets/{c4Diagram-YGBWAQC7-Bj9LcWNm.js → c4Diagram-YGBWAQC7-BmotHez3.js} +1 -1
  168. package/packages/web/dist/client/assets/channel-BKV-IcTA.js +1 -0
  169. package/packages/web/dist/client/assets/{chunk-3FUC2YCW-BmDurFxp.js → chunk-3FUC2YCW-CWNS7-n4.js} +1 -1
  170. package/packages/web/dist/client/assets/{chunk-5DYCD2WN-DAjG-pqn.js → chunk-5DYCD2WN-DflvXohu.js} +1 -1
  171. package/packages/web/dist/client/assets/{chunk-742MDFTN-Bmxs5puY.js → chunk-742MDFTN-B_0t6_RE.js} +1 -1
  172. package/packages/web/dist/client/assets/{chunk-7INBJB4K-Di7kIxrQ.js → chunk-7INBJB4K-wKj0Q3Kv.js} +1 -1
  173. package/packages/web/dist/client/assets/{chunk-7M6MHVWA-CSc8sR0b.js → chunk-7M6MHVWA-BMvUghc8.js} +1 -1
  174. package/packages/web/dist/client/assets/{chunk-7PRAP22T-C8kd5QvY.js → chunk-7PRAP22T-BY41wkcT.js} +1 -1
  175. package/packages/web/dist/client/assets/{chunk-GTNCS2PH-DG2b2LYE.js → chunk-GTNCS2PH-PqY_YoS0.js} +1 -1
  176. package/packages/web/dist/client/assets/{chunk-GWA4HPMP-DdLaLQCu.js → chunk-GWA4HPMP-fozSU5ty.js} +1 -1
  177. package/packages/web/dist/client/assets/{chunk-MBY4JIJT-C1j0yQlk.js → chunk-MBY4JIJT-CrHrC3jw.js} +1 -1
  178. package/packages/web/dist/client/assets/{chunk-NETBCI7D-Bfq9H74p.js → chunk-NETBCI7D-DWdAxieG.js} +1 -1
  179. package/packages/web/dist/client/assets/{chunk-O7XYJQB3-ZHvBmBO-.js → chunk-O7XYJQB3-B6uOn9iS.js} +1 -1
  180. package/packages/web/dist/client/assets/{chunk-UA2S7LBM-ku1JeHSK.js → chunk-UA2S7LBM-BxYSHqyy.js} +1 -1
  181. package/packages/web/dist/client/assets/{chunk-WEXAMYUT-BP1iga_7.js → chunk-WEXAMYUT-CtwIaQDa.js} +1 -1
  182. package/packages/web/dist/client/assets/{chunk-XXDRQBXY-BBeccPcX.js → chunk-XXDRQBXY-fgBFe-PX.js} +1 -1
  183. package/packages/web/dist/client/assets/{chunk-Z7XXMR3K-SMHZugaR.js → chunk-Z7XXMR3K-CPonoV3p.js} +1 -1
  184. package/packages/web/dist/client/assets/{chunk-ZIGJFQKS-CFU_JLG4.js → chunk-ZIGJFQKS-Cdu87cTj.js} +1 -1
  185. package/packages/web/dist/client/assets/{classDiagram-v2-NBCMYWYE-CYviuQzJ.js → classDiagram-v2-NBCMYWYE-DSWsPaa7.js} +1 -1
  186. package/packages/web/dist/client/assets/{core-DyHsgs5k.js → core-CBoS7AST.js} +1 -1
  187. package/packages/web/dist/client/assets/{cose-bilkent-JH36ORCC-9v2ZkJzo.js → cose-bilkent-JH36ORCC-CTwlldtJ.js} +1 -1
  188. package/packages/web/dist/client/assets/{cynefin-OW5HDTMX-Bx5xRadi.js → cynefin-OW5HDTMX-CZQ4EPNJ.js} +1 -1
  189. package/packages/web/dist/client/assets/{cynefinDiagram-VND7K2PF-BRfGDKqa.js → cynefinDiagram-VND7K2PF-C2gp6H_Q.js} +1 -1
  190. package/packages/web/dist/client/assets/{dagre-6A5THRUB-C4l-2L2e.js → dagre-6A5THRUB-CDxTP47t.js} +1 -1
  191. package/packages/web/dist/client/assets/{diagram-22UHCM2B-d_hQ3kyi.js → diagram-22UHCM2B-BIohE1lI.js} +1 -1
  192. package/packages/web/dist/client/assets/{diagram-3UASUU5V-DrEeguPI.js → diagram-3UASUU5V-TwAUir-w.js} +1 -1
  193. package/packages/web/dist/client/assets/{diagram-ATOU4E4O-BmfQ22pD.js → diagram-ATOU4E4O-BM1JQrIu.js} +1 -1
  194. package/packages/web/dist/client/assets/{diagram-CDSNMT55-BjVWjuPF.js → diagram-CDSNMT55-BKIqhxSI.js} +1 -1
  195. package/packages/web/dist/client/assets/{diagram-MLGK6HIB-wDjk-O8i.js → diagram-MLGK6HIB-CVe3QDHr.js} +1 -1
  196. package/packages/web/dist/client/assets/{diagram-MPIPVDR6-DqXQFE1s.js → diagram-MPIPVDR6-Bj-MQIFI.js} +1 -1
  197. package/packages/web/dist/client/assets/{dist-BddGOpie.js → dist-DlZpcyc2.js} +1 -1
  198. package/packages/web/dist/client/assets/{ebnfDiagram-ZINNZB2B-UB8kOITM.js → ebnfDiagram-ZINNZB2B-C06mmvmz.js} +1 -1
  199. package/packages/web/dist/client/assets/{elk-276RUBZZ-D4miYQnD.js → elk-276RUBZZ-CizJSNEj.js} +1 -1
  200. package/packages/web/dist/client/assets/{erDiagram-OPXOYQCR--RXKv7KH.js → erDiagram-OPXOYQCR-M3FQkKI1.js} +1 -1
  201. package/packages/web/dist/client/assets/eventmodeling-NTZA5JFV-D_UXLpIu.js +1 -0
  202. package/packages/web/dist/client/assets/flowDiagram-KWPJA3E3-BT4eEEVm.js +1 -0
  203. package/packages/web/dist/client/assets/{ganttDiagram-FUAMR5RP-C0CFpE5x.js → ganttDiagram-FUAMR5RP-CojF9Q16.js} +1 -1
  204. package/packages/web/dist/client/assets/{gitGraph-4MIJSDKK-VPL7nv2c.js → gitGraph-4MIJSDKK-YvKqGEPk.js} +1 -1
  205. package/packages/web/dist/client/assets/{gitGraphDiagram-X574FWY7-CgxJA-T8.js → gitGraphDiagram-X574FWY7-XqNqIFo9.js} +1 -1
  206. package/packages/web/dist/client/assets/index--2CDtzvY.css +1 -0
  207. package/packages/web/dist/client/assets/{index-XTlTnQmx.js → index-bT6QPJZs.js} +7 -7
  208. package/packages/web/dist/client/assets/{info-A6RAGUB7-P0-o68pj.js → info-A6RAGUB7-B_1BCzM2.js} +1 -1
  209. package/packages/web/dist/client/assets/{infoDiagram-VRGFBTTK-DFJZNRUP.js → infoDiagram-VRGFBTTK-zFX8B-dP.js} +1 -1
  210. package/packages/web/dist/client/assets/{ishikawaDiagram-OU5B5YK6-ZkyhNd1T.js → ishikawaDiagram-OU5B5YK6-DLzQ_Uyf.js} +1 -1
  211. package/packages/web/dist/client/assets/{journeyDiagram-ZHPQQLJL-DFevH8my.js → journeyDiagram-ZHPQQLJL-S3WRMmoa.js} +1 -1
  212. package/packages/web/dist/client/assets/{kanban-definition-PNTS6WVX-l7e7ZZDC.js → kanban-definition-PNTS6WVX-B5Z8I-kM.js} +1 -1
  213. package/packages/web/dist/client/assets/{line-DO_s6Ujd.js → line-BGMH-i2X.js} +1 -1
  214. package/packages/web/dist/client/assets/{linear-C4xH5PPc.js → linear-KsrestXG.js} +1 -1
  215. package/packages/web/dist/client/assets/{mermaid-parser.core-nQaTP3VY.js → mermaid-parser.core-DNffyJJA.js} +3 -3
  216. package/packages/web/dist/client/assets/{mermaid.core-Cz05yTZh.js → mermaid.core-Cz3vJeQG.js} +4 -4
  217. package/packages/web/dist/client/assets/{mindmap-definition-NLK3R4M7-CHOEuHm2.js → mindmap-definition-NLK3R4M7-CzA61XUf.js} +1 -1
  218. package/packages/web/dist/client/assets/{packet-AYTQ26CC-BH3ehPMo.js → packet-AYTQ26CC-BO55e7cR.js} +1 -1
  219. package/packages/web/dist/client/assets/{pegDiagram-GJSIUBJH-CkWBLtad.js → pegDiagram-GJSIUBJH-DeRiXYNd.js} +1 -1
  220. package/packages/web/dist/client/assets/{pie-WAS4IAKB-nNVEq3dx.js → pie-WAS4IAKB-D9ibrQRe.js} +1 -1
  221. package/packages/web/dist/client/assets/{pieDiagram-5QR66LMP-DimUBAgN.js → pieDiagram-5QR66LMP-BaBpPP33.js} +1 -1
  222. package/packages/web/dist/client/assets/{quadrantDiagram-O4NWA36T-BR0XpRGQ.js → quadrantDiagram-O4NWA36T-DIfn4GjM.js} +1 -1
  223. package/packages/web/dist/client/assets/{radar-RG4KPBEZ-paA-H3mb.js → radar-RG4KPBEZ-Dmn5-nJq.js} +1 -1
  224. package/packages/web/dist/client/assets/{railroad-74A4TZTK-yEiKB3rz.js → railroad-74A4TZTK-ZCWNEd2Z.js} +1 -1
  225. package/packages/web/dist/client/assets/railroad-abnf-HS5TGJTU-_swqhy0y.js +1 -0
  226. package/packages/web/dist/client/assets/railroad-ebnf-LZEXJU2U-CytbazPx.js +1 -0
  227. package/packages/web/dist/client/assets/railroad-peg-WCYAUIDC-Bg8QW17T.js +1 -0
  228. package/packages/web/dist/client/assets/{railroadDiagram-XR7U4H2S--TXe6rdU.js → railroadDiagram-XR7U4H2S-CDPZ39sy.js} +1 -1
  229. package/packages/web/dist/client/assets/{requirementDiagram-PLB6GJNP-DyqH36Za.js → requirementDiagram-PLB6GJNP-CyCI8Yul.js} +1 -1
  230. package/packages/web/dist/client/assets/{sankeyDiagram-IPEJSGJF-Bg6e3BlM.js → sankeyDiagram-IPEJSGJF-p_XAPv72.js} +1 -1
  231. package/packages/web/dist/client/assets/{sequenceDiagram-PO4LG4MO-ClGf85My.js → sequenceDiagram-PO4LG4MO-DmR6IP_o.js} +1 -1
  232. package/packages/web/dist/client/assets/{src-DF7hQfMh.js → src-gnv7pJ1z.js} +1 -1
  233. package/packages/web/dist/client/assets/{stateDiagram-v2-GCMORJYK-BK8TpxNo.js → stateDiagram-v2-GCMORJYK-7ZTHl6ih.js} +1 -1
  234. package/packages/web/dist/client/assets/{swimlanes-2SLR337P-DDnTIT9M.js → swimlanes-2SLR337P-N6bj71pw.js} +1 -1
  235. package/packages/web/dist/client/assets/swimlanesDiagram-TC7HE7FX-BJQ2CBsp.js +8 -0
  236. package/packages/web/dist/client/assets/{timeline-definition-EJHVYXUP-mBBbH3bk.js → timeline-definition-EJHVYXUP-GNez0kW8.js} +1 -1
  237. package/packages/web/dist/client/assets/{treeView-Q6P3EWNA-CTK7tRwR.js → treeView-Q6P3EWNA-DyQZkboR.js} +1 -1
  238. package/packages/web/dist/client/assets/{treemap-WGGIJYW6-vOI-Ef_m.js → treemap-WGGIJYW6-_J8Pw2TM.js} +1 -1
  239. package/packages/web/dist/client/assets/{usecaseDiagram-POWQR4AR-COcii8x-.js → usecaseDiagram-POWQR4AR-Bg-CzKjl.js} +1 -1
  240. package/packages/web/dist/client/assets/{vennDiagram-UO4OBE2U-D6kmDGpM.js → vennDiagram-UO4OBE2U-DBQKHXhK.js} +1 -1
  241. package/packages/web/dist/client/assets/{wardley-WFR3VGLG-CtE2KI9r.js → wardley-WFR3VGLG-B5aAsQ3F.js} +1 -1
  242. package/packages/web/dist/client/assets/{wardleyDiagram-VNRHLVJA-ncxNGWnW.js → wardleyDiagram-VNRHLVJA-BKUQ1LK6.js} +1 -1
  243. package/packages/web/dist/client/assets/{xychartDiagram-PMCCYNJV-DUvd2Qze.js → xychartDiagram-PMCCYNJV-BI2sVbks.js} +1 -1
  244. package/packages/web/dist/client/index.html +2 -2
  245. package/packages/web/dist/client/assets/architecture-7GRP2DOG-HIzEIkwQ.js +0 -1
  246. package/packages/web/dist/client/assets/channel-BCA6qe9I.js +0 -1
  247. package/packages/web/dist/client/assets/eventmodeling-NTZA5JFV-l6SYFw1s.js +0 -1
  248. package/packages/web/dist/client/assets/flowDiagram-KWPJA3E3-CWW9qnqm.js +0 -1
  249. package/packages/web/dist/client/assets/index-D9_XJLSc.css +0 -1
  250. package/packages/web/dist/client/assets/railroad-abnf-HS5TGJTU-mVQx9aVM.js +0 -1
  251. package/packages/web/dist/client/assets/railroad-ebnf-LZEXJU2U-CllXx1OJ.js +0 -1
  252. package/packages/web/dist/client/assets/railroad-peg-WCYAUIDC-BBD2Q5W_.js +0 -1
  253. package/packages/web/dist/client/assets/swimlanesDiagram-TC7HE7FX-CM8UnP5t.js +0 -8
@@ -28,7 +28,7 @@ export type ToolDiff = {
28
28
  readonly hunks: readonly ToolDiffHunk[];
29
29
  };
30
30
 
31
- // Keep the EOF-newline flag out of `lines`, or `joinComparable` emits a phantom blank line.
31
+ // The EOF newline is a flag, not an empty last line, or diffs gain a phantom blank line.
32
32
  export type ToolDiffSide = {
33
33
  readonly lines: readonly string[];
34
34
  readonly hasTrailingNewline: boolean;
@@ -48,16 +48,11 @@ function buildToolDiff(
48
48
 
49
49
  const lines = build(oldSide.lines, newSide.lines);
50
50
 
51
- if (!lines.some((line) => line.kind !== "context")) {
51
+ if (lines.every((line) => line.kind === "context")) {
52
52
  return undefined;
53
53
  }
54
54
 
55
- const emphasized = attachEmphasis(lines);
56
-
57
- return {
58
- path,
59
- hunks: buildHunks(emphasized, contextSize),
60
- };
55
+ return { path, hunks: buildHunks(attachEmphasis(lines), contextSize) };
61
56
  }
62
57
 
63
58
  function fromText(text: string): ToolDiffSide {
@@ -82,48 +77,35 @@ function attachEmphasis(
82
77
  let i = 0;
83
78
 
84
79
  while (i < result.length) {
85
- if (result[i]?.kind !== "removed") {
80
+ if (result[i]!.kind !== "removed") {
86
81
  i += 1;
87
82
  continue;
88
83
  }
89
84
 
90
85
  let removedEnd = i;
91
- while (
92
- removedEnd < result.length &&
93
- result[removedEnd]?.kind === "removed"
94
- ) {
86
+ while (result[removedEnd]?.kind === "removed") {
95
87
  removedEnd += 1;
96
88
  }
97
-
98
89
  let addedEnd = removedEnd;
99
- while (addedEnd < result.length && result[addedEnd]?.kind === "added") {
90
+ while (result[addedEnd]?.kind === "added") {
100
91
  addedEnd += 1;
101
92
  }
102
93
 
103
- const removedCount = removedEnd - i;
104
- const addedCount = addedEnd - removedEnd;
105
-
106
- if (removedCount > 0 && removedCount === addedCount) {
107
- for (let k = 0; k < removedCount; k += 1) {
108
- const removed = result[i + k];
109
- const added = result[removedEnd + k];
110
-
111
- if (removed === undefined || added === undefined) {
112
- continue;
113
- }
114
-
94
+ // Only pair lines when the removed and added runs are the same length.
95
+ const count = removedEnd - i;
96
+ if (count === addedEnd - removedEnd) {
97
+ for (let k = 0; k < count; k += 1) {
98
+ const removed = result[i + k]!;
99
+ const added = result[removedEnd + k]!;
115
100
  const ranges = computeIntraLineRanges(removed.text, added.text);
116
-
117
- if (ranges === undefined) {
118
- continue;
101
+ if (ranges !== undefined) {
102
+ result[i + k] = { ...removed, emphasis: ranges.removed };
103
+ result[removedEnd + k] = { ...added, emphasis: ranges.added };
119
104
  }
120
-
121
- result[i + k] = { ...removed, emphasis: ranges.removed };
122
- result[removedEnd + k] = { ...added, emphasis: ranges.added };
123
105
  }
124
106
  }
125
107
 
126
- i = addedEnd > i ? addedEnd : i + 1;
108
+ i = addedEnd;
127
109
  }
128
110
 
129
111
  return result;
@@ -112,8 +112,7 @@ function backgroundsFor(theme: Theme): DiffBackgrounds {
112
112
  }
113
113
 
114
114
  function isLightTheme(theme: Theme): boolean {
115
- const name = theme.name?.toLowerCase() ?? "";
116
- return name === "light" || name.includes("light");
115
+ return theme.name?.toLowerCase().includes("light") ?? false;
117
116
  }
118
117
 
119
118
  function renderHunk(
@@ -174,10 +173,6 @@ function applyEmphasis(
174
173
  lineBg: string,
175
174
  emphBg: string
176
175
  ): string {
177
- if (ranges.length === 0) {
178
- return text;
179
- }
180
-
181
176
  const starts = new Set<number>();
182
177
  const ends = new Set<number>();
183
178
 
@@ -192,6 +187,9 @@ function applyEmphasis(
192
187
  return text;
193
188
  }
194
189
 
190
+ const marker = (pos: number): string =>
191
+ (ends.has(pos) ? lineBg : "") + (starts.has(pos) ? emphBg : "");
192
+
195
193
  let result = "";
196
194
  let visiblePos = 0;
197
195
  let i = 0;
@@ -209,16 +207,9 @@ function applyEmphasis(
209
207
  continue;
210
208
  }
211
209
 
212
- if (ends.has(visiblePos) || starts.has(visiblePos)) {
213
- if (i > segStart) {
214
- result += text.slice(segStart, i);
215
- }
216
- if (ends.has(visiblePos)) {
217
- result += lineBg;
218
- }
219
- if (starts.has(visiblePos)) {
220
- result += emphBg;
221
- }
210
+ const mark = marker(visiblePos);
211
+ if (mark !== "") {
212
+ result += text.slice(segStart, i) + mark;
222
213
  segStart = i;
223
214
  }
224
215
 
@@ -226,17 +217,7 @@ function applyEmphasis(
226
217
  i += 1;
227
218
  }
228
219
 
229
- if (i > segStart) {
230
- result += text.slice(segStart, i);
231
- }
232
- if (ends.has(visiblePos)) {
233
- result += lineBg;
234
- }
235
- if (starts.has(visiblePos)) {
236
- result += emphBg;
237
- }
238
-
239
- return result;
220
+ return result + text.slice(segStart) + marker(visiblePos);
240
221
  }
241
222
 
242
223
  function applyBackground(
@@ -10,11 +10,11 @@ export type DirectoryEntry = {
10
10
  };
11
11
 
12
12
  export type DirectoryListing = {
13
- /** What was read: absolute, resolved, and with `~` expanded. */
13
+ /** Absolute, resolved, `~` expanded. */
14
14
  readonly path: string;
15
- /** Absent at the root of the filesystem, which is its own parent. */
15
+ /** Absent at the filesystem root. */
16
16
  readonly parent?: string;
17
- /** The directories inside it, name-sorted, hidden ones included. */
17
+ /** Subdirectories only, name-sorted, hidden included. */
18
18
  readonly entries: readonly DirectoryEntry[];
19
19
  };
20
20
 
@@ -78,7 +78,7 @@ function refusal(error: unknown, path: string): string {
78
78
  }
79
79
  }
80
80
 
81
- /** Makes one directory inside an existing one. Never recursive: a parent that is missing is said so, not invented. */
81
+ /** Not recursive: a missing parent is an error. */
82
82
  async function create(path: string): Promise<void> {
83
83
  const name = basename(Paths.expandHome(path));
84
84
  if (name === "" || name === "." || name === "..") {
@@ -81,7 +81,9 @@ function resolve(
81
81
 
82
82
  if (replaceAll) {
83
83
  return {
84
- ranges: sortRanges(candidates.map((candidate) => candidate.range)),
84
+ ranges: candidates
85
+ .map((candidate) => candidate.range)
86
+ .sort((left, right) => left[0] - right[0]),
85
87
  strategy: strategy.name,
86
88
  matchCount: candidates.length,
87
89
  };
@@ -441,25 +443,10 @@ function unicodeNormalized({
441
443
  content,
442
444
  oldString,
443
445
  }: MatchInput): readonly Candidate[] {
444
- // normalizeUnicode substitutions must stay 1:1 by UTF-16 code unit, or offsets desync.
445
- const normalizedContent = normalizeUnicode(content);
446
- const normalizedOld = normalizeUnicode(oldString);
447
- const candidates: Candidate[] = [];
448
- let index = 0;
449
-
450
- while (true) {
451
- const start = normalizedContent.indexOf(normalizedOld, index);
452
-
453
- if (start === -1) {
454
- break;
455
- }
456
-
457
- const end = start + oldString.length;
458
- candidates.push({ range: [start, end], text: content.slice(start, end) });
459
- index = Math.max(start + 1, end);
460
- }
461
-
462
- return candidates;
446
+ // normalizeUnicode must map 1:1 by UTF-16 code unit, or offsets desync.
447
+ return findAll(normalizeUnicode(content), normalizeUnicode(oldString)).map(
448
+ ({ range }) => ({ range, text: content.slice(...range) })
449
+ );
463
450
  }
464
451
 
465
452
  function blockAnchor(input: MatchInput): readonly Candidate[] {
@@ -477,7 +464,7 @@ function blockAnchor(input: MatchInput): readonly Candidate[] {
477
464
  ) / middleCount;
478
465
  }
479
466
 
480
- // 0.3 floor filters anchor coincidence on unrelated blocks sharing first/last line text.
467
+ // Rejects unrelated blocks that merely share first and last lines.
481
468
  return similarity >= 0.3;
482
469
  });
483
470
  }
@@ -598,10 +585,6 @@ function dedupeCandidates(
598
585
  return deduped;
599
586
  }
600
587
 
601
- function sortRanges(ranges: readonly EditRange[]): readonly EditRange[] {
602
- return [...ranges].sort((left, right) => left[0] - right[0]);
603
- }
604
-
605
588
  function logicalLines(content: string): readonly string[] {
606
589
  if (content.length === 0) {
607
590
  return [];
@@ -653,31 +636,17 @@ function offsetLines(content: string): readonly OffsetLine[] {
653
636
  return lines;
654
637
  }
655
638
 
639
+ const CONTROL_ESCAPES: Readonly<Record<string, string>> = {
640
+ n: "\n",
641
+ t: "\t",
642
+ r: "\r",
643
+ };
644
+
656
645
  function unescapeString(text: string): string {
657
- return text.replace(/\\(n|t|r|'|"|`|\\|\n|\$)/gu, (match, value) => {
658
- switch (value) {
659
- case "n":
660
- return "\n";
661
- case "t":
662
- return "\t";
663
- case "r":
664
- return "\r";
665
- case "'":
666
- return "'";
667
- case '"':
668
- return '"';
669
- case "`":
670
- return "`";
671
- case "\\":
672
- return "\\";
673
- case "\n":
674
- return "\n";
675
- case "$":
676
- return "$";
677
- default:
678
- return match;
679
- }
680
- });
646
+ return text.replace(
647
+ /\\(n|t|r|'|"|`|\\|\n|\$)/gu,
648
+ (_, value: string) => CONTROL_ESCAPES[value] ?? value
649
+ );
681
650
  }
682
651
 
683
652
  function escapeSequences(text: string): readonly string[] {
@@ -763,5 +732,4 @@ export const EditMatcher = {
763
732
  renderNotFound,
764
733
  lineRangeFor,
765
734
  NotFoundError,
766
- MultipleMatchesError,
767
735
  };
@@ -2,7 +2,7 @@ import type { ExtensionFactory } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  import { PimSettings } from "./PimSettings";
4
4
 
5
- // Pi's `enabled` filter covers disk paths only, never inline factories: pim owns this roster.
5
+ // Pi's `enabled` filter skips inline factories, so pim keeps its own roster.
6
6
  const EXTENSIONS = {
7
7
  _init: "Splash screen, runtime guard, /clear",
8
8
  "apply-patch": "apply_patch tool",
@@ -34,8 +34,7 @@ const DEFAULT_DISABLED: readonly PimExtensionName[] = ["todo", "tps"];
34
34
 
35
35
  const REQUIRED: readonly PimExtensionName[] = ["_init", "pim", "session-lease"];
36
36
 
37
- // Hardcoded, never derived: the browser switches these and nothing else, under
38
- // the label it gives them — the terminal menu names them by key, this does not.
37
+ // The only toggles the browser shows.
39
38
  const WEB = [{ name: "todo", label: "Todo Tool" }] as const satisfies readonly {
40
39
  name: PimExtensionName;
41
40
  label: string;
@@ -53,7 +52,7 @@ function describe(name: PimExtensionName): string {
53
52
  return EXTENSIONS[name];
54
53
  }
55
54
 
56
- // Gate inside the factory, not around the roster: pi re-invokes factories on `ctx.reload()`.
55
+ // Checked inside the factory because pi re-runs factories on `ctx.reload()`.
57
56
  function gate(
58
57
  name: PimExtensionName,
59
58
  factory: ExtensionFactory
@@ -79,7 +78,6 @@ async function isDisabled(name: string): Promise<boolean> {
79
78
  return !enabled(name, toggles);
80
79
  }
81
80
 
82
- // Serialized: each call is a read-modify-write of the same record.
83
81
  async function setDisabled(name: string, isOff: boolean): Promise<void> {
84
82
  if (!isKnown(name)) {
85
83
  throw new Error(`Unknown pim extension "${name}"`);
@@ -128,7 +126,6 @@ export const ExtensionToggles = {
128
126
  NAMES,
129
127
  WEB,
130
128
  isRequired,
131
- isKnown,
132
129
  describe,
133
130
  gate,
134
131
  disabled,
@@ -1,4 +1,4 @@
1
- import { readdir } from "node:fs/promises";
1
+ import { readdir, stat } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import ignore, { type Ignore } from "ignore";
4
4
 
@@ -86,8 +86,8 @@ function pushRules(out: string[], content: string): void {
86
86
  }
87
87
  }
88
88
 
89
- // Gitignore anchoring: a pattern containing a non-trailing slash only gains the prefix; one
90
- // without also gains `**/`, or it stops matching below its own directory.
89
+ // Rewrites a nested .gitignore relative to the repo root. Slash-free patterns gain `**/`
90
+ // so they still match at any depth below their directory.
91
91
  function reanchorRules(
92
92
  content: string,
93
93
  basePrefix: string,
@@ -122,10 +122,30 @@ function reanchorRules(
122
122
  }
123
123
  }
124
124
 
125
+ function addDirRules(
126
+ out: string[],
127
+ content: string,
128
+ dirAbs: string,
129
+ repoRootAbs: string
130
+ ): void {
131
+ const dirRel = relFromBase(dirAbs, repoRootAbs);
132
+ if (dirRel === undefined || dirRel === "") {
133
+ pushRules(out, content);
134
+ } else {
135
+ reanchorRules(content, `${dirRel}/`, out);
136
+ }
137
+ }
138
+
125
139
  async function findRepoRoot(start: string): Promise<string | undefined> {
126
140
  let dir = start;
127
141
  for (;;) {
128
- if (await Bun.file(join(dir, ".git")).exists()) {
142
+ // `.git` is a directory in a normal repo and a file in a worktree.
143
+ if (
144
+ await stat(join(dir, ".git")).then(
145
+ () => true,
146
+ () => false
147
+ )
148
+ ) {
129
149
  return dir;
130
150
  }
131
151
  const parent = dirname(dir);
@@ -177,7 +197,7 @@ async function processDir(
177
197
  if (ctx.useIgnore) {
178
198
  const hasDotGit = entries.some((e) => e.name === ".git");
179
199
  if (hasDotGit) {
180
- // A .git is a repo boundary: a child repo must not inherit its parent's rules.
200
+ // A nested repo does not inherit its parent's rules.
181
201
  inRepo = true;
182
202
  repoRootAbs = currentDir.abs;
183
203
  rules = await repoBaseRules(currentDir.abs, ctx.globalGitIgnore);
@@ -191,14 +211,8 @@ async function processDir(
191
211
  join(currentDir.abs, ".gitignore")
192
212
  );
193
213
  if (content !== undefined) {
194
- const dirRel = relFromBase(currentDir.abs, repoRootAbs);
195
- const next = rules.slice();
196
- if (dirRel === undefined || dirRel === "") {
197
- pushRules(next, content);
198
- } else {
199
- reanchorRules(content, `${dirRel}/`, next);
200
- }
201
- rules = next;
214
+ rules = rules.slice();
215
+ addDirRules(rules, content, currentDir.abs, repoRootAbs);
202
216
  matcher = ignore().add(rules);
203
217
  }
204
218
  }
@@ -255,13 +269,11 @@ async function processDir(
255
269
 
256
270
  if (entry.isFile() || isSymlink) {
257
271
  ctx.result.push(relPath);
258
- continue;
259
272
  }
260
273
  }
261
274
  }
262
275
 
263
- // Refill from the shared stack on each completion; N worker loops would exit on the root-only
264
- // stack before any child was pushed and silently return a partial listing.
276
+ // Refill on each completion: fixed workers would exit early while the stack holds only the root.
265
277
  function drain(ctx: WalkContext): Promise<void> {
266
278
  let inFlight = 0;
267
279
  return new Promise<void>((resolve, reject) => {
@@ -282,6 +294,41 @@ function drain(ctx: WalkContext): Promise<void> {
282
294
  });
283
295
  }
284
296
 
297
+ /** Rules from a repo strictly above `root`; `root`'s own .gitignore is read by processDir. */
298
+ async function enclosingRepo(
299
+ root: string,
300
+ globalGitIgnore: string | undefined
301
+ ): Promise<
302
+ { readonly repoRoot: string; readonly rules: string[] } | undefined
303
+ > {
304
+ const repoRoot = await findRepoRoot(root);
305
+ if (repoRoot === undefined || repoRoot === root) {
306
+ return undefined;
307
+ }
308
+ const rules = await repoBaseRules(repoRoot, globalGitIgnore);
309
+
310
+ // Shallowest first: gitignore precedence is by depth.
311
+ const intermediates: string[] = [];
312
+ let dir = dirname(root);
313
+ while (dir !== repoRoot && dir.length > repoRoot.length) {
314
+ intermediates.push(dir);
315
+ const parent = dirname(dir);
316
+ if (parent === dir) {
317
+ break;
318
+ }
319
+ dir = parent;
320
+ }
321
+ intermediates.reverse();
322
+
323
+ for (const dirAbs of intermediates) {
324
+ const content = await readIgnoreFile(join(dirAbs, ".gitignore"));
325
+ if (content !== undefined) {
326
+ addDirRules(rules, content, dirAbs, repoRoot);
327
+ }
328
+ }
329
+ return { repoRoot, rules };
330
+ }
331
+
285
332
  /** All files under `root` as root-relative POSIX paths; `.gitignore` is honored only inside a repo. */
286
333
  async function enumerate(
287
334
  root: string,
@@ -298,43 +345,9 @@ async function enumerate(
298
345
  pathname === undefined ? undefined : await readIgnoreFile(pathname);
299
346
  }
300
347
 
301
- // Seed rules from a repo enclosing `root`; `root`'s own .gitignore is added by processDir.
302
- let initialInRepo = false;
303
- let initialRepoRootAbs = root;
304
- let initialRules: string[] = [];
305
- if (useIgnore) {
306
- const repoRoot = await findRepoRoot(root);
307
- if (repoRoot !== undefined && repoRoot !== root) {
308
- initialInRepo = true;
309
- initialRepoRootAbs = repoRoot;
310
- initialRules = await repoBaseRules(repoRoot, globalGitIgnore);
311
-
312
- // Shallowest first: gitignore precedence is by depth.
313
- const intermediates: string[] = [];
314
- let dir = dirname(root);
315
- while (dir !== repoRoot && dir.length > repoRoot.length) {
316
- intermediates.push(dir);
317
- const parent = dirname(dir);
318
- if (parent === dir) {
319
- break;
320
- }
321
- dir = parent;
322
- }
323
- intermediates.reverse();
324
-
325
- for (const dirAbs of intermediates) {
326
- const content = await readIgnoreFile(join(dirAbs, ".gitignore"));
327
- if (content !== undefined) {
328
- const dirRel = relFromBase(dirAbs, repoRoot);
329
- if (dirRel === undefined || dirRel === "") {
330
- pushRules(initialRules, content);
331
- } else {
332
- reanchorRules(content, `${dirRel}/`, initialRules);
333
- }
334
- }
335
- }
336
- }
337
- }
348
+ const enclosing = useIgnore
349
+ ? await enclosingRepo(root, globalGitIgnore)
350
+ : undefined;
338
351
 
339
352
  const ctx: WalkContext = {
340
353
  includeDotfiles,
@@ -344,10 +357,13 @@ async function enumerate(
344
357
  {
345
358
  abs: root,
346
359
  rel: "",
347
- inRepo: initialInRepo,
348
- repoRootAbs: initialRepoRootAbs,
349
- ignoreRules: initialRules,
350
- matcher: initialInRepo ? ignore().add(initialRules) : EMPTY_MATCHER,
360
+ inRepo: enclosing !== undefined,
361
+ repoRootAbs: enclosing?.repoRoot ?? root,
362
+ ignoreRules: enclosing?.rules ?? [],
363
+ matcher:
364
+ enclosing === undefined
365
+ ? EMPTY_MATCHER
366
+ : ignore().add(enclosing.rules),
351
367
  },
352
368
  ],
353
369
  result: [],
@@ -21,19 +21,11 @@ async function scan(
21
21
  });
22
22
  const matcher = new Bun.Glob(await expandDirectory(absoluteRoot, pattern));
23
23
  const excludes = GlobExclusions.compile(options.exclude);
24
- const files: string[] = [];
25
-
26
- for (const relativePath of relativePaths) {
27
- if (!matcher.match(relativePath)) {
28
- continue;
29
- }
30
- if (GlobExclusions.ignores(excludes, relativePath)) {
31
- continue;
32
- }
33
- files.push(join(absoluteRoot, relativePath));
34
- }
35
-
36
- return files;
24
+ return relativePaths
25
+ .filter(
26
+ (path) => matcher.match(path) && !GlobExclusions.ignores(excludes, path)
27
+ )
28
+ .map((path) => join(absoluteRoot, path));
37
29
  }
38
30
 
39
31
  async function expandDirectory(
@@ -8,10 +8,7 @@ import { basename, dirname, join } from "node:path";
8
8
 
9
9
  const POLL_MS = 1_000;
10
10
 
11
- /**
12
- * A burst coalesces into one event, and the read it triggers can land inside
13
- * the burst — so look once more after it, or the last write waits on the poll.
14
- */
11
+ /** A burst arrives as one event, so look again shortly after to catch its last write. */
15
12
  const SETTLE_MS = 25;
16
13
 
17
14
  function signatureOf(path: string): string {
@@ -21,7 +18,7 @@ function signatureOf(path: string): string {
21
18
  : `${stats.mtimeMs}:${stats.size}:${stats.ino}`;
22
19
  }
23
20
 
24
- /** Reports a change only once the path itself moved, so a watcher firing for its neighbours costs nothing. */
21
+ /** Calls `onChange` only when the path's stat signature changed. */
25
22
  function tracker(path: string, onChange: () => void): () => void {
26
23
  let signature = signatureOf(path);
27
24
  return (): void => {
@@ -34,11 +31,7 @@ function tracker(path: string, onChange: () => void): () => void {
34
31
  };
35
32
  }
36
33
 
37
- /**
38
- * `fs.watch` reports nothing at all on some filesystems and network mounts, so
39
- * the poll beside it is not optional. The poll also re-arms the watcher, which
40
- * a directory that does not exist yet refuses to give.
41
- */
34
+ /** `fs.watch` is silent on some filesystems, so a poll always runs too and re-arms a watcher that failed. */
42
35
  function follow(
43
36
  watched: string,
44
37
  entry: string | undefined,
@@ -86,27 +79,31 @@ function follow(
86
79
  };
87
80
  }
88
81
 
89
- /** Fires when one file appears, grows, or goes away; the directory is watched, so the file need not exist yet. */
82
+ /** Watches the parent directory, so the file need not exist yet. `onLook` sees every stat, changed or not. */
90
83
  function file(
91
84
  path: string,
92
85
  onChange: () => void,
93
- pollMs: number = POLL_MS
86
+ pollMs: number = POLL_MS,
87
+ onLook?: () => void
94
88
  ): () => void {
95
- const check = tracker(path, onChange);
89
+ const track = tracker(path, onChange);
90
+ const check = (): void => {
91
+ track();
92
+ onLook?.();
93
+ };
96
94
  return follow(dirname(path), basename(path), check, check, pollMs);
97
95
  }
98
96
 
99
- /** Fires when a directory gains or loses an entry, or when anything inside it is written. */
97
+ /** Fires on entries added, removed or written. The poll only sees adds and removes. */
100
98
  function directory(
101
99
  path: string,
102
100
  onChange: () => void,
103
101
  pollMs: number = POLL_MS
104
102
  ): () => void {
105
- // Writing a file moves nothing on the directory holding it, so the poll answers only for entries coming and going.
106
103
  return follow(path, undefined, onChange, tracker(path, onChange), pollMs);
107
104
  }
108
105
 
109
- /** The directories directly inside `path`, or none when it does not exist. */
106
+ /** Empty when `path` is missing. */
110
107
  function subdirectories(path: string): readonly string[] {
111
108
  try {
112
109
  return readdirSync(path, { withFileTypes: true })
@@ -117,5 +114,4 @@ function subdirectories(path: string): readonly string[] {
117
114
  }
118
115
  }
119
116
 
120
- /** Watching paths other processes write: an `fs.watch` that may do nothing, backed by a poll that always does. */
121
117
  export const FileWatch = { file, directory, subdirectories };
@@ -26,7 +26,6 @@ function formatElapsed(ms: number): string {
26
26
  : `${seconds}s`;
27
27
  }
28
28
 
29
- /** How full the context window reads, as a verdict each frontend colours. */
30
29
  export type ContextFill = "ok" | "warn" | "full";
31
30
 
32
31
  function contextFill(percent: number): ContextFill {
@@ -46,7 +45,7 @@ function scaleBytes(total: number): readonly [string, string] {
46
45
  return [value.toFixed(2).replace(/\.?0+$/u, ""), unit];
47
46
  }
48
47
 
49
- /** A byte count as a person reads it, unit spaced: `40 bytes`, `240 KB`, `1.5 MB`. */
48
+ /** `40 bytes`, `240 KB`, `1.5 MB`. */
50
49
  function bytes(total: number): string {
51
50
  if (total < 1024) {
52
51
  return `${total} bytes`;
@@ -55,7 +54,7 @@ function bytes(total: number): string {
55
54
  return `${value} ${unit}`;
56
55
  }
57
56
 
58
- /** The same count where a title has no room for the space: `40B`, `240KB`, `1.5MB`. */
57
+ /** `40B`, `240KB`, `1.5MB`. */
59
58
  function bytesCompact(total: number): string {
60
59
  if (total < 1024) {
61
60
  return `${total}B`;