byterover-cli 0.4.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (474) hide show
  1. package/README.md +1 -9
  2. package/dist/commands/curate.d.ts +1 -3
  3. package/dist/commands/curate.js +14 -51
  4. package/dist/commands/main.d.ts +8 -0
  5. package/dist/commands/main.js +29 -8
  6. package/dist/commands/query.d.ts +1 -3
  7. package/dist/commands/query.js +8 -35
  8. package/dist/config/context-tree-domains.d.ts +5 -0
  9. package/dist/config/context-tree-domains.js +6 -1
  10. package/dist/config/environment.js +9 -9
  11. package/dist/constants.d.ts +14 -0
  12. package/dist/constants.js +18 -0
  13. package/dist/core/domain/cipher/agent/agent-info.d.ts +199 -0
  14. package/dist/core/domain/cipher/agent/agent-info.js +143 -0
  15. package/dist/core/domain/cipher/agent/agent-registry.d.ts +96 -0
  16. package/dist/core/domain/cipher/agent/agent-registry.js +254 -0
  17. package/dist/core/domain/cipher/agent/index.d.ts +4 -1
  18. package/dist/core/domain/cipher/agent/index.js +7 -1
  19. package/dist/core/domain/cipher/agent-events/types.d.ts +355 -2
  20. package/dist/core/domain/cipher/agent-events/types.js +11 -0
  21. package/dist/core/domain/cipher/errors/error-normalizer.d.ts +156 -0
  22. package/dist/core/domain/cipher/errors/error-normalizer.js +379 -0
  23. package/dist/core/domain/cipher/errors/file-system-error.d.ts +2 -1
  24. package/dist/core/domain/cipher/errors/file-system-error.js +3 -2
  25. package/dist/core/domain/cipher/errors/system-prompt-error-codes.d.ts +79 -0
  26. package/dist/core/domain/cipher/errors/system-prompt-error-codes.js +80 -0
  27. package/dist/core/domain/cipher/errors/system-prompt-error.d.ts +114 -0
  28. package/dist/core/domain/cipher/errors/system-prompt-error.js +144 -0
  29. package/dist/core/domain/cipher/file-system/types.d.ts +57 -0
  30. package/dist/core/domain/cipher/llm/error-codes.d.ts +51 -0
  31. package/dist/core/domain/cipher/llm/error-codes.js +51 -0
  32. package/dist/core/domain/cipher/llm/index.d.ts +9 -0
  33. package/dist/core/domain/cipher/llm/index.js +13 -0
  34. package/dist/core/domain/cipher/llm/registry.d.ts +113 -0
  35. package/dist/core/domain/cipher/llm/registry.js +244 -0
  36. package/dist/core/domain/cipher/llm/schemas.d.ts +155 -0
  37. package/dist/core/domain/cipher/llm/schemas.js +151 -0
  38. package/dist/core/domain/cipher/llm/types.d.ts +121 -0
  39. package/dist/core/domain/cipher/llm/types.js +60 -0
  40. package/dist/core/domain/cipher/storage/message-storage-types.d.ts +114 -5
  41. package/dist/core/domain/cipher/streaming/types.d.ts +119 -0
  42. package/dist/core/domain/cipher/streaming/types.js +16 -0
  43. package/dist/core/domain/cipher/system-prompt/types.d.ts +44 -0
  44. package/dist/core/domain/cipher/todos/types.d.ts +34 -0
  45. package/dist/core/domain/cipher/tools/constants.d.ts +5 -2
  46. package/dist/core/domain/cipher/tools/constants.js +5 -2
  47. package/dist/core/domain/cipher/tools/types.d.ts +31 -0
  48. package/dist/core/domain/errors/connection-error.d.ts +33 -0
  49. package/dist/core/domain/errors/connection-error.js +54 -0
  50. package/dist/core/domain/errors/core-process-error.d.ts +27 -0
  51. package/dist/core/domain/errors/core-process-error.js +43 -0
  52. package/dist/core/domain/errors/task-error.d.ts +64 -0
  53. package/dist/core/domain/errors/task-error.js +116 -0
  54. package/dist/core/domain/errors/transport-error.d.ts +72 -0
  55. package/dist/core/domain/errors/transport-error.js +114 -0
  56. package/dist/core/domain/instance/index.d.ts +1 -0
  57. package/dist/core/domain/instance/index.js +1 -0
  58. package/dist/core/domain/instance/types.d.ts +57 -0
  59. package/dist/core/domain/instance/types.js +72 -0
  60. package/dist/core/domain/knowledge/directory-manager.d.ts +16 -0
  61. package/dist/core/domain/knowledge/directory-manager.js +31 -0
  62. package/dist/core/domain/transport/index.d.ts +2 -0
  63. package/dist/core/domain/transport/index.js +2 -0
  64. package/dist/core/domain/transport/schemas.d.ts +1149 -0
  65. package/dist/core/domain/transport/schemas.js +554 -0
  66. package/dist/core/domain/transport/types.d.ts +67 -0
  67. package/dist/core/domain/transport/types.js +7 -0
  68. package/dist/core/interfaces/cipher/cipher-services.d.ts +15 -3
  69. package/dist/core/interfaces/cipher/i-chat-session.d.ts +47 -5
  70. package/dist/core/interfaces/cipher/i-cipher-agent.d.ts +39 -4
  71. package/dist/core/interfaces/cipher/i-content-generator.d.ts +3 -5
  72. package/dist/core/interfaces/cipher/i-file-system.d.ts +12 -1
  73. package/dist/core/interfaces/cipher/i-llm-service.d.ts +4 -5
  74. package/dist/core/interfaces/cipher/i-todo-storage.d.ts +24 -0
  75. package/dist/core/interfaces/cipher/i-todo-storage.js +1 -0
  76. package/dist/core/interfaces/cipher/i-tool-plugin.d.ts +90 -0
  77. package/dist/core/interfaces/cipher/i-tool-plugin.js +1 -0
  78. package/dist/core/interfaces/cipher/i-tool-provider.d.ts +3 -2
  79. package/dist/core/interfaces/cipher/i-tool-scheduler.d.ts +4 -0
  80. package/dist/core/interfaces/cipher/index.d.ts +35 -0
  81. package/dist/core/interfaces/cipher/index.js +11 -0
  82. package/dist/core/interfaces/cipher/message-factory.d.ts +155 -0
  83. package/dist/core/interfaces/cipher/message-factory.js +252 -0
  84. package/dist/core/interfaces/cipher/message-type-guards.d.ts +139 -0
  85. package/dist/core/interfaces/cipher/message-type-guards.js +173 -0
  86. package/dist/core/interfaces/cipher/message-types.d.ts +279 -5
  87. package/dist/core/interfaces/cipher/message-types.js +6 -0
  88. package/dist/core/interfaces/cipher/sanitization-types.d.ts +147 -0
  89. package/dist/core/interfaces/cipher/sanitization-types.js +46 -0
  90. package/dist/core/interfaces/executor/i-curate-executor.d.ts +34 -0
  91. package/dist/core/interfaces/executor/i-curate-executor.js +1 -0
  92. package/dist/core/interfaces/executor/i-query-executor.d.ts +32 -0
  93. package/dist/core/interfaces/executor/i-query-executor.js +1 -0
  94. package/dist/core/interfaces/executor/index.d.ts +2 -0
  95. package/dist/core/interfaces/executor/index.js +2 -0
  96. package/dist/core/interfaces/instance/i-instance-discovery.d.ts +45 -0
  97. package/dist/core/interfaces/instance/i-instance-discovery.js +1 -0
  98. package/dist/core/interfaces/instance/i-instance-manager.d.ts +58 -0
  99. package/dist/core/interfaces/instance/i-instance-manager.js +1 -0
  100. package/dist/core/interfaces/instance/index.d.ts +2 -0
  101. package/dist/core/interfaces/instance/index.js +2 -0
  102. package/dist/core/interfaces/noop-implementations.d.ts +53 -0
  103. package/dist/core/interfaces/noop-implementations.js +62 -0
  104. package/dist/core/interfaces/transport/i-transport-client.d.ts +97 -0
  105. package/dist/core/interfaces/transport/i-transport-client.js +1 -0
  106. package/dist/core/interfaces/transport/i-transport-server.d.ts +93 -0
  107. package/dist/core/interfaces/transport/i-transport-server.js +1 -0
  108. package/dist/core/interfaces/transport/index.d.ts +2 -0
  109. package/dist/core/interfaces/transport/index.js +2 -0
  110. package/dist/infra/cipher/agent/agent-error-codes.d.ts +16 -0
  111. package/dist/infra/cipher/agent/agent-error-codes.js +17 -0
  112. package/dist/infra/cipher/agent/agent-error.d.ts +54 -0
  113. package/dist/infra/cipher/agent/agent-error.js +79 -0
  114. package/dist/infra/cipher/agent/agent-schemas.d.ts +264 -0
  115. package/dist/infra/cipher/agent/agent-schemas.js +97 -0
  116. package/dist/infra/cipher/agent/agent-state-manager.d.ts +140 -0
  117. package/dist/infra/cipher/agent/agent-state-manager.js +275 -0
  118. package/dist/infra/cipher/agent/base-agent.d.ts +118 -0
  119. package/dist/infra/cipher/agent/base-agent.js +240 -0
  120. package/dist/infra/cipher/agent/cipher-agent.d.ts +165 -0
  121. package/dist/infra/cipher/agent/cipher-agent.js +546 -0
  122. package/dist/infra/cipher/agent/index.d.ts +22 -0
  123. package/dist/infra/cipher/agent/index.js +24 -0
  124. package/dist/infra/cipher/agent/service-initializer.d.ts +79 -0
  125. package/dist/infra/cipher/{agent-service-factory.js → agent/service-initializer.js} +117 -68
  126. package/dist/infra/cipher/agent/types.d.ts +35 -0
  127. package/dist/infra/cipher/agent/types.js +1 -0
  128. package/dist/infra/cipher/blob/blob-reference-resolver.d.ts +107 -0
  129. package/dist/infra/cipher/blob/blob-reference-resolver.js +228 -0
  130. package/dist/infra/cipher/blob/blob-reference-utils.d.ts +117 -0
  131. package/dist/infra/cipher/blob/blob-reference-utils.js +230 -0
  132. package/dist/infra/cipher/consumer/consumer-lock.js +1 -0
  133. package/dist/infra/cipher/consumer/consumer-service.js +1 -0
  134. package/dist/infra/cipher/consumer/execution-consumer.d.ts +6 -1
  135. package/dist/infra/cipher/consumer/execution-consumer.js +54 -16
  136. package/dist/infra/cipher/consumer/index.d.ts +1 -1
  137. package/dist/infra/cipher/consumer/index.js +2 -1
  138. package/dist/infra/cipher/consumer/queue-polling-service.js +1 -0
  139. package/dist/infra/cipher/file-system/binary-utils.d.ts +43 -0
  140. package/dist/infra/cipher/file-system/binary-utils.js +164 -0
  141. package/dist/infra/cipher/file-system/context-tree-file-system-factory.d.ts +9 -0
  142. package/dist/infra/cipher/file-system/context-tree-file-system-factory.js +24 -0
  143. package/dist/infra/cipher/file-system/file-system-service.d.ts +17 -1
  144. package/dist/infra/cipher/file-system/file-system-service.js +327 -36
  145. package/dist/infra/cipher/file-system/path-validator.d.ts +32 -0
  146. package/dist/infra/cipher/file-system/path-validator.js +111 -6
  147. package/dist/infra/cipher/interactive-loop.js +41 -33
  148. package/dist/infra/cipher/llm/capability-cache.d.ts +87 -0
  149. package/dist/infra/cipher/llm/capability-cache.js +125 -0
  150. package/dist/infra/cipher/llm/context/compaction/compaction-service.d.ts +32 -0
  151. package/dist/infra/cipher/llm/context/compaction/compaction-service.js +44 -3
  152. package/dist/infra/cipher/llm/context/compression/enhanced-compaction.d.ts +112 -0
  153. package/dist/infra/cipher/llm/context/compression/enhanced-compaction.js +175 -0
  154. package/dist/infra/cipher/llm/context/compression/filter-compacted.d.ts +83 -0
  155. package/dist/infra/cipher/llm/context/compression/filter-compacted.js +150 -0
  156. package/dist/infra/cipher/llm/context/compression/index.d.ts +5 -0
  157. package/dist/infra/cipher/llm/context/compression/index.js +6 -0
  158. package/dist/infra/cipher/llm/context/compression/reactive-overflow.d.ts +107 -0
  159. package/dist/infra/cipher/llm/context/compression/reactive-overflow.js +272 -0
  160. package/dist/infra/cipher/llm/context/context-manager.d.ts +47 -1
  161. package/dist/infra/cipher/llm/context/context-manager.js +129 -0
  162. package/dist/infra/cipher/llm/context/utils.js +17 -4
  163. package/dist/infra/cipher/llm/generators/byterover-content-generator.js +4 -2
  164. package/dist/infra/cipher/llm/internal-llm-service.d.ts +50 -17
  165. package/dist/infra/cipher/llm/internal-llm-service.js +273 -50
  166. package/dist/infra/cipher/llm/openrouter-llm-service.d.ts +6 -8
  167. package/dist/infra/cipher/llm/openrouter-llm-service.js +14 -16
  168. package/dist/infra/cipher/llm/retry/retry-policy.d.ts +1 -0
  169. package/dist/infra/cipher/llm/retry/retry-policy.js +11 -0
  170. package/dist/infra/cipher/llm/retry/retry-with-backoff.js +3 -2
  171. package/dist/infra/cipher/llm/sanitization/base64-utils.d.ts +102 -0
  172. package/dist/infra/cipher/llm/sanitization/base64-utils.js +182 -0
  173. package/dist/infra/cipher/llm/sanitization/index.d.ts +12 -0
  174. package/dist/infra/cipher/llm/sanitization/index.js +13 -0
  175. package/dist/infra/cipher/llm/sanitization/tool-sanitizer.d.ts +74 -0
  176. package/dist/infra/cipher/llm/sanitization/tool-sanitizer.js +398 -0
  177. package/dist/infra/cipher/llm/stream-processor.d.ts +158 -0
  178. package/dist/infra/cipher/llm/stream-processor.js +276 -0
  179. package/dist/infra/cipher/llm/tokenizers/claude-tokenizer.d.ts +13 -20
  180. package/dist/infra/cipher/llm/tokenizers/claude-tokenizer.js +17 -24
  181. package/dist/infra/cipher/llm/tokenizers/gemini-tokenizer.d.ts +12 -11
  182. package/dist/infra/cipher/llm/tokenizers/gemini-tokenizer.js +16 -15
  183. package/dist/infra/cipher/llm/tokenizers/openrouter-tokenizer.d.ts +15 -7
  184. package/dist/infra/cipher/llm/tokenizers/openrouter-tokenizer.js +22 -10
  185. package/dist/infra/cipher/llm/tool-output-processor.d.ts +51 -0
  186. package/dist/infra/cipher/llm/tool-output-processor.js +139 -0
  187. package/dist/infra/cipher/process/command-validator.d.ts +23 -0
  188. package/dist/infra/cipher/process/command-validator.js +75 -0
  189. package/dist/infra/cipher/process/path-utils.d.ts +66 -0
  190. package/dist/infra/cipher/process/path-utils.js +94 -0
  191. package/dist/infra/cipher/process/process-service.d.ts +32 -0
  192. package/dist/infra/cipher/process/process-service.js +98 -17
  193. package/dist/infra/cipher/session/chat-session.d.ts +56 -7
  194. package/dist/infra/cipher/session/chat-session.js +163 -13
  195. package/dist/infra/cipher/session/index.d.ts +1 -0
  196. package/dist/infra/cipher/session/index.js +2 -0
  197. package/dist/infra/cipher/session/message-queue.d.ts +65 -0
  198. package/dist/infra/cipher/session/message-queue.js +90 -0
  199. package/dist/infra/cipher/session/session-manager.d.ts +106 -5
  200. package/dist/infra/cipher/session/session-manager.js +254 -7
  201. package/dist/infra/cipher/session/session-status.d.ts +137 -0
  202. package/dist/infra/cipher/session/session-status.js +184 -0
  203. package/dist/infra/cipher/session/title-generator.d.ts +8 -0
  204. package/dist/infra/cipher/session/title-generator.js +31 -0
  205. package/dist/infra/cipher/storage/message-storage-service.d.ts +65 -2
  206. package/dist/infra/cipher/storage/message-storage-service.js +300 -54
  207. package/dist/infra/cipher/storage/tool-part-factory.d.ts +116 -0
  208. package/dist/infra/cipher/storage/tool-part-factory.js +197 -0
  209. package/dist/infra/cipher/system-prompt/contributor-schemas.d.ts +516 -0
  210. package/dist/infra/cipher/system-prompt/contributor-schemas.js +85 -0
  211. package/dist/infra/cipher/system-prompt/contributors/agent-prompt-contributor.d.ts +59 -0
  212. package/dist/infra/cipher/system-prompt/contributors/agent-prompt-contributor.js +131 -0
  213. package/dist/infra/cipher/system-prompt/contributors/companion-contributor.d.ts +54 -0
  214. package/dist/infra/cipher/system-prompt/contributors/companion-contributor.js +107 -0
  215. package/dist/infra/cipher/system-prompt/contributors/context-tree-structure-contributor.d.ts +68 -0
  216. package/dist/infra/cipher/system-prompt/contributors/context-tree-structure-contributor.js +179 -0
  217. package/dist/infra/cipher/system-prompt/contributors/datetime-contributor.d.ts +25 -0
  218. package/dist/infra/cipher/system-prompt/contributors/datetime-contributor.js +29 -0
  219. package/dist/infra/cipher/system-prompt/contributors/environment-contributor.d.ts +25 -0
  220. package/dist/infra/cipher/system-prompt/contributors/environment-contributor.js +54 -0
  221. package/dist/infra/cipher/system-prompt/contributors/file-contributor.d.ts +60 -0
  222. package/dist/infra/cipher/system-prompt/contributors/file-contributor.js +128 -0
  223. package/dist/infra/cipher/system-prompt/contributors/index.d.ts +13 -0
  224. package/dist/infra/cipher/system-prompt/contributors/index.js +8 -0
  225. package/dist/infra/cipher/system-prompt/contributors/memory-contributor.d.ts +40 -0
  226. package/dist/infra/cipher/system-prompt/contributors/memory-contributor.js +56 -0
  227. package/dist/infra/cipher/system-prompt/contributors/static-contributor.d.ts +26 -0
  228. package/dist/infra/cipher/system-prompt/contributors/static-contributor.js +31 -0
  229. package/dist/infra/cipher/system-prompt/environment-context-builder.d.ts +112 -0
  230. package/dist/infra/cipher/system-prompt/environment-context-builder.js +256 -0
  231. package/dist/infra/cipher/system-prompt/prompt-cache.d.ts +102 -0
  232. package/dist/infra/cipher/system-prompt/prompt-cache.js +156 -0
  233. package/dist/infra/cipher/system-prompt/schemas.d.ts +151 -0
  234. package/dist/infra/cipher/system-prompt/schemas.js +94 -0
  235. package/dist/infra/cipher/system-prompt/system-prompt-manager.d.ts +136 -0
  236. package/dist/infra/cipher/system-prompt/system-prompt-manager.js +307 -0
  237. package/dist/infra/cipher/todos/todo-storage-service.d.ts +26 -0
  238. package/dist/infra/cipher/todos/todo-storage-service.js +28 -0
  239. package/dist/infra/cipher/tools/core-tool-scheduler.js +5 -1
  240. package/dist/infra/cipher/tools/default-policy-rules.js +1 -1
  241. package/dist/infra/cipher/tools/implementations/bash-exec-tool.d.ts +1 -0
  242. package/dist/infra/cipher/tools/implementations/bash-exec-tool.js +27 -10
  243. package/dist/infra/cipher/tools/implementations/bash-output-tool.js +1 -5
  244. package/dist/infra/cipher/tools/implementations/batch-tool.d.ts +12 -0
  245. package/dist/infra/cipher/tools/implementations/batch-tool.js +142 -0
  246. package/dist/infra/cipher/tools/implementations/curate-tool.js +195 -68
  247. package/dist/infra/cipher/tools/implementations/list-directory-tool.d.ts +12 -0
  248. package/dist/infra/cipher/tools/implementations/list-directory-tool.js +52 -0
  249. package/dist/infra/cipher/tools/implementations/read-file-tool.d.ts +8 -1
  250. package/dist/infra/cipher/tools/implementations/read-file-tool.js +17 -7
  251. package/dist/infra/cipher/tools/implementations/read-todos-tool.d.ts +11 -0
  252. package/dist/infra/cipher/tools/implementations/read-todos-tool.js +39 -0
  253. package/dist/infra/cipher/tools/implementations/{detect-domains-tool.d.ts → spec-analyze-tool.d.ts} +1 -1
  254. package/dist/infra/cipher/tools/implementations/{detect-domains-tool.js → spec-analyze-tool.js} +9 -7
  255. package/dist/infra/cipher/tools/implementations/task-tool.d.ts +34 -0
  256. package/dist/infra/cipher/tools/implementations/task-tool.js +207 -0
  257. package/dist/infra/cipher/tools/implementations/write-todos-tool.d.ts +4 -1
  258. package/dist/infra/cipher/tools/implementations/write-todos-tool.js +19 -63
  259. package/dist/infra/cipher/tools/index.d.ts +1 -1
  260. package/dist/infra/cipher/tools/index.js +1 -1
  261. package/dist/infra/cipher/tools/plugins/index.d.ts +3 -0
  262. package/dist/infra/cipher/tools/plugins/index.js +2 -0
  263. package/dist/infra/cipher/tools/plugins/logging-plugin.d.ts +28 -0
  264. package/dist/infra/cipher/tools/plugins/logging-plugin.js +66 -0
  265. package/dist/infra/cipher/tools/plugins/plugin-manager.d.ts +81 -0
  266. package/dist/infra/cipher/tools/plugins/plugin-manager.js +122 -0
  267. package/dist/infra/cipher/tools/streaming/index.d.ts +1 -0
  268. package/dist/infra/cipher/tools/streaming/index.js +1 -0
  269. package/dist/infra/cipher/tools/streaming/metadata-handler.d.ts +31 -0
  270. package/dist/infra/cipher/tools/streaming/metadata-handler.js +39 -0
  271. package/dist/infra/cipher/tools/tool-description-loader.d.ts +57 -0
  272. package/dist/infra/cipher/tools/tool-description-loader.js +108 -0
  273. package/dist/infra/cipher/tools/tool-manager.d.ts +38 -4
  274. package/dist/infra/cipher/tools/tool-manager.js +107 -11
  275. package/dist/infra/cipher/tools/tool-provider-getter.d.ts +6 -0
  276. package/dist/infra/cipher/tools/tool-provider-getter.js +1 -0
  277. package/dist/infra/cipher/tools/tool-provider.d.ts +32 -7
  278. package/dist/infra/cipher/tools/tool-provider.js +81 -25
  279. package/dist/infra/cipher/tools/tool-registry.d.ts +23 -0
  280. package/dist/infra/cipher/tools/tool-registry.js +58 -16
  281. package/dist/infra/context-tree/file-context-tree-snapshot-service.js +10 -4
  282. package/dist/infra/context-tree/file-context-tree-writer-service.d.ts +4 -3
  283. package/dist/infra/context-tree/file-context-tree-writer-service.js +6 -4
  284. package/dist/infra/context-tree/path-utils.d.ts +7 -0
  285. package/dist/infra/context-tree/path-utils.js +7 -0
  286. package/dist/infra/core/executors/curate-executor.d.ts +35 -0
  287. package/dist/infra/core/executors/curate-executor.js +123 -0
  288. package/dist/infra/core/executors/index.d.ts +2 -0
  289. package/dist/infra/core/executors/index.js +2 -0
  290. package/dist/infra/core/executors/query-executor.d.ts +23 -0
  291. package/dist/infra/core/executors/query-executor.js +51 -0
  292. package/dist/infra/core/task-processor.d.ts +81 -0
  293. package/dist/infra/core/task-processor.js +115 -0
  294. package/dist/infra/instance/file-instance-discovery.d.ts +31 -0
  295. package/dist/infra/instance/file-instance-discovery.js +84 -0
  296. package/dist/infra/instance/file-instance-manager.d.ts +46 -0
  297. package/dist/infra/instance/file-instance-manager.js +123 -0
  298. package/dist/infra/instance/index.d.ts +3 -0
  299. package/dist/infra/instance/index.js +3 -0
  300. package/dist/infra/instance/process-utils.d.ts +14 -0
  301. package/dist/infra/instance/process-utils.js +39 -0
  302. package/dist/infra/process/agent-worker.d.ts +20 -0
  303. package/dist/infra/process/agent-worker.js +602 -0
  304. package/dist/infra/process/index.d.ts +12 -0
  305. package/dist/infra/process/index.js +11 -0
  306. package/dist/infra/process/ipc-types.d.ts +55 -0
  307. package/dist/infra/process/ipc-types.js +12 -0
  308. package/dist/infra/process/process-manager.d.ts +154 -0
  309. package/dist/infra/process/process-manager.js +471 -0
  310. package/dist/infra/process/task-queue-manager.d.ts +123 -0
  311. package/dist/infra/process/task-queue-manager.js +226 -0
  312. package/dist/infra/process/transport-handlers.d.ts +124 -0
  313. package/dist/infra/process/transport-handlers.js +348 -0
  314. package/dist/infra/process/transport-worker.d.ts +20 -0
  315. package/dist/infra/process/transport-worker.js +168 -0
  316. package/dist/infra/repl/commands/curate-command.js +0 -5
  317. package/dist/infra/repl/commands/query-command.js +0 -3
  318. package/dist/infra/repl/repl-startup.d.ts +4 -0
  319. package/dist/infra/repl/repl-startup.js +8 -1
  320. package/dist/infra/repl/transport-client-helper.d.ts +9 -0
  321. package/dist/infra/repl/transport-client-helper.js +96 -0
  322. package/dist/infra/transport/index.d.ts +4 -0
  323. package/dist/infra/transport/index.js +4 -0
  324. package/dist/infra/transport/port-utils.d.ts +42 -0
  325. package/dist/infra/transport/port-utils.js +84 -0
  326. package/dist/infra/transport/socket-io-transport-client.d.ts +45 -0
  327. package/dist/infra/transport/socket-io-transport-client.js +270 -0
  328. package/dist/infra/transport/socket-io-transport-server.d.ts +35 -0
  329. package/dist/infra/transport/socket-io-transport-server.js +207 -0
  330. package/dist/infra/transport/transport-client-factory.d.ts +76 -0
  331. package/dist/infra/transport/transport-client-factory.js +168 -0
  332. package/dist/infra/transport/transport-factory.d.ts +33 -0
  333. package/dist/infra/transport/transport-factory.js +59 -0
  334. package/dist/infra/usecase/curate-use-case.d.ts +8 -55
  335. package/dist/infra/usecase/curate-use-case.js +71 -262
  336. package/dist/infra/usecase/init-use-case.js +3 -2
  337. package/dist/infra/usecase/query-use-case.d.ts +18 -45
  338. package/dist/infra/usecase/query-use-case.js +250 -326
  339. package/dist/infra/usecase/status-use-case.js +1 -1
  340. package/dist/resources/prompts/{curate-context-tree-curation.yml → curate.yml} +25 -22
  341. package/dist/resources/prompts/explore.yml +78 -0
  342. package/dist/resources/prompts/plan.yml +114 -0
  343. package/dist/resources/prompts/reflection.yml +1 -1
  344. package/dist/resources/prompts/system-prompt.yml +15 -8
  345. package/dist/resources/prompts/tool-outputs.yml +0 -5
  346. package/dist/resources/tools/bash_exec.txt +98 -0
  347. package/dist/resources/tools/bash_output.txt +40 -0
  348. package/dist/resources/tools/batch.txt +28 -0
  349. package/dist/resources/tools/create_knowledge_topic.txt +23 -0
  350. package/dist/resources/tools/curate.txt +22 -0
  351. package/dist/resources/tools/delete_memory.txt +1 -0
  352. package/dist/resources/tools/detect_domains.txt +11 -0
  353. package/dist/resources/tools/edit_file.txt +1 -0
  354. package/dist/resources/tools/edit_memory.txt +1 -0
  355. package/dist/resources/tools/glob_files.txt +20 -0
  356. package/dist/resources/tools/grep_content.txt +18 -0
  357. package/dist/resources/tools/kill_process.txt +16 -0
  358. package/dist/resources/tools/list_directory.txt +16 -0
  359. package/dist/resources/tools/list_memories.txt +1 -0
  360. package/dist/resources/tools/read_file.txt +31 -0
  361. package/dist/resources/tools/read_memory.txt +1 -0
  362. package/dist/resources/tools/read_todos.txt +17 -0
  363. package/dist/resources/tools/search_history.txt +1 -0
  364. package/dist/resources/tools/task.txt +23 -0
  365. package/dist/resources/tools/write_file.txt +1 -0
  366. package/dist/resources/tools/write_memory.txt +1 -0
  367. package/dist/resources/tools/write_todos.txt +29 -0
  368. package/dist/tui/app.js +9 -13
  369. package/dist/tui/components/command-details.d.ts +14 -0
  370. package/dist/tui/components/command-details.js +35 -0
  371. package/dist/tui/components/execution/execution-changes.d.ts +5 -0
  372. package/dist/tui/components/execution/execution-changes.js +19 -4
  373. package/dist/tui/components/execution/execution-content.d.ts +4 -2
  374. package/dist/tui/components/execution/execution-content.js +26 -13
  375. package/dist/tui/components/execution/execution-input.js +3 -3
  376. package/dist/tui/components/execution/execution-progress.d.ts +2 -2
  377. package/dist/tui/components/execution/execution-progress.js +8 -6
  378. package/dist/tui/components/execution/log-item.d.ts +3 -4
  379. package/dist/tui/components/execution/log-item.js +2 -5
  380. package/dist/tui/components/footer.js +9 -4
  381. package/dist/tui/components/header.d.ts +3 -3
  382. package/dist/tui/components/header.js +5 -3
  383. package/dist/tui/components/index.d.ts +1 -0
  384. package/dist/tui/components/index.js +1 -0
  385. package/dist/tui/components/onboarding/copyable-prompt.d.ts +5 -3
  386. package/dist/tui/components/onboarding/copyable-prompt.js +7 -8
  387. package/dist/tui/components/onboarding/onboarding-flow.js +35 -25
  388. package/dist/tui/components/scrollable-list.js +12 -10
  389. package/dist/tui/components/suggestions.js +39 -41
  390. package/dist/tui/components/tab-bar.d.ts +2 -1
  391. package/dist/tui/components/tab-bar.js +3 -4
  392. package/dist/tui/constants.d.ts +0 -5
  393. package/dist/tui/constants.js +0 -5
  394. package/dist/tui/contexts/auth-context.js +9 -2
  395. package/dist/tui/contexts/{use-commands.js → commands-context.js} +3 -3
  396. package/dist/tui/contexts/index.d.ts +6 -1
  397. package/dist/tui/contexts/index.js +6 -1
  398. package/dist/tui/contexts/onboarding-context.d.ts +1 -1
  399. package/dist/tui/contexts/onboarding-context.js +9 -9
  400. package/dist/tui/contexts/tasks-context.d.ts +84 -0
  401. package/dist/tui/contexts/tasks-context.js +218 -0
  402. package/dist/tui/contexts/transport-context.d.ts +29 -0
  403. package/dist/tui/contexts/transport-context.js +82 -0
  404. package/dist/tui/hooks/index.d.ts +10 -6
  405. package/dist/tui/hooks/index.js +7 -6
  406. package/dist/tui/hooks/use-activity-logs.d.ts +3 -11
  407. package/dist/tui/hooks/use-activity-logs.js +87 -34
  408. package/dist/tui/hooks/use-auth-polling.d.ts +24 -0
  409. package/dist/tui/hooks/use-auth-polling.js +104 -0
  410. package/dist/tui/hooks/use-slash-command-processor.js +0 -1
  411. package/dist/tui/hooks/use-slash-completion.js +1 -1
  412. package/dist/tui/hooks/use-tab-navigation.d.ts +2 -1
  413. package/dist/tui/hooks/use-tab-navigation.js +16 -7
  414. package/dist/tui/hooks/use-terminal-breakpoint.d.ts +21 -0
  415. package/dist/tui/hooks/use-terminal-breakpoint.js +38 -0
  416. package/dist/tui/hooks/use-ui-heights.d.ts +120 -0
  417. package/dist/tui/hooks/use-ui-heights.js +88 -0
  418. package/dist/tui/providers/app-providers.js +2 -6
  419. package/dist/tui/types/commands.d.ts +0 -26
  420. package/dist/tui/types/index.d.ts +1 -1
  421. package/dist/tui/types/ui.d.ts +9 -4
  422. package/dist/tui/utils/line.d.ts +11 -0
  423. package/dist/tui/utils/line.js +16 -0
  424. package/dist/tui/utils/log.d.ts +27 -0
  425. package/dist/tui/utils/log.js +114 -0
  426. package/dist/tui/views/command-view.d.ts +7 -0
  427. package/dist/tui/views/command-view.js +103 -80
  428. package/dist/tui/views/login-view.js +7 -4
  429. package/dist/tui/views/logs-view.d.ts +13 -0
  430. package/dist/tui/views/logs-view.js +27 -52
  431. package/dist/utils/connection-error-handler.d.ts +16 -0
  432. package/dist/utils/connection-error-handler.js +49 -0
  433. package/dist/utils/crash-log.d.ts +14 -0
  434. package/dist/utils/crash-log.js +19 -0
  435. package/dist/utils/file-helpers.d.ts +14 -0
  436. package/dist/utils/file-helpers.js +21 -0
  437. package/dist/utils/global-logs-path.d.ts +11 -0
  438. package/dist/utils/global-logs-path.js +37 -0
  439. package/dist/utils/process-logger.d.ts +53 -0
  440. package/dist/utils/process-logger.js +253 -0
  441. package/dist/utils/sandbox-detector.d.ts +31 -0
  442. package/dist/utils/sandbox-detector.js +122 -0
  443. package/oclif.manifest.json +10 -198
  444. package/package.json +5 -1
  445. package/dist/commands/cipher-agent/run.d.ts +0 -142
  446. package/dist/commands/cipher-agent/run.js +0 -555
  447. package/dist/commands/cipher-agent/set-prompt.d.ts +0 -16
  448. package/dist/commands/cipher-agent/set-prompt.js +0 -58
  449. package/dist/commands/cipher-agent/show-prompt.d.ts +0 -13
  450. package/dist/commands/cipher-agent/show-prompt.js +0 -53
  451. package/dist/commands/foo.d.ts +0 -14
  452. package/dist/commands/foo.js +0 -66
  453. package/dist/infra/cipher/agent-service-factory.d.ts +0 -93
  454. package/dist/infra/cipher/cipher-agent-state-manager.d.ts +0 -63
  455. package/dist/infra/cipher/cipher-agent-state-manager.js +0 -108
  456. package/dist/infra/cipher/cipher-agent.d.ts +0 -182
  457. package/dist/infra/cipher/cipher-agent.js +0 -317
  458. package/dist/infra/cipher/system-prompt/simple-prompt-factory.d.ts +0 -106
  459. package/dist/infra/cipher/system-prompt/simple-prompt-factory.js +0 -297
  460. package/dist/infra/cipher/tools/implementations/find-knowledge-topics-tool.d.ts +0 -7
  461. package/dist/infra/cipher/tools/implementations/find-knowledge-topics-tool.js +0 -424
  462. package/dist/resources/prompts/modes/autonomous.yml +0 -9
  463. package/dist/resources/prompts/query-context-tree-retrieval.yml +0 -48
  464. package/dist/tui/contexts/consumer.d.ts +0 -31
  465. package/dist/tui/contexts/consumer.js +0 -56
  466. package/dist/tui/hooks/use-consumer.d.ts +0 -12
  467. package/dist/tui/hooks/use-consumer.js +0 -50
  468. package/dist/tui/hooks/use-queue-polling.d.ts +0 -31
  469. package/dist/tui/hooks/use-queue-polling.js +0 -90
  470. /package/dist/tui/contexts/{use-commands.d.ts → commands-context.d.ts} +0 -0
  471. /package/dist/tui/contexts/{use-mode.d.ts → mode-context.d.ts} +0 -0
  472. /package/dist/tui/contexts/{use-mode.js → mode-context.js} +0 -0
  473. /package/dist/tui/contexts/{use-theme.d.ts → theme-context.d.ts} +0 -0
  474. /package/dist/tui/contexts/{use-theme.js → theme-context.js} +0 -0
@@ -5,9 +5,113 @@ import { EOL } from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { DirectoryNotFoundError, EditOperationError, FileNotFoundError, FileTooLargeError, GlobOperationError, InvalidExtensionError, InvalidPathError, InvalidPatternError, PathBlockedError, PathNotAllowedError, PathTraversalError, ReadOperationError, SearchOperationError, ServiceNotInitializedError, StringNotFoundError, StringNotUniqueError, WriteOperationError, } from '../../../core/domain/cipher/errors/file-system-error.js';
7
7
  import { getErrorMessage } from '../../../utils/error-helpers.js';
8
+ import { getMimeType, isBinaryFile, isMediaFile, isPdfFile } from './binary-utils.js';
8
9
  import { createGitignoreFilter } from './gitignore-filter.js';
9
10
  import { collectFileMetadata, escapeIfExactMatch, extractPaths, sortFilesByRecency } from './glob-utils.js';
10
11
  import { PathValidator } from './path-validator.js';
12
+ /**
13
+ * Maximum line length for search results.
14
+ * Prevents context overflow from minified files or long lines.
15
+ */
16
+ const MAX_LINE_LENGTH = 2000;
17
+ /**
18
+ * Default number of lines to read when no limit specified.
19
+ * Prevents context overflow while providing enough content.
20
+ */
21
+ const DEFAULT_READ_LIMIT = 2000;
22
+ /**
23
+ * Number of lines to include in the preview.
24
+ */
25
+ const PREVIEW_LINES = 20;
26
+ /**
27
+ * Buffer size for binary file detection (4KB sample).
28
+ */
29
+ const BINARY_DETECTION_BUFFER_SIZE = 4096;
30
+ /**
31
+ * Whitelist of .env file patterns that are safe to read.
32
+ * These are typically example/template files without real secrets.
33
+ */
34
+ const ENV_WHITELIST = ['.env.sample', '.env.example', '.env.template', '.env.defaults'];
35
+ /**
36
+ * Truncates a line to MAX_LINE_LENGTH, adding ellipsis if truncated.
37
+ */
38
+ function truncateLine(line) {
39
+ if (line.length <= MAX_LINE_LENGTH) {
40
+ return line;
41
+ }
42
+ return line.slice(0, MAX_LINE_LENGTH) + '...';
43
+ }
44
+ /**
45
+ * Formats content with line numbers in 00001| format.
46
+ * @param lines - Array of lines to format
47
+ * @param startLine - Starting line number (1-based)
48
+ * @returns Formatted string with line numbers
49
+ */
50
+ function formatWithLineNumbers(lines, startLine) {
51
+ return lines
52
+ .map((line, index) => {
53
+ const lineNum = (startLine + index).toString().padStart(5, '0');
54
+ return `${lineNum}| ${line}`;
55
+ })
56
+ .join('\n');
57
+ }
58
+ /**
59
+ * Finds similar files in a directory for "Did you mean?" suggestions.
60
+ * @param dirPath - Directory to search in
61
+ * @param fileName - File name to find similar matches for
62
+ * @returns Array of suggested file paths (max 3)
63
+ */
64
+ async function findSimilarFiles(dirPath, fileName) {
65
+ try {
66
+ const entries = await fs.readdir(dirPath);
67
+ const lowerFileName = fileName.toLowerCase();
68
+ const suggestions = entries
69
+ .filter((entry) => {
70
+ const lowerEntry = entry.toLowerCase();
71
+ return lowerEntry.includes(lowerFileName) || lowerFileName.includes(lowerEntry);
72
+ })
73
+ .slice(0, 3)
74
+ .map((entry) => path.join(dirPath, entry));
75
+ return suggestions;
76
+ }
77
+ catch {
78
+ return [];
79
+ }
80
+ }
81
+ /**
82
+ * Default patterns to ignore when listing directories.
83
+ * Common build artifacts, caches, and IDE directories.
84
+ */
85
+ const DEFAULT_IGNORE_PATTERNS = [
86
+ 'node_modules/',
87
+ '__pycache__/',
88
+ '.git/',
89
+ 'dist/',
90
+ 'build/',
91
+ 'target/',
92
+ 'vendor/',
93
+ 'bin/',
94
+ 'obj/',
95
+ '.idea/',
96
+ '.vscode/',
97
+ '.zig-cache/',
98
+ 'zig-out/',
99
+ '.coverage/',
100
+ 'coverage/',
101
+ 'tmp/',
102
+ 'temp/',
103
+ '.cache/',
104
+ 'cache/',
105
+ 'logs/',
106
+ '.venv/',
107
+ 'venv/',
108
+ 'env/',
109
+ '.byterover/',
110
+ ];
111
+ /**
112
+ * Maximum number of files for directory listing.
113
+ */
114
+ const LIST_DIRECTORY_LIMIT = 100;
11
115
  /**
12
116
  * File system service implementation.
13
117
  * Provides secure, validated file system operations with comprehensive
@@ -200,17 +304,108 @@ export class FileSystemService {
200
304
  }
201
305
  this.initialized = true;
202
306
  }
307
+ /**
308
+ * List files and directories in a path.
309
+ */
310
+ async listDirectory(dirPath, options = {}) {
311
+ this.ensureInitialized();
312
+ // Resolve path
313
+ const resolvedPath = path.resolve(this.config.workingDirectory, dirPath || '.');
314
+ // Validate path
315
+ const validation = this.pathValidator.validate(resolvedPath, 'read');
316
+ if (!validation.valid) {
317
+ this.throwValidationError(resolvedPath, validation.error);
318
+ }
319
+ const { normalizedPath } = validation;
320
+ // Verify directory exists
321
+ try {
322
+ const stats = await fs.stat(normalizedPath);
323
+ if (!stats.isDirectory()) {
324
+ throw new DirectoryNotFoundError(normalizedPath);
325
+ }
326
+ }
327
+ catch (error) {
328
+ if (error.code === 'ENOENT') {
329
+ throw new DirectoryNotFoundError(normalizedPath);
330
+ }
331
+ throw error;
332
+ }
333
+ const maxResults = options.maxResults ?? LIST_DIRECTORY_LIMIT;
334
+ const ignorePatterns = [...DEFAULT_IGNORE_PATTERNS, ...(options.ignore ?? [])];
335
+ // Use glob with ** pattern to get all files
336
+ const files = await glob('**/*', {
337
+ absolute: false,
338
+ cwd: normalizedPath,
339
+ follow: false,
340
+ ignore: ignorePatterns.map((p) => `**/${p}**`),
341
+ nodir: true,
342
+ });
343
+ // Limit results
344
+ const truncated = files.length > maxResults;
345
+ const limitedFiles = files.slice(0, maxResults);
346
+ // Build directory structure
347
+ const dirs = new Set();
348
+ const filesByDir = new Map();
349
+ for (const file of limitedFiles) {
350
+ const dir = path.dirname(file);
351
+ const parts = dir === '.' ? [] : dir.split('/');
352
+ // Add all parent directories
353
+ for (let i = 0; i <= parts.length; i++) {
354
+ const currentDirPath = i === 0 ? '.' : parts.slice(0, i).join('/');
355
+ dirs.add(currentDirPath);
356
+ }
357
+ // Add file to its directory
358
+ if (!filesByDir.has(dir)) {
359
+ filesByDir.set(dir, []);
360
+ }
361
+ filesByDir.get(dir).push(path.basename(file));
362
+ }
363
+ // Build entries array
364
+ const entries = limitedFiles.map((file) => ({
365
+ isDirectory: false,
366
+ name: path.basename(file),
367
+ path: file,
368
+ }));
369
+ // Render tree
370
+ const tree = this.renderDirectoryTree(normalizedPath, dirs, filesByDir);
371
+ return {
372
+ count: limitedFiles.length,
373
+ entries,
374
+ tree,
375
+ truncated,
376
+ };
377
+ }
203
378
  /**
204
379
  * Read the contents of a file.
380
+ *
381
+ * Features:
382
+ * - Relative path support (resolved against working directory)
383
+ * - Image/PDF files returned as base64 attachments
384
+ * - Binary file detection and rejection
385
+ * - .env file blocking with whitelist for example files
386
+ * - XML-wrapped output for clearer LLM parsing
387
+ * - Preview metadata (first 20 lines)
205
388
  */
206
389
  async readFile(filePath, options = {}) {
207
390
  this.ensureInitialized();
391
+ // Resolve relative paths against working directory
392
+ let resolvedPath = filePath;
393
+ if (!path.isAbsolute(filePath)) {
394
+ resolvedPath = path.resolve(this.config.workingDirectory, filePath);
395
+ }
208
396
  // Validate path
209
- const validation = this.pathValidator.validate(filePath, 'read');
397
+ const validation = this.pathValidator.validate(resolvedPath, 'read');
210
398
  if (!validation.valid) {
211
- this.throwValidationError(filePath, validation.error);
399
+ this.throwValidationError(resolvedPath, validation.error);
212
400
  }
213
401
  const { normalizedPath } = validation;
402
+ // Check .env file whitelist (allow .env.sample, .env.example, etc.)
403
+ const fileName = path.basename(normalizedPath).toLowerCase();
404
+ const isEnvFile = fileName.includes('.env');
405
+ const isWhitelisted = ENV_WHITELIST.some((w) => fileName.endsWith(w));
406
+ if (isEnvFile && !isWhitelisted) {
407
+ throw new PathBlockedError(normalizedPath, 'Environment files are blocked for security. Only example files (.env.sample, .env.example) are allowed.');
408
+ }
214
409
  try {
215
410
  // Check if file exists
216
411
  let stats;
@@ -219,6 +414,13 @@ export class FileSystemService {
219
414
  }
220
415
  catch (error) {
221
416
  if (error.code === 'ENOENT') {
417
+ // Try to find similar files for suggestions
418
+ const dirPath = path.dirname(normalizedPath);
419
+ const baseName = path.basename(normalizedPath);
420
+ const suggestions = await findSimilarFiles(dirPath, baseName);
421
+ if (suggestions.length > 0) {
422
+ throw new FileNotFoundError(normalizedPath, `File not found: ${normalizedPath}\n\nDid you mean one of these?\n${suggestions.join('\n')}`);
423
+ }
222
424
  throw new FileNotFoundError(normalizedPath);
223
425
  }
224
426
  throw error;
@@ -227,30 +429,81 @@ export class FileSystemService {
227
429
  if (stats.size > this.config.maxFileSize) {
228
430
  throw new FileTooLargeError(normalizedPath, stats.size, this.config.maxFileSize);
229
431
  }
230
- // Read file
432
+ // Handle image/PDF files - return as base64 attachment
433
+ if (isMediaFile(normalizedPath)) {
434
+ const buffer = await fs.readFile(normalizedPath);
435
+ const mimeType = getMimeType(normalizedPath) ?? 'application/octet-stream';
436
+ const fileType = isPdfFile(normalizedPath) ? 'PDF' : 'Image';
437
+ const baseName = path.basename(normalizedPath);
438
+ return {
439
+ attachment: {
440
+ base64: buffer.toString('base64'),
441
+ fileName: baseName,
442
+ mimeType,
443
+ },
444
+ content: `[${fileType} file: ${baseName}]`,
445
+ encoding: 'base64',
446
+ formattedContent: `<file>\n[${fileType} file: ${baseName} (${stats.size} bytes)]\n</file>`,
447
+ lines: 1,
448
+ message: `${fileType} file read successfully. Content available as base64 attachment.`,
449
+ size: stats.size,
450
+ totalLines: 1,
451
+ truncated: false,
452
+ };
453
+ }
454
+ // Check for binary files (read first 4KB for detection)
455
+ const handle = await fs.open(normalizedPath, 'r');
456
+ const sampleBuffer = Buffer.alloc(BINARY_DETECTION_BUFFER_SIZE);
457
+ const { bytesRead } = await handle.read(sampleBuffer, 0, BINARY_DETECTION_BUFFER_SIZE, 0);
458
+ await handle.close();
459
+ if (isBinaryFile(normalizedPath, sampleBuffer.subarray(0, bytesRead))) {
460
+ throw new ReadOperationError(normalizedPath, 'Cannot read binary file. Use a hex editor or appropriate tool for binary files.');
461
+ }
462
+ // Read text file
231
463
  const encoding = (options.encoding ?? 'utf8');
232
- const content = await fs.readFile(normalizedPath, encoding);
233
- // Handle pagination
234
- const lines = content.split('\n');
235
- let selectedLines;
236
- let truncated = false;
237
- // Apply offset (1-based, like text editors)
238
- const { limit, offset: offset1 } = options;
239
- if (offset1 !== undefined || limit !== undefined) {
240
- const start = offset1 !== undefined && offset1 > 0 ? Math.max(0, offset1 - 1) : 0;
241
- const end = limit === undefined ? lines.length : start + limit;
242
- selectedLines = lines.slice(start, end);
243
- truncated = end < lines.length;
464
+ const rawContent = await fs.readFile(normalizedPath, encoding);
465
+ // Split into lines
466
+ const allLines = rawContent.split('\n');
467
+ const totalLines = allLines.length;
468
+ // Apply offset and limit (with default limit of 2000)
469
+ const offset = options.offset !== undefined && options.offset > 0 ? options.offset - 1 : 0;
470
+ const limit = options.limit ?? DEFAULT_READ_LIMIT;
471
+ const endLine = Math.min(offset + limit, totalLines);
472
+ const selectedLines = allLines.slice(offset, endLine);
473
+ const truncated = endLine < totalLines;
474
+ // Truncate long lines
475
+ const truncatedLines = selectedLines.map((line) => truncateLine(line));
476
+ // Format with line numbers
477
+ const numberedContent = formatWithLineNumbers(truncatedLines, offset + 1);
478
+ // Build informative message
479
+ const lastReadLine = offset + selectedLines.length;
480
+ let message;
481
+ if (truncated) {
482
+ const remainingLines = totalLines - lastReadLine;
483
+ message =
484
+ `Read lines ${offset + 1}-${lastReadLine} of ${totalLines} total lines. ` +
485
+ `${remainingLines} more lines available. Use offset=${lastReadLine + 1} to continue reading.`;
244
486
  }
245
487
  else {
246
- selectedLines = lines;
488
+ message = `End of file - read ${selectedLines.length} lines (${totalLines} total).`;
247
489
  }
248
- const selectedContent = selectedLines.join('\n');
490
+ // Generate preview (first N lines of selected content)
491
+ const previewLines = truncatedLines.slice(0, PREVIEW_LINES);
492
+ const preview = previewLines.join('\n');
493
+ // Wrap in XML tags for clearer LLM parsing
494
+ const truncationNote = truncated
495
+ ? `\n\n(File has more lines. Use offset=${lastReadLine + 1} to continue)`
496
+ : `\n\n(End of file - ${totalLines} lines)`;
497
+ const formattedContent = `<file>\n${numberedContent}${truncationNote}\n</file>`;
249
498
  return {
250
- content: selectedContent,
499
+ content: truncatedLines.join('\n'),
251
500
  encoding,
501
+ formattedContent,
252
502
  lines: selectedLines.length,
503
+ message,
504
+ preview,
253
505
  size: stats.size,
506
+ totalLines,
254
507
  truncated,
255
508
  };
256
509
  }
@@ -260,7 +513,8 @@ export class FileSystemService {
260
513
  error instanceof FileTooLargeError ||
261
514
  error instanceof PathNotAllowedError ||
262
515
  error instanceof PathTraversalError ||
263
- error instanceof PathBlockedError) {
516
+ error instanceof PathBlockedError ||
517
+ error instanceof ReadOperationError) {
264
518
  throw error;
265
519
  }
266
520
  // Wrap other errors
@@ -291,12 +545,24 @@ export class FileSystemService {
291
545
  if (matches === null) {
292
546
  return this.searchContentJS(pattern, options);
293
547
  }
548
+ // Collect file modification times for sorting (recent files first)
549
+ const matchesWithMtime = await Promise.all(matches.map(async (match) => {
550
+ try {
551
+ const stats = await fs.stat(match.file);
552
+ return { ...match, mtime: stats.mtime.getTime() };
553
+ }
554
+ catch {
555
+ return { ...match, mtime: 0 };
556
+ }
557
+ }));
558
+ // Sort by modification time (newest first)
559
+ matchesWithMtime.sort((a, b) => (b.mtime ?? 0) - (a.mtime ?? 0));
294
560
  // Apply maxResults limit
295
- const truncated = matches.length > maxResults;
561
+ const truncated = matchesWithMtime.length > maxResults;
296
562
  return {
297
- filesSearched: new Set(matches.map((m) => m.file)).size,
298
- matches: matches.slice(0, maxResults),
299
- totalMatches: matches.length,
563
+ filesSearched: new Set(matchesWithMtime.map((m) => m.file)).size,
564
+ matches: matchesWithMtime.slice(0, maxResults),
565
+ totalMatches: matchesWithMtime.length,
300
566
  truncated,
301
567
  };
302
568
  }
@@ -373,11 +639,11 @@ export class FileSystemService {
373
639
  if (contextLines > 0) {
374
640
  // Lines before
375
641
  for (let j = Math.max(0, lineIndex - contextLines); j < lineIndex; j++) {
376
- before.push(lines[j]);
642
+ before.push(truncateLine(lines[j]));
377
643
  }
378
644
  // Lines after
379
645
  for (let j = lineIndex + 1; j < Math.min(lines.length, lineIndex + 1 + contextLines); j++) {
380
- after.push(lines[j]);
646
+ after.push(truncateLine(lines[j]));
381
647
  }
382
648
  }
383
649
  return { after, before };
@@ -493,13 +759,38 @@ export class FileSystemService {
493
759
  if (!Number.isNaN(lineNumber)) {
494
760
  results.push({
495
761
  file: path.resolve(basePath, filePath),
496
- line: content,
762
+ line: truncateLine(content),
497
763
  lineNumber,
498
764
  });
499
765
  }
500
766
  }
501
767
  return results;
502
768
  }
769
+ /**
770
+ * Renders a directory tree as a string.
771
+ */
772
+ renderDirectoryTree(basePath, dirs, filesByDir) {
773
+ const renderDir = (dirPath, depth) => {
774
+ const indent = ' '.repeat(depth);
775
+ let output = '';
776
+ if (depth > 0) {
777
+ output += `${indent}${path.basename(dirPath)}/\n`;
778
+ }
779
+ const childIndent = ' '.repeat(depth + 1);
780
+ const children = [...dirs].filter((d) => path.dirname(d) === dirPath && d !== dirPath).sort();
781
+ // Render subdirectories first
782
+ for (const child of children) {
783
+ output += renderDir(child, depth + 1);
784
+ }
785
+ // Render files
786
+ const files = filesByDir.get(dirPath) ?? [];
787
+ for (const file of files.sort()) {
788
+ output += `${childIndent}${file}\n`;
789
+ }
790
+ return output;
791
+ };
792
+ return `${basePath}/\n${renderDir('.', 0)}`;
793
+ }
503
794
  /**
504
795
  * JavaScript-based content search implementation.
505
796
  * Used as fallback when native grep commands are unavailable or when context lines are needed.
@@ -548,7 +839,7 @@ export class FileSystemService {
548
839
  matches.push({
549
840
  context: contextLines > 0 ? context : undefined,
550
841
  file: filePath,
551
- line,
842
+ line: truncateLine(line),
552
843
  lineNumber: i + 1, // 1-based line numbers
553
844
  });
554
845
  }
@@ -600,7 +891,7 @@ export class FileSystemService {
600
891
  const child = spawn(cmd, args, { cwd, windowsHide: true });
601
892
  const chunks = [];
602
893
  if (signal) {
603
- signal.addEventListener('abort', () => child.kill());
894
+ signal.addEventListener('abort', () => child.kill(), { once: true });
604
895
  }
605
896
  child.stdout.on('data', (chunk) => chunks.push(chunk));
606
897
  child.on('error', reject);
@@ -618,24 +909,24 @@ export class FileSystemService {
618
909
  /**
619
910
  * Throws the appropriate error based on validation error message.
620
911
  */
621
- throwValidationError(path, error) {
912
+ throwValidationError(filePath, error) {
622
913
  if (error.includes('empty')) {
623
- throw new InvalidPathError(path, error);
914
+ throw new InvalidPathError(filePath, error);
624
915
  }
625
916
  if (error.includes('traversal')) {
626
- throw new PathTraversalError(path);
917
+ throw new PathTraversalError(filePath);
627
918
  }
628
919
  if (error.includes('not in allowed paths')) {
629
- throw new PathNotAllowedError(path, this.config.allowedPaths);
920
+ throw new PathNotAllowedError(filePath, this.config.allowedPaths);
630
921
  }
631
922
  if (error.includes('blocked')) {
632
- throw new PathBlockedError(path, error);
923
+ throw new PathBlockedError(filePath, error);
633
924
  }
634
925
  if (error.includes('extension')) {
635
- const ext = path.split('.').pop() ?? '';
636
- throw new InvalidExtensionError(path, ext);
926
+ const ext = filePath.split('.').pop() ?? '';
927
+ throw new InvalidExtensionError(filePath, ext);
637
928
  }
638
929
  // Fallback
639
- throw new InvalidPathError(path, error);
930
+ throw new InvalidPathError(filePath, error);
640
931
  }
641
932
  }
@@ -62,8 +62,40 @@ export declare class PathValidator {
62
62
  * Normalizes and resolves a file path to an absolute path.
63
63
  * Uses realpath to resolve symlinks if the path exists.
64
64
  *
65
+ * Handles path duplication prevention:
66
+ * - If the working directory ends with a path segment that matches the start
67
+ * of the relative file path, the resolution is adjusted to prevent duplication.
68
+ * - Example: workingDir = "/project/.brv/context-tree", filePath = ".brv/context-tree/domain/file.md"
69
+ * Without fix: "/project/.brv/context-tree/.brv/context-tree/domain/file.md" (WRONG)
70
+ * With fix: "/project/.brv/context-tree/domain/file.md" (CORRECT)
71
+ *
65
72
  * @param filePath - Path to normalize
66
73
  * @returns Normalized absolute path
67
74
  */
68
75
  private normalizeAndResolve;
76
+ /**
77
+ * For non-existent files, try to resolve the parent directory's real path
78
+ * and append the filename. This ensures consistency with existing paths
79
+ * when symlinks are involved (e.g., /var -> /private/var on macOS).
80
+ *
81
+ * @param filePath - Absolute path to a potentially non-existent file
82
+ * @returns Path with parent directory symlinks resolved
83
+ */
84
+ private resolveParentRealPath;
85
+ /**
86
+ * Resolves a relative path against the working directory while preventing
87
+ * path segment duplication.
88
+ *
89
+ * @param filePath - Relative path to resolve
90
+ * @returns Resolved absolute path
91
+ */
92
+ private resolveWithoutDuplication;
93
+ /**
94
+ * Checks if two arrays of path segments match.
95
+ *
96
+ * @param segments1 - First array of path segments
97
+ * @param segments2 - Second array of path segments
98
+ * @returns True if all segments match
99
+ */
100
+ private segmentsMatch;
69
101
  }
@@ -21,10 +21,28 @@ export class PathValidator {
21
21
  * @param config - File system configuration
22
22
  */
23
23
  constructor(config) {
24
- this.workingDirectory = path.resolve(config.workingDirectory);
24
+ // Resolve working directory, trying to resolve symlinks for consistency
25
+ const resolvedWorkingDir = path.resolve(config.workingDirectory);
26
+ try {
27
+ this.workingDirectory = realpathSync.native(resolvedWorkingDir);
28
+ }
29
+ catch {
30
+ this.workingDirectory = resolvedWorkingDir;
31
+ }
25
32
  this.blockedExtensions = new Set(config.blockedExtensions.map((ext) => ext.toLowerCase()));
26
33
  // Normalize and resolve all allowed paths to absolute paths
27
- this.normalizedAllowedPaths = config.allowedPaths.map((allowedPath) => path.resolve(this.workingDirectory, allowedPath));
34
+ // Also try to resolve symlinks for consistent comparison with realpathSync results
35
+ this.normalizedAllowedPaths = config.allowedPaths.map((allowedPath) => {
36
+ const resolved = path.resolve(this.workingDirectory, allowedPath);
37
+ try {
38
+ // Try to get the real path (resolving symlinks like /var -> /private/var on macOS)
39
+ return realpathSync.native(resolved);
40
+ }
41
+ catch {
42
+ // Path might not exist yet, use resolved path as-is
43
+ return resolved;
44
+ }
45
+ });
28
46
  // Normalize blocked paths
29
47
  this.normalizedBlockedPaths = config.blockedPaths.map((blockedPath) => path.isAbsolute(blockedPath) ? path.normalize(blockedPath) : blockedPath);
30
48
  }
@@ -164,21 +182,108 @@ export class PathValidator {
164
182
  * Normalizes and resolves a file path to an absolute path.
165
183
  * Uses realpath to resolve symlinks if the path exists.
166
184
  *
185
+ * Handles path duplication prevention:
186
+ * - If the working directory ends with a path segment that matches the start
187
+ * of the relative file path, the resolution is adjusted to prevent duplication.
188
+ * - Example: workingDir = "/project/.brv/context-tree", filePath = ".brv/context-tree/domain/file.md"
189
+ * Without fix: "/project/.brv/context-tree/.brv/context-tree/domain/file.md" (WRONG)
190
+ * With fix: "/project/.brv/context-tree/domain/file.md" (CORRECT)
191
+ *
167
192
  * @param filePath - Path to normalize
168
193
  * @returns Normalized absolute path
169
194
  */
170
195
  normalizeAndResolve(filePath) {
171
- // First resolve relative to working directory
172
- let normalizedPath = path.resolve(this.workingDirectory, filePath);
196
+ // If the path is already absolute, just normalize it.
197
+ // For relative paths, check for potential path duplication.
198
+ // This can happen when:
199
+ // 1. workingDirectory ends with a subdirectory like ".brv/context-tree"
200
+ // 2. filePath starts with the same subdirectory ".brv/context-tree/..."
201
+ // In this case, we should resolve from the parent to avoid duplication.
202
+ let normalizedPath = path.isAbsolute(filePath)
203
+ ? path.normalize(filePath)
204
+ : this.resolveWithoutDuplication(filePath);
173
205
  // Try to resolve symlinks if path exists
174
206
  try {
175
207
  // Use native variant to preserve casing on Windows
176
208
  normalizedPath = realpathSync.native(normalizedPath);
177
209
  }
178
210
  catch {
179
- // Path doesn't exist yet (e.g., for writes) - use resolved path as-is
180
- normalizedPath = path.normalize(normalizedPath);
211
+ // Path doesn't exist yet (e.g., for writes)
212
+ // Try to resolve the parent directory's real path for consistency
213
+ normalizedPath = this.resolveParentRealPath(normalizedPath);
181
214
  }
182
215
  return normalizedPath;
183
216
  }
217
+ /**
218
+ * For non-existent files, try to resolve the parent directory's real path
219
+ * and append the filename. This ensures consistency with existing paths
220
+ * when symlinks are involved (e.g., /var -> /private/var on macOS).
221
+ *
222
+ * @param filePath - Absolute path to a potentially non-existent file
223
+ * @returns Path with parent directory symlinks resolved
224
+ */
225
+ resolveParentRealPath(filePath) {
226
+ const dir = path.dirname(filePath);
227
+ const base = path.basename(filePath);
228
+ try {
229
+ // Try to resolve the parent directory
230
+ const realDir = realpathSync.native(dir);
231
+ return path.join(realDir, base);
232
+ }
233
+ catch {
234
+ // Parent doesn't exist either, try grandparent
235
+ const grandparentDir = path.dirname(dir);
236
+ const parentBase = path.basename(dir);
237
+ try {
238
+ const realGrandparent = realpathSync.native(grandparentDir);
239
+ return path.join(realGrandparent, parentBase, base);
240
+ }
241
+ catch {
242
+ // Give up and return normalized path
243
+ return path.normalize(filePath);
244
+ }
245
+ }
246
+ }
247
+ /**
248
+ * Resolves a relative path against the working directory while preventing
249
+ * path segment duplication.
250
+ *
251
+ * @param filePath - Relative path to resolve
252
+ * @returns Resolved absolute path
253
+ */
254
+ resolveWithoutDuplication(filePath) {
255
+ // Normalize the file path to handle different separators
256
+ const normalizedFilePath = path.normalize(filePath);
257
+ // Split both paths into segments for comparison
258
+ const workingDirSegments = this.workingDirectory.split(path.sep).filter(Boolean);
259
+ const filePathSegments = normalizedFilePath.split(path.sep).filter(Boolean);
260
+ // Try to find a matching suffix of workingDirectory that matches
261
+ // the prefix of filePath to detect potential duplication
262
+ for (let suffixLen = Math.min(workingDirSegments.length, filePathSegments.length); suffixLen > 0; suffixLen--) {
263
+ const workingDirSuffix = workingDirSegments.slice(-suffixLen);
264
+ const filePathPrefix = filePathSegments.slice(0, suffixLen);
265
+ // Check if the suffix of working dir matches the prefix of file path
266
+ if (this.segmentsMatch(workingDirSuffix, filePathPrefix)) {
267
+ // Found a match! Remove the duplicate prefix from file path
268
+ // and resolve from working directory
269
+ const remainingSegments = filePathSegments.slice(suffixLen);
270
+ return path.join(this.workingDirectory, ...remainingSegments);
271
+ }
272
+ }
273
+ // No duplication detected, resolve normally
274
+ return path.resolve(this.workingDirectory, filePath);
275
+ }
276
+ /**
277
+ * Checks if two arrays of path segments match.
278
+ *
279
+ * @param segments1 - First array of path segments
280
+ * @param segments2 - Second array of path segments
281
+ * @returns True if all segments match
282
+ */
283
+ segmentsMatch(segments1, segments2) {
284
+ if (segments1.length !== segments2.length) {
285
+ return false;
286
+ }
287
+ return segments1.every((seg, i) => seg === segments2[i]);
288
+ }
184
289
  }