byterover-cli 2.6.0 → 3.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 (316) hide show
  1. package/.env.production +1 -0
  2. package/README.md +240 -14
  3. package/dist/agent/core/domain/knowledge/conflict-detector.d.ts +38 -0
  4. package/dist/agent/core/domain/knowledge/conflict-detector.js +71 -0
  5. package/dist/agent/core/domain/knowledge/conflict-resolver.d.ts +17 -0
  6. package/dist/agent/core/domain/knowledge/conflict-resolver.js +118 -0
  7. package/dist/agent/core/domain/knowledge/utils.d.ts +4 -0
  8. package/dist/agent/core/domain/knowledge/utils.js +6 -0
  9. package/dist/agent/core/interfaces/i-curate-service.d.ts +6 -0
  10. package/dist/agent/infra/tools/implementations/curate-tool.d.ts +67 -34
  11. package/dist/agent/infra/tools/implementations/curate-tool.js +294 -47
  12. package/dist/agent/resources/prompts/system-prompt.yml +15 -8
  13. package/dist/agent/resources/tools/code_exec.txt +3 -0
  14. package/dist/agent/resources/tools/curate.txt +12 -3
  15. package/dist/oclif/commands/connectors/install.d.ts +2 -1
  16. package/dist/oclif/commands/connectors/install.js +38 -3
  17. package/dist/oclif/commands/curate/index.d.ts +18 -0
  18. package/dist/oclif/commands/curate/index.js +78 -1
  19. package/dist/oclif/commands/init.d.ts +12 -0
  20. package/dist/oclif/commands/init.js +75 -0
  21. package/dist/oclif/commands/locations.js +1 -1
  22. package/dist/oclif/commands/providers/connect.d.ts +31 -1
  23. package/dist/oclif/commands/providers/connect.js +307 -27
  24. package/dist/oclif/commands/pull.d.ts +1 -0
  25. package/dist/oclif/commands/pull.js +7 -0
  26. package/dist/oclif/commands/push.d.ts +1 -0
  27. package/dist/oclif/commands/push.js +8 -0
  28. package/dist/oclif/commands/review/approve.d.ts +17 -0
  29. package/dist/oclif/commands/review/approve.js +37 -0
  30. package/dist/oclif/commands/review/base-review-decision.d.ts +18 -0
  31. package/dist/oclif/commands/review/base-review-decision.js +71 -0
  32. package/dist/oclif/commands/review/pending.d.ts +13 -0
  33. package/dist/oclif/commands/review/pending.js +94 -0
  34. package/dist/oclif/commands/review/reject.d.ts +17 -0
  35. package/dist/oclif/commands/review/reject.js +38 -0
  36. package/dist/oclif/commands/space/list.d.ts +2 -2
  37. package/dist/oclif/commands/space/list.js +13 -35
  38. package/dist/oclif/commands/space/switch.d.ts +2 -7
  39. package/dist/oclif/commands/space/switch.js +13 -56
  40. package/dist/oclif/commands/status.d.ts +1 -0
  41. package/dist/oclif/commands/status.js +11 -1
  42. package/dist/oclif/commands/vc/add.d.ts +7 -0
  43. package/dist/oclif/commands/vc/add.js +29 -0
  44. package/dist/oclif/commands/vc/branch.d.ts +15 -0
  45. package/dist/oclif/commands/vc/branch.js +70 -0
  46. package/dist/oclif/commands/vc/checkout.d.ts +14 -0
  47. package/dist/oclif/commands/vc/checkout.js +47 -0
  48. package/dist/oclif/commands/vc/clone.d.ts +9 -0
  49. package/dist/oclif/commands/vc/clone.js +61 -0
  50. package/dist/oclif/commands/vc/commit.d.ts +10 -0
  51. package/dist/oclif/commands/vc/commit.js +32 -0
  52. package/dist/oclif/commands/vc/config.d.ts +10 -0
  53. package/dist/oclif/commands/vc/config.js +30 -0
  54. package/dist/oclif/commands/vc/fetch.d.ts +10 -0
  55. package/dist/oclif/commands/vc/fetch.js +42 -0
  56. package/dist/oclif/commands/vc/index.d.ts +6 -0
  57. package/dist/oclif/commands/vc/index.js +8 -0
  58. package/dist/oclif/commands/vc/init.d.ts +6 -0
  59. package/dist/oclif/commands/vc/init.js +25 -0
  60. package/dist/oclif/commands/vc/log.d.ts +13 -0
  61. package/dist/oclif/commands/vc/log.js +48 -0
  62. package/dist/oclif/commands/vc/merge.d.ts +19 -0
  63. package/dist/oclif/commands/vc/merge.js +130 -0
  64. package/dist/oclif/commands/vc/pull.d.ts +13 -0
  65. package/dist/oclif/commands/vc/pull.js +60 -0
  66. package/dist/oclif/commands/vc/push.d.ts +13 -0
  67. package/dist/oclif/commands/vc/push.js +60 -0
  68. package/dist/oclif/commands/vc/remote/add.d.ts +10 -0
  69. package/dist/oclif/commands/vc/remote/add.js +30 -0
  70. package/dist/oclif/commands/vc/remote/index.d.ts +6 -0
  71. package/dist/oclif/commands/vc/remote/index.js +16 -0
  72. package/dist/oclif/commands/vc/remote/set-url.d.ts +10 -0
  73. package/dist/oclif/commands/vc/remote/set-url.js +30 -0
  74. package/dist/oclif/commands/vc/reset.d.ts +13 -0
  75. package/dist/oclif/commands/vc/reset.js +62 -0
  76. package/dist/oclif/commands/vc/status.d.ts +8 -0
  77. package/dist/oclif/commands/vc/status.js +106 -0
  78. package/dist/oclif/hooks/init/validate-brv-config.d.ts +26 -0
  79. package/dist/oclif/hooks/init/validate-brv-config.js +62 -0
  80. package/dist/oclif/lib/daemon-client.d.ts +2 -0
  81. package/dist/oclif/lib/daemon-client.js +36 -10
  82. package/dist/oclif/lib/prompt-utils.d.ts +43 -0
  83. package/dist/oclif/lib/prompt-utils.js +84 -0
  84. package/dist/oclif/lib/spinner.d.ts +8 -0
  85. package/dist/oclif/lib/spinner.js +23 -0
  86. package/dist/oclif/lib/task-client.d.ts +5 -0
  87. package/dist/oclif/lib/task-client.js +15 -2
  88. package/dist/server/config/environment.d.ts +2 -0
  89. package/dist/server/config/environment.js +2 -0
  90. package/dist/server/constants.d.ts +3 -0
  91. package/dist/server/constants.js +9 -0
  92. package/dist/server/core/domain/entities/auth-token.d.ts +2 -0
  93. package/dist/server/core/domain/entities/auth-token.js +7 -1
  94. package/dist/server/core/domain/entities/curate-log-entry.d.ts +11 -0
  95. package/dist/server/core/domain/entities/space.d.ts +4 -0
  96. package/dist/server/core/domain/entities/space.js +8 -0
  97. package/dist/server/core/domain/entities/team.d.ts +2 -0
  98. package/dist/server/core/domain/entities/team.js +4 -0
  99. package/dist/server/core/domain/errors/git-error.d.ts +6 -0
  100. package/dist/server/core/domain/errors/git-error.js +12 -0
  101. package/dist/server/core/domain/errors/task-error.d.ts +4 -0
  102. package/dist/server/core/domain/errors/task-error.js +8 -0
  103. package/dist/server/core/domain/errors/vc-error.d.ts +5 -0
  104. package/dist/server/core/domain/errors/vc-error.js +8 -0
  105. package/dist/server/core/domain/knowledge/markdown-writer.d.ts +4 -1
  106. package/dist/server/core/domain/knowledge/markdown-writer.js +37 -7
  107. package/dist/server/core/domain/transport/schemas.d.ts +6 -6
  108. package/dist/server/core/interfaces/context-tree/i-context-tree-service.d.ts +11 -0
  109. package/dist/server/core/interfaces/process/i-task-lifecycle-hook.d.ts +6 -0
  110. package/dist/server/core/interfaces/services/i-git-service.d.ts +234 -0
  111. package/dist/server/core/interfaces/services/i-git-service.js +1 -0
  112. package/dist/server/core/interfaces/storage/i-curate-log-store.d.ts +5 -0
  113. package/dist/server/core/interfaces/storage/i-review-backup-store.d.ts +19 -0
  114. package/dist/server/core/interfaces/storage/i-review-backup-store.js +1 -0
  115. package/dist/server/core/interfaces/vc/i-vc-git-config-store.d.ts +8 -0
  116. package/dist/server/core/interfaces/vc/i-vc-git-config-store.js +1 -0
  117. package/dist/server/infra/config/auto-init.d.ts +0 -2
  118. package/dist/server/infra/config/auto-init.js +0 -1
  119. package/dist/server/infra/context-tree/file-context-tree-service.d.ts +2 -0
  120. package/dist/server/infra/context-tree/file-context-tree-service.js +13 -0
  121. package/dist/server/infra/daemon/brv-server.js +23 -3
  122. package/dist/server/infra/git/cogit-url.d.ts +17 -0
  123. package/dist/server/infra/git/cogit-url.js +39 -0
  124. package/dist/server/infra/git/git-http-wrapper.d.ts +20 -0
  125. package/dist/server/infra/git/git-http-wrapper.js +334 -0
  126. package/dist/server/infra/git/isomorphic-git-service.d.ts +78 -0
  127. package/dist/server/infra/git/isomorphic-git-service.js +983 -0
  128. package/dist/server/infra/http/review-api-handler.d.ts +13 -0
  129. package/dist/server/infra/http/review-api-handler.js +286 -0
  130. package/dist/server/infra/http/review-ui.d.ts +7 -0
  131. package/dist/server/infra/http/review-ui.js +606 -0
  132. package/dist/server/infra/mcp/tools/brv-curate-tool.d.ts +2 -2
  133. package/dist/server/infra/process/curate-log-handler.d.ts +18 -2
  134. package/dist/server/infra/process/curate-log-handler.js +50 -13
  135. package/dist/server/infra/process/feature-handlers.js +41 -1
  136. package/dist/server/infra/process/task-router.js +16 -0
  137. package/dist/server/infra/space/http-space-service.js +2 -0
  138. package/dist/server/infra/storage/file-curate-log-store.d.ts +10 -0
  139. package/dist/server/infra/storage/file-curate-log-store.js +35 -0
  140. package/dist/server/infra/storage/file-review-backup-store.d.ts +29 -0
  141. package/dist/server/infra/storage/file-review-backup-store.js +121 -0
  142. package/dist/server/infra/transport/handlers/auth-handler.js +9 -5
  143. package/dist/server/infra/transport/handlers/handler-types.d.ts +9 -0
  144. package/dist/server/infra/transport/handlers/handler-types.js +11 -0
  145. package/dist/server/infra/transport/handlers/index.d.ts +4 -0
  146. package/dist/server/infra/transport/handlers/index.js +2 -0
  147. package/dist/server/infra/transport/handlers/init-handler.d.ts +1 -0
  148. package/dist/server/infra/transport/handlers/init-handler.js +13 -1
  149. package/dist/server/infra/transport/handlers/pull-handler.d.ts +3 -0
  150. package/dist/server/infra/transport/handlers/pull-handler.js +5 -1
  151. package/dist/server/infra/transport/handlers/push-handler.d.ts +20 -0
  152. package/dist/server/infra/transport/handlers/push-handler.js +116 -14
  153. package/dist/server/infra/transport/handlers/reset-handler.d.ts +11 -0
  154. package/dist/server/infra/transport/handlers/reset-handler.js +37 -1
  155. package/dist/server/infra/transport/handlers/review-handler.d.ts +35 -0
  156. package/dist/server/infra/transport/handlers/review-handler.js +162 -0
  157. package/dist/server/infra/transport/handlers/space-handler.d.ts +3 -0
  158. package/dist/server/infra/transport/handlers/space-handler.js +4 -1
  159. package/dist/server/infra/transport/handlers/status-handler.d.ts +5 -0
  160. package/dist/server/infra/transport/handlers/status-handler.js +51 -16
  161. package/dist/server/infra/transport/handlers/vc-handler.d.ts +100 -0
  162. package/dist/server/infra/transport/handlers/vc-handler.js +1050 -0
  163. package/dist/server/infra/transport/socket-io-transport-server.d.ts +7 -0
  164. package/dist/server/infra/transport/socket-io-transport-server.js +12 -1
  165. package/dist/server/infra/transport/transport-connector.d.ts +1 -1
  166. package/dist/server/infra/transport/transport-connector.js +2 -1
  167. package/dist/server/infra/vc/file-vc-git-config-store.d.ts +11 -0
  168. package/dist/server/infra/vc/file-vc-git-config-store.js +43 -0
  169. package/dist/server/templates/skill/SKILL.md +167 -33
  170. package/dist/server/utils/curate-result-parser.d.ts +64 -0
  171. package/dist/server/utils/curate-result-parser.js +8 -0
  172. package/dist/server/utils/gitignore.d.ts +9 -0
  173. package/dist/server/utils/gitignore.js +47 -0
  174. package/dist/shared/transport/events/index.d.ts +6 -0
  175. package/dist/shared/transport/events/index.js +3 -0
  176. package/dist/shared/transport/events/init-events.d.ts +8 -0
  177. package/dist/shared/transport/events/init-events.js +1 -0
  178. package/dist/shared/transport/events/push-events.d.ts +6 -0
  179. package/dist/shared/transport/events/review-events.d.ts +41 -0
  180. package/dist/shared/transport/events/review-events.js +5 -0
  181. package/dist/shared/transport/events/vc-events.d.ts +257 -0
  182. package/dist/shared/transport/events/vc-events.js +67 -0
  183. package/dist/shared/transport/types/dto.d.ts +6 -1
  184. package/dist/tui/app/pages/init-project-page.d.ts +9 -0
  185. package/dist/tui/app/pages/init-project-page.js +54 -0
  186. package/dist/tui/app/pages/protected-routes.js +14 -6
  187. package/dist/tui/components/index.d.ts +0 -2
  188. package/dist/tui/components/index.js +0 -1
  189. package/dist/tui/features/activity/hooks/use-activity-logs.js +7 -1
  190. package/dist/tui/features/commands/definitions/index.js +3 -0
  191. package/dist/tui/features/commands/definitions/space-list.js +9 -18
  192. package/dist/tui/features/commands/definitions/space-switch.js +10 -6
  193. package/dist/tui/features/commands/definitions/vc-add.d.ts +2 -0
  194. package/dist/tui/features/commands/definitions/vc-add.js +15 -0
  195. package/dist/tui/features/commands/definitions/vc-branch.d.ts +2 -0
  196. package/dist/tui/features/commands/definitions/vc-branch.js +33 -0
  197. package/dist/tui/features/commands/definitions/vc-checkout.d.ts +2 -0
  198. package/dist/tui/features/commands/definitions/vc-checkout.js +32 -0
  199. package/dist/tui/features/commands/definitions/vc-clone.d.ts +2 -0
  200. package/dist/tui/features/commands/definitions/vc-clone.js +18 -0
  201. package/dist/tui/features/commands/definitions/vc-commit.d.ts +2 -0
  202. package/dist/tui/features/commands/definitions/vc-commit.js +32 -0
  203. package/dist/tui/features/commands/definitions/vc-config.d.ts +2 -0
  204. package/dist/tui/features/commands/definitions/vc-config.js +40 -0
  205. package/dist/tui/features/commands/definitions/vc-fetch.d.ts +2 -0
  206. package/dist/tui/features/commands/definitions/vc-fetch.js +37 -0
  207. package/dist/tui/features/commands/definitions/vc-init.d.ts +2 -0
  208. package/dist/tui/features/commands/definitions/vc-init.js +11 -0
  209. package/dist/tui/features/commands/definitions/vc-log.d.ts +2 -0
  210. package/dist/tui/features/commands/definitions/vc-log.js +25 -0
  211. package/dist/tui/features/commands/definitions/vc-merge.d.ts +2 -0
  212. package/dist/tui/features/commands/definitions/vc-merge.js +48 -0
  213. package/dist/tui/features/commands/definitions/vc-pull.d.ts +2 -0
  214. package/dist/tui/features/commands/definitions/vc-pull.js +42 -0
  215. package/dist/tui/features/commands/definitions/vc-push.d.ts +2 -0
  216. package/dist/tui/features/commands/definitions/vc-push.js +38 -0
  217. package/dist/tui/features/commands/definitions/vc-remote.d.ts +2 -0
  218. package/dist/tui/features/commands/definitions/vc-remote.js +57 -0
  219. package/dist/tui/features/commands/definitions/vc-reset.d.ts +2 -0
  220. package/dist/tui/features/commands/definitions/vc-reset.js +35 -0
  221. package/dist/tui/features/commands/definitions/vc-status.d.ts +2 -0
  222. package/dist/tui/features/commands/definitions/vc-status.js +11 -0
  223. package/dist/tui/features/commands/definitions/vc.d.ts +2 -0
  224. package/dist/tui/features/commands/definitions/vc.js +36 -0
  225. package/dist/tui/features/commands/hooks/use-slash-command-processor.js +5 -5
  226. package/dist/tui/features/log/api/execute-log.d.ts +8 -0
  227. package/dist/tui/features/log/api/execute-log.js +13 -0
  228. package/dist/tui/features/log/components/log-flow.d.ts +14 -0
  229. package/dist/tui/features/log/components/log-flow.js +29 -0
  230. package/dist/tui/features/log/utils/format-log.d.ts +3 -0
  231. package/dist/tui/features/log/utils/format-log.js +42 -0
  232. package/dist/tui/features/onboarding/hooks/use-app-view-mode.d.ts +9 -5
  233. package/dist/tui/features/onboarding/hooks/use-app-view-mode.js +12 -5
  234. package/dist/tui/features/push/components/push-flow.js +9 -2
  235. package/dist/tui/features/reset/components/reset-flow.js +2 -1
  236. package/dist/tui/features/status/components/status-view.js +2 -1
  237. package/dist/tui/features/status/utils/format-status.js +9 -0
  238. package/dist/tui/features/tasks/hooks/use-task-subscriptions.js +11 -0
  239. package/dist/tui/features/tasks/stores/tasks-store.d.ts +10 -0
  240. package/dist/tui/features/tasks/stores/tasks-store.js +16 -0
  241. package/dist/tui/features/vc/add/api/execute-vc-add.d.ts +8 -0
  242. package/dist/tui/features/vc/add/api/execute-vc-add.js +13 -0
  243. package/dist/tui/features/vc/add/components/vc-add-flow.d.ts +7 -0
  244. package/dist/tui/features/vc/add/components/vc-add-flow.js +35 -0
  245. package/dist/tui/features/vc/branch/api/execute-vc-branch.d.ts +8 -0
  246. package/dist/tui/features/vc/branch/api/execute-vc-branch.js +13 -0
  247. package/dist/tui/features/vc/branch/components/vc-branch-flow.d.ts +8 -0
  248. package/dist/tui/features/vc/branch/components/vc-branch-flow.js +53 -0
  249. package/dist/tui/features/vc/branch/utils/format-branch.d.ts +4 -0
  250. package/dist/tui/features/vc/branch/utils/format-branch.js +12 -0
  251. package/dist/tui/features/vc/checkout/api/execute-vc-checkout.d.ts +8 -0
  252. package/dist/tui/features/vc/checkout/api/execute-vc-checkout.js +13 -0
  253. package/dist/tui/features/vc/checkout/components/vc-checkout-flow.d.ts +8 -0
  254. package/dist/tui/features/vc/checkout/components/vc-checkout-flow.js +33 -0
  255. package/dist/tui/features/vc/clone/api/execute-vc-clone.d.ts +8 -0
  256. package/dist/tui/features/vc/clone/api/execute-vc-clone.js +13 -0
  257. package/dist/tui/features/vc/clone/components/vc-clone-flow.d.ts +7 -0
  258. package/dist/tui/features/vc/clone/components/vc-clone-flow.js +79 -0
  259. package/dist/tui/features/vc/commit/api/execute-vc-commit.d.ts +8 -0
  260. package/dist/tui/features/vc/commit/api/execute-vc-commit.js +13 -0
  261. package/dist/tui/features/vc/commit/components/vc-commit-flow.d.ts +7 -0
  262. package/dist/tui/features/vc/commit/components/vc-commit-flow.js +29 -0
  263. package/dist/tui/features/vc/config/api/execute-vc-config.d.ts +8 -0
  264. package/dist/tui/features/vc/config/api/execute-vc-config.js +13 -0
  265. package/dist/tui/features/vc/config/components/vc-config-flow.d.ts +9 -0
  266. package/dist/tui/features/vc/config/components/vc-config-flow.js +30 -0
  267. package/dist/tui/features/vc/fetch/api/execute-vc-fetch.d.ts +8 -0
  268. package/dist/tui/features/vc/fetch/api/execute-vc-fetch.js +13 -0
  269. package/dist/tui/features/vc/fetch/components/vc-fetch-flow.d.ts +8 -0
  270. package/dist/tui/features/vc/fetch/components/vc-fetch-flow.js +75 -0
  271. package/dist/tui/features/vc/init/api/execute-vc-init.d.ts +8 -0
  272. package/dist/tui/features/vc/init/api/execute-vc-init.js +13 -0
  273. package/dist/tui/features/vc/init/components/vc-init-flow.d.ts +10 -0
  274. package/dist/tui/features/vc/init/components/vc-init-flow.js +37 -0
  275. package/dist/tui/features/vc/merge/api/execute-vc-merge.d.ts +8 -0
  276. package/dist/tui/features/vc/merge/api/execute-vc-merge.js +13 -0
  277. package/dist/tui/features/vc/merge/components/vc-merge-flow.d.ts +11 -0
  278. package/dist/tui/features/vc/merge/components/vc-merge-flow.js +72 -0
  279. package/dist/tui/features/vc/pull/api/execute-vc-pull.d.ts +8 -0
  280. package/dist/tui/features/vc/pull/api/execute-vc-pull.js +13 -0
  281. package/dist/tui/features/vc/pull/components/vc-pull-flow.d.ts +9 -0
  282. package/dist/tui/features/vc/pull/components/vc-pull-flow.js +83 -0
  283. package/dist/tui/features/vc/push/api/execute-vc-push.d.ts +8 -0
  284. package/dist/tui/features/vc/push/api/execute-vc-push.js +13 -0
  285. package/dist/tui/features/vc/push/components/vc-push-flow.d.ts +8 -0
  286. package/dist/tui/features/vc/push/components/vc-push-flow.js +83 -0
  287. package/dist/tui/features/vc/remote/api/execute-vc-remote.d.ts +8 -0
  288. package/dist/tui/features/vc/remote/api/execute-vc-remote.js +13 -0
  289. package/dist/tui/features/vc/remote/components/vc-remote-flow.d.ts +9 -0
  290. package/dist/tui/features/vc/remote/components/vc-remote-flow.js +42 -0
  291. package/dist/tui/features/vc/reset/api/execute-vc-reset.d.ts +8 -0
  292. package/dist/tui/features/vc/reset/api/execute-vc-reset.js +13 -0
  293. package/dist/tui/features/vc/reset/components/vc-reset-flow.d.ts +10 -0
  294. package/dist/tui/features/vc/reset/components/vc-reset-flow.js +63 -0
  295. package/dist/tui/features/vc/status/api/execute-vc-status.d.ts +8 -0
  296. package/dist/tui/features/vc/status/api/execute-vc-status.js +13 -0
  297. package/dist/tui/features/vc/status/components/vc-status-flow.d.ts +10 -0
  298. package/dist/tui/features/vc/status/components/vc-status-flow.js +133 -0
  299. package/dist/tui/lib/environment.d.ts +8 -0
  300. package/dist/tui/lib/environment.js +8 -0
  301. package/dist/tui/utils/error-messages.d.ts +5 -1
  302. package/dist/tui/utils/error-messages.js +32 -3
  303. package/oclif.manifest.json +1018 -98
  304. package/package.json +9 -3
  305. package/dist/oclif/hooks/prerun/validate-brv-config-version.d.ts +0 -33
  306. package/dist/oclif/hooks/prerun/validate-brv-config-version.js +0 -86
  307. package/dist/tui/components/init.d.ts +0 -33
  308. package/dist/tui/components/init.js +0 -234
  309. package/dist/tui/features/space/api/get-spaces.d.ts +0 -16
  310. package/dist/tui/features/space/api/get-spaces.js +0 -17
  311. package/dist/tui/features/space/api/switch-space.d.ts +0 -11
  312. package/dist/tui/features/space/api/switch-space.js +0 -24
  313. package/dist/tui/features/space/components/space-list-view.d.ts +0 -12
  314. package/dist/tui/features/space/components/space-list-view.js +0 -56
  315. package/dist/tui/features/space/components/space-switch-flow.d.ts +0 -13
  316. package/dist/tui/features/space/components/space-switch-flow.js +0 -97
@@ -5,8 +5,9 @@ import { BrvConfig } from '../../../core/domain/entities/brv-config.js';
5
5
  import { NotAuthenticatedError, SpaceNotFoundError } from '../../../core/domain/errors/task-error.js';
6
6
  import { syncConfigToXdg } from '../../../utils/config-xdg-sync.js';
7
7
  import { getErrorMessage } from '../../../utils/error-helpers.js';
8
+ import { ensureProjectInitialized } from '../../config/auto-init.js';
8
9
  import { mapAgentsToDTOs } from './agent-dto-mapper.js';
9
- import { resolveRequiredProjectPath } from './handler-types.js';
10
+ import { guardAgainstGitVc, resolveRequiredProjectPath, } from './handler-types.js';
10
11
  /**
11
12
  * Handles init:* events.
12
13
  * Business logic for project initialization — no terminal/UI calls.
@@ -44,9 +45,11 @@ export class InitHandler {
44
45
  this.transport.onRequest(InitEvents.GET_SPACES, (data) => this.handleGetSpaces(data));
45
46
  this.transport.onRequest(InitEvents.GET_AGENTS, () => this.handleGetAgents());
46
47
  this.transport.onRequest(InitEvents.EXECUTE, (data, clientId) => this.handleExecute(data, clientId));
48
+ this.transport.onRequest(InitEvents.LOCAL, (data, clientId) => this.handleLocalInit(data, clientId));
47
49
  }
48
50
  async handleExecute(data, clientId) {
49
51
  const projectPath = resolveRequiredProjectPath(this.resolveProjectPath, clientId);
52
+ await guardAgainstGitVc({ contextTreeService: this.contextTreeService, projectPath });
50
53
  const token = await this.tokenStore.load();
51
54
  if (!token || !token.isValid()) {
52
55
  throw new NotAuthenticatedError();
@@ -154,4 +157,13 @@ export class InitHandler {
154
157
  })),
155
158
  };
156
159
  }
160
+ async handleLocalInit(data, clientId) {
161
+ const projectPath = resolveRequiredProjectPath(this.resolveProjectPath, clientId);
162
+ const exists = await this.projectConfigStore.exists(projectPath);
163
+ if (exists && !data.force) {
164
+ return { alreadyInitialized: true, success: true };
165
+ }
166
+ await ensureProjectInitialized({ contextTreeService: this.contextTreeService, projectConfigStore: this.projectConfigStore }, projectPath);
167
+ return { alreadyInitialized: false, success: true };
168
+ }
157
169
  }
@@ -1,4 +1,5 @@
1
1
  import type { ITokenStore } from '../../../core/interfaces/auth/i-token-store.js';
2
+ import type { IContextTreeService } from '../../../core/interfaces/context-tree/i-context-tree-service.js';
2
3
  import type { IContextTreeSnapshotService } from '../../../core/interfaces/context-tree/i-context-tree-snapshot-service.js';
3
4
  import type { IContextTreeWriterService } from '../../../core/interfaces/context-tree/i-context-tree-writer-service.js';
4
5
  import type { ICogitPullService } from '../../../core/interfaces/services/i-cogit-pull-service.js';
@@ -8,6 +9,7 @@ import { type ProjectBroadcaster, type ProjectPathResolver } from './handler-typ
8
9
  export interface PullHandlerDeps {
9
10
  broadcastToProject: ProjectBroadcaster;
10
11
  cogitPullService: ICogitPullService;
12
+ contextTreeService: IContextTreeService;
11
13
  contextTreeSnapshotService: IContextTreeSnapshotService;
12
14
  contextTreeWriterService: IContextTreeWriterService;
13
15
  projectConfigStore: IProjectConfigStore;
@@ -22,6 +24,7 @@ export interface PullHandlerDeps {
22
24
  export declare class PullHandler {
23
25
  private readonly broadcastToProject;
24
26
  private readonly cogitPullService;
27
+ private readonly contextTreeService;
25
28
  private readonly contextTreeSnapshotService;
26
29
  private readonly contextTreeWriterService;
27
30
  private readonly projectConfigStore;
@@ -1,6 +1,6 @@
1
1
  import { PullEvents, } from '../../../../shared/transport/events/pull-events.js';
2
2
  import { LocalChangesExistError, NotAuthenticatedError, ProjectNotInitError, SpaceNotConfiguredError, } from '../../../core/domain/errors/task-error.js';
3
- import { hasAnyChanges, resolveRequiredProjectPath } from './handler-types.js';
3
+ import { guardAgainstGitVc, hasAnyChanges, resolveRequiredProjectPath, } from './handler-types.js';
4
4
  /**
5
5
  * Handles pull:* events.
6
6
  * Business logic for pulling context tree from cloud — no terminal/UI calls.
@@ -8,6 +8,7 @@ import { hasAnyChanges, resolveRequiredProjectPath } from './handler-types.js';
8
8
  export class PullHandler {
9
9
  broadcastToProject;
10
10
  cogitPullService;
11
+ contextTreeService;
11
12
  contextTreeSnapshotService;
12
13
  contextTreeWriterService;
13
14
  projectConfigStore;
@@ -17,6 +18,7 @@ export class PullHandler {
17
18
  constructor(deps) {
18
19
  this.broadcastToProject = deps.broadcastToProject;
19
20
  this.cogitPullService = deps.cogitPullService;
21
+ this.contextTreeService = deps.contextTreeService;
20
22
  this.contextTreeSnapshotService = deps.contextTreeSnapshotService;
21
23
  this.contextTreeWriterService = deps.contextTreeWriterService;
22
24
  this.projectConfigStore = deps.projectConfigStore;
@@ -30,6 +32,7 @@ export class PullHandler {
30
32
  }
31
33
  async handleExecute(data, clientId) {
32
34
  const projectPath = resolveRequiredProjectPath(this.resolveProjectPath, clientId);
35
+ await guardAgainstGitVc({ contextTreeService: this.contextTreeService, projectPath });
33
36
  const token = await this.tokenStore.load();
34
37
  if (!token || !token.isValid()) {
35
38
  throw new NotAuthenticatedError();
@@ -66,6 +69,7 @@ export class PullHandler {
66
69
  }
67
70
  async handlePrepare(_data, clientId) {
68
71
  const projectPath = resolveRequiredProjectPath(this.resolveProjectPath, clientId);
72
+ await guardAgainstGitVc({ contextTreeService: this.contextTreeService, projectPath });
69
73
  const token = await this.tokenStore.load();
70
74
  if (!token || !token.isValid()) {
71
75
  throw new NotAuthenticatedError();
@@ -1,17 +1,25 @@
1
1
  import type { ITokenStore } from '../../../core/interfaces/auth/i-token-store.js';
2
2
  import type { IContextFileReader } from '../../../core/interfaces/context-tree/i-context-file-reader.js';
3
+ import type { IContextTreeService } from '../../../core/interfaces/context-tree/i-context-tree-service.js';
3
4
  import type { IContextTreeSnapshotService } from '../../../core/interfaces/context-tree/i-context-tree-snapshot-service.js';
4
5
  import type { ICogitPushService } from '../../../core/interfaces/services/i-cogit-push-service.js';
6
+ import type { ICurateLogStore } from '../../../core/interfaces/storage/i-curate-log-store.js';
5
7
  import type { IProjectConfigStore } from '../../../core/interfaces/storage/i-project-config-store.js';
8
+ import type { IReviewBackupStore } from '../../../core/interfaces/storage/i-review-backup-store.js';
6
9
  import type { ITransportServer } from '../../../core/interfaces/transport/i-transport-server.js';
7
10
  import { type ProjectBroadcaster, type ProjectPathResolver } from './handler-types.js';
11
+ /** Factory that creates a curate log store scoped to a project directory. */
12
+ export type CurateLogStoreFactory = (projectPath: string) => ICurateLogStore;
8
13
  export interface PushHandlerDeps {
9
14
  broadcastToProject: ProjectBroadcaster;
10
15
  cogitPushService: ICogitPushService;
11
16
  contextFileReader: IContextFileReader;
17
+ contextTreeService: IContextTreeService;
12
18
  contextTreeSnapshotService: IContextTreeSnapshotService;
19
+ curateLogStoreFactory: CurateLogStoreFactory;
13
20
  projectConfigStore: IProjectConfigStore;
14
21
  resolveProjectPath: ProjectPathResolver;
22
+ reviewBackupStoreFactory: (projectPath: string) => IReviewBackupStore;
15
23
  tokenStore: ITokenStore;
16
24
  transport: ITransportServer;
17
25
  webAppUrl: string;
@@ -24,14 +32,26 @@ export declare class PushHandler {
24
32
  private readonly broadcastToProject;
25
33
  private readonly cogitPushService;
26
34
  private readonly contextFileReader;
35
+ private readonly contextTreeService;
27
36
  private readonly contextTreeSnapshotService;
37
+ private readonly curateLogStoreFactory;
28
38
  private readonly projectConfigStore;
29
39
  private readonly resolveProjectPath;
40
+ private readonly reviewBackupStoreFactory;
30
41
  private readonly tokenStore;
31
42
  private readonly transport;
32
43
  private readonly webAppUrl;
33
44
  constructor(deps: PushHandlerDeps);
34
45
  setup(): void;
46
+ /**
47
+ * Filter a list of file paths, removing any with pending or rejected review status.
48
+ */
49
+ private filterPushablePaths;
50
+ /**
51
+ * Build a map of context-tree-relative file path → review status from the curate log.
52
+ * Uses newest-wins strategy for the same file across multiple entries.
53
+ */
54
+ private getFileReviewStatuses;
35
55
  private handleExecute;
36
56
  private handlePrepare;
37
57
  }
@@ -1,7 +1,10 @@
1
+ import { join, relative } from 'node:path';
1
2
  import { PushEvents, } from '../../../../shared/transport/events/push-events.js';
2
3
  import { NotAuthenticatedError, ProjectNotInitError, SpaceNotConfiguredError, } from '../../../core/domain/errors/task-error.js';
3
4
  import { mapToPushContexts } from '../../cogit/context-tree-to-push-context-mapper.js';
4
- import { resolveRequiredProjectPath } from './handler-types.js';
5
+ import { guardAgainstGitVc, resolveRequiredProjectPath, } from './handler-types.js';
6
+ /** Path prefix of the context tree relative to the project root. */
7
+ const CONTEXT_TREE_RELATIVE = join('.brv', 'context-tree');
5
8
  /**
6
9
  * Handles push:* events.
7
10
  * Business logic for pushing context tree to cloud — no terminal/UI calls.
@@ -10,9 +13,12 @@ export class PushHandler {
10
13
  broadcastToProject;
11
14
  cogitPushService;
12
15
  contextFileReader;
16
+ contextTreeService;
13
17
  contextTreeSnapshotService;
18
+ curateLogStoreFactory;
14
19
  projectConfigStore;
15
20
  resolveProjectPath;
21
+ reviewBackupStoreFactory;
16
22
  tokenStore;
17
23
  transport;
18
24
  webAppUrl;
@@ -20,9 +26,12 @@ export class PushHandler {
20
26
  this.broadcastToProject = deps.broadcastToProject;
21
27
  this.cogitPushService = deps.cogitPushService;
22
28
  this.contextFileReader = deps.contextFileReader;
29
+ this.contextTreeService = deps.contextTreeService;
23
30
  this.contextTreeSnapshotService = deps.contextTreeSnapshotService;
31
+ this.curateLogStoreFactory = deps.curateLogStoreFactory;
24
32
  this.projectConfigStore = deps.projectConfigStore;
25
33
  this.resolveProjectPath = deps.resolveProjectPath;
34
+ this.reviewBackupStoreFactory = deps.reviewBackupStoreFactory;
26
35
  this.tokenStore = deps.tokenStore;
27
36
  this.transport = deps.transport;
28
37
  this.webAppUrl = deps.webAppUrl;
@@ -31,8 +40,45 @@ export class PushHandler {
31
40
  this.transport.onRequest(PushEvents.PREPARE, (data, clientId) => this.handlePrepare(data, clientId));
32
41
  this.transport.onRequest(PushEvents.EXECUTE, (data, clientId) => this.handleExecute(data, clientId));
33
42
  }
43
+ /**
44
+ * Filter a list of file paths, removing any with pending or rejected review status.
45
+ */
46
+ filterPushablePaths(paths, reviewStatuses) {
47
+ return paths.filter((path) => {
48
+ const status = reviewStatuses.get(path);
49
+ return status !== 'pending' && status !== 'rejected';
50
+ });
51
+ }
52
+ /**
53
+ * Build a map of context-tree-relative file path → review status from the curate log.
54
+ * Uses newest-wins strategy for the same file across multiple entries.
55
+ */
56
+ async getFileReviewStatuses(projectPath) {
57
+ const map = new Map();
58
+ try {
59
+ const store = this.curateLogStoreFactory(projectPath);
60
+ const entries = await store.list({ limit: 500, status: ['completed'] });
61
+ const contextTreeRoot = join(projectPath, CONTEXT_TREE_RELATIVE);
62
+ // Process oldest-first so the newest entry wins for each path
63
+ for (const entry of [...entries].reverse()) {
64
+ for (const op of entry.operations) {
65
+ if (!op.filePath || !op.reviewStatus)
66
+ continue;
67
+ const relativePath = relative(contextTreeRoot, op.filePath);
68
+ if (relativePath.startsWith('..'))
69
+ continue;
70
+ map.set(relativePath, op.reviewStatus);
71
+ }
72
+ }
73
+ }
74
+ catch {
75
+ // Best-effort
76
+ }
77
+ return map;
78
+ }
34
79
  async handleExecute(data, clientId) {
35
80
  const projectPath = resolveRequiredProjectPath(this.resolveProjectPath, clientId);
81
+ await guardAgainstGitVc({ contextTreeService: this.contextTreeService, projectPath });
36
82
  const token = await this.tokenStore.load();
37
83
  if (!token || !token.isValid()) {
38
84
  throw new NotAuthenticatedError();
@@ -46,13 +92,18 @@ export class PushHandler {
46
92
  }
47
93
  this.broadcastToProject(projectPath, PushEvents.PROGRESS, { message: 'Reading context files...', step: 'reading' });
48
94
  const changes = await this.contextTreeSnapshotService.getChanges(projectPath);
95
+ // Filter out files with pending/rejected review status
96
+ const reviewStatuses = await this.getFileReviewStatuses(projectPath);
97
+ const pushableAdded = this.filterPushablePaths(changes.added, reviewStatuses);
98
+ const pushableModified = this.filterPushablePaths(changes.modified, reviewStatuses);
99
+ const pushableDeleted = this.filterPushablePaths(changes.deleted, reviewStatuses);
49
100
  const [addedFiles, modifiedFiles] = await Promise.all([
50
- this.contextFileReader.readMany(changes.added, projectPath),
51
- this.contextFileReader.readMany(changes.modified, projectPath),
101
+ this.contextFileReader.readMany(pushableAdded, projectPath),
102
+ this.contextFileReader.readMany(pushableModified, projectPath),
52
103
  ]);
53
104
  const pushContexts = mapToPushContexts({
54
105
  addedFiles,
55
- deletedPaths: changes.deleted,
106
+ deletedPaths: pushableDeleted,
56
107
  modifiedFiles,
57
108
  });
58
109
  this.broadcastToProject(projectPath, PushEvents.PROGRESS, { message: 'Pushing to cloud...', step: 'pushing' });
@@ -64,11 +115,37 @@ export class PushHandler {
64
115
  spaceId: config.spaceId,
65
116
  teamId: config.teamId,
66
117
  });
67
- await this.contextTreeSnapshotService.saveSnapshot(projectPath);
118
+ // Save snapshot selectively: only record pushed files so excluded files
119
+ // still appear as changes in the next push.
120
+ const oldSnapshot = await this.contextTreeSnapshotService.getSnapshotState(projectPath);
121
+ const currentState = await this.contextTreeSnapshotService.getCurrentState(projectPath);
122
+ const newSnapshot = new Map(oldSnapshot);
123
+ for (const path of pushableAdded) {
124
+ const state = currentState.get(path);
125
+ if (state)
126
+ newSnapshot.set(path, state);
127
+ }
128
+ for (const path of pushableModified) {
129
+ const state = currentState.get(path);
130
+ if (state)
131
+ newSnapshot.set(path, state);
132
+ }
133
+ for (const path of pushableDeleted) {
134
+ newSnapshot.delete(path);
135
+ }
136
+ await this.contextTreeSnapshotService.saveSnapshotFromState(newSnapshot, projectPath);
137
+ // Best-effort: clear backups for pushed files so the pushed state becomes the new diff baseline
138
+ try {
139
+ const backupStore = this.reviewBackupStoreFactory(projectPath);
140
+ await Promise.all([...pushableAdded, ...pushableModified, ...pushableDeleted].map((p) => backupStore.delete(p)));
141
+ }
142
+ catch {
143
+ // Backup cleanup must never block the push response
144
+ }
68
145
  const url = `${this.webAppUrl}/${config.teamName}/${config.spaceName}`;
69
146
  return {
70
147
  added: addedFiles.length,
71
- deleted: changes.deleted.length,
148
+ deleted: pushableDeleted.length,
72
149
  edited: modifiedFiles.length,
73
150
  success: true,
74
151
  url,
@@ -76,6 +153,7 @@ export class PushHandler {
76
153
  }
77
154
  async handlePrepare(_data, clientId) {
78
155
  const projectPath = resolveRequiredProjectPath(this.resolveProjectPath, clientId);
156
+ await guardAgainstGitVc({ contextTreeService: this.contextTreeService, projectPath });
79
157
  const token = await this.tokenStore.load();
80
158
  if (!token || !token.isValid()) {
81
159
  throw new NotAuthenticatedError();
@@ -92,17 +170,41 @@ export class PushHandler {
92
170
  await this.contextTreeSnapshotService.initEmptySnapshot(projectPath);
93
171
  }
94
172
  const changes = await this.contextTreeSnapshotService.getChanges(projectPath);
173
+ // Get review statuses and filter pushable files
174
+ const reviewStatuses = await this.getFileReviewStatuses(projectPath);
175
+ const pushableAdded = this.filterPushablePaths(changes.added, reviewStatuses);
176
+ const pushableModified = this.filterPushablePaths(changes.modified, reviewStatuses);
177
+ const pushableDeleted = this.filterPushablePaths(changes.deleted, reviewStatuses);
95
178
  const totalChanges = changes.added.length + changes.modified.length + changes.deleted.length;
179
+ const pushableTotal = pushableAdded.length + pushableModified.length + pushableDeleted.length;
180
+ const excludedReviewCount = totalChanges - pushableTotal;
181
+ // Count pending reviews from review statuses map
182
+ let pendingReviewCount = 0;
183
+ for (const status of reviewStatuses.values()) {
184
+ if (status === 'pending')
185
+ pendingReviewCount++;
186
+ }
96
187
  const parts = [];
97
- if (changes.added.length > 0)
98
- parts.push(`${changes.added.length} added`);
99
- if (changes.modified.length > 0)
100
- parts.push(`${changes.modified.length} modified`);
101
- if (changes.deleted.length > 0)
102
- parts.push(`${changes.deleted.length} deleted`);
188
+ if (pushableAdded.length > 0)
189
+ parts.push(`${pushableAdded.length} added`);
190
+ if (pushableModified.length > 0)
191
+ parts.push(`${pushableModified.length} modified`);
192
+ if (pushableDeleted.length > 0)
193
+ parts.push(`${pushableDeleted.length} deleted`);
194
+ let reviewUrl;
195
+ if (pendingReviewCount > 0) {
196
+ const port = this.transport.getPort();
197
+ if (port) {
198
+ const encoded = Buffer.from(projectPath).toString('base64url');
199
+ reviewUrl = `http://127.0.0.1:${port}/review?project=${encoded}`;
200
+ }
201
+ }
103
202
  return {
104
- fileCount: totalChanges,
105
- hasChanges: totalChanges > 0,
203
+ excludedReviewCount,
204
+ fileCount: pushableTotal,
205
+ hasChanges: pushableTotal > 0,
206
+ pendingReviewCount,
207
+ ...(reviewUrl ? { reviewUrl } : {}),
106
208
  summary: parts.length > 0 ? parts.join(', ') : 'No changes',
107
209
  };
108
210
  }
@@ -1,11 +1,15 @@
1
1
  import type { IContextTreeService } from '../../../core/interfaces/context-tree/i-context-tree-service.js';
2
2
  import type { IContextTreeSnapshotService } from '../../../core/interfaces/context-tree/i-context-tree-snapshot-service.js';
3
+ import type { ICurateLogStore } from '../../../core/interfaces/storage/i-curate-log-store.js';
4
+ import type { IReviewBackupStore } from '../../../core/interfaces/storage/i-review-backup-store.js';
3
5
  import type { ITransportServer } from '../../../core/interfaces/transport/i-transport-server.js';
4
6
  import { type ProjectPathResolver } from './handler-types.js';
5
7
  export interface ResetHandlerDeps {
6
8
  contextTreeService: IContextTreeService;
7
9
  contextTreeSnapshotService: IContextTreeSnapshotService;
10
+ curateLogStoreFactory: (projectPath: string) => ICurateLogStore;
8
11
  resolveProjectPath: ProjectPathResolver;
12
+ reviewBackupStoreFactory: (projectPath: string) => IReviewBackupStore;
9
13
  transport: ITransportServer;
10
14
  }
11
15
  /**
@@ -15,9 +19,16 @@ export interface ResetHandlerDeps {
15
19
  export declare class ResetHandler {
16
20
  private readonly contextTreeService;
17
21
  private readonly contextTreeSnapshotService;
22
+ private readonly curateLogStoreFactory;
18
23
  private readonly resolveProjectPath;
24
+ private readonly reviewBackupStoreFactory;
19
25
  private readonly transport;
20
26
  constructor(deps: ResetHandlerDeps);
21
27
  setup(): void;
28
+ /**
29
+ * Mark all pending review operations as 'rejected' so they no longer appear in /status.
30
+ * The context tree has been wiped, so these reviews are no longer actionable.
31
+ */
32
+ private clearPendingReviews;
22
33
  private handleExecute;
23
34
  }
@@ -1,6 +1,6 @@
1
1
  import { ResetEvents } from '../../../../shared/transport/events/reset-events.js';
2
2
  import { ContextTreeNotInitializedError } from '../../../core/domain/errors/task-error.js';
3
- import { resolveRequiredProjectPath } from './handler-types.js';
3
+ import { guardAgainstGitVc, resolveRequiredProjectPath } from './handler-types.js';
4
4
  /**
5
5
  * Handles reset:execute event.
6
6
  * Deletes and re-initializes the context tree — no terminal/UI calls.
@@ -8,19 +8,42 @@ import { resolveRequiredProjectPath } from './handler-types.js';
8
8
  export class ResetHandler {
9
9
  contextTreeService;
10
10
  contextTreeSnapshotService;
11
+ curateLogStoreFactory;
11
12
  resolveProjectPath;
13
+ reviewBackupStoreFactory;
12
14
  transport;
13
15
  constructor(deps) {
14
16
  this.contextTreeService = deps.contextTreeService;
15
17
  this.contextTreeSnapshotService = deps.contextTreeSnapshotService;
18
+ this.curateLogStoreFactory = deps.curateLogStoreFactory;
16
19
  this.resolveProjectPath = deps.resolveProjectPath;
20
+ this.reviewBackupStoreFactory = deps.reviewBackupStoreFactory;
17
21
  this.transport = deps.transport;
18
22
  }
19
23
  setup() {
20
24
  this.transport.onRequest(ResetEvents.EXECUTE, (_data, clientId) => this.handleExecute(clientId));
21
25
  }
26
+ /**
27
+ * Mark all pending review operations as 'rejected' so they no longer appear in /status.
28
+ * The context tree has been wiped, so these reviews are no longer actionable.
29
+ */
30
+ async clearPendingReviews(projectPath) {
31
+ const store = this.curateLogStoreFactory(projectPath);
32
+ // Only completed entries can carry reviewable operations; pending reviews are assigned at completion time.
33
+ const entries = await store.list({ status: ['completed'] });
34
+ const updates = entries
35
+ .map((entry) => {
36
+ const pendingIndices = entry.operations
37
+ .map((op, i) => (op.reviewStatus === 'pending' ? { operationIndex: i, reviewStatus: 'rejected' } : null))
38
+ .filter((u) => u !== null);
39
+ return pendingIndices.length > 0 ? { id: entry.id, pendingIndices } : null;
40
+ })
41
+ .filter((u) => u !== null);
42
+ await Promise.all(updates.map((u) => store.batchUpdateOperationReviewStatus(u.id, u.pendingIndices)));
43
+ }
22
44
  async handleExecute(clientId) {
23
45
  const projectPath = resolveRequiredProjectPath(this.resolveProjectPath, clientId);
46
+ await guardAgainstGitVc({ contextTreeService: this.contextTreeService, projectPath });
24
47
  const exists = await this.contextTreeService.exists(projectPath);
25
48
  if (!exists) {
26
49
  throw new ContextTreeNotInitializedError();
@@ -28,6 +51,19 @@ export class ResetHandler {
28
51
  await this.contextTreeService.delete(projectPath);
29
52
  await this.contextTreeService.initialize(projectPath);
30
53
  await this.contextTreeSnapshotService.initEmptySnapshot(projectPath);
54
+ // Best-effort: clear review backups and pending review statuses so /status starts fresh
55
+ try {
56
+ await this.reviewBackupStoreFactory(projectPath).clear();
57
+ }
58
+ catch {
59
+ // Backup cleanup must never block the reset response
60
+ }
61
+ try {
62
+ await this.clearPendingReviews(projectPath);
63
+ }
64
+ catch {
65
+ // Review status cleanup must never block the reset response
66
+ }
31
67
  return { success: true };
32
68
  }
33
69
  }
@@ -0,0 +1,35 @@
1
+ import type { ICurateLogStore } from '../../../core/interfaces/storage/i-curate-log-store.js';
2
+ import type { IReviewBackupStore } from '../../../core/interfaces/storage/i-review-backup-store.js';
3
+ import type { ITransportServer } from '../../../core/interfaces/transport/i-transport-server.js';
4
+ import { type ProjectPathResolver } from './handler-types.js';
5
+ type CurateLogStoreFactory = (projectPath: string) => ICurateLogStore;
6
+ type ReviewBackupStoreFactory = (projectPath: string) => IReviewBackupStore;
7
+ export interface ReviewHandlerDeps {
8
+ curateLogStoreFactory: CurateLogStoreFactory;
9
+ /** Called after all pending ops for a task are decided. Used to notify TUI clients. */
10
+ onResolved?: (info: {
11
+ projectPath: string;
12
+ taskId: string;
13
+ }) => void;
14
+ resolveProjectPath: ProjectPathResolver;
15
+ reviewBackupStoreFactory: ReviewBackupStoreFactory;
16
+ transport: ITransportServer;
17
+ }
18
+ /**
19
+ * Handles review:decideTask — approves or rejects all pending review operations
20
+ * for a given task ID in a single transport request.
21
+ *
22
+ * Mirrors the per-file logic in review-api-handler.ts but operates at task scope.
23
+ */
24
+ export declare class ReviewHandler {
25
+ private readonly curateLogStoreFactory;
26
+ private readonly onResolved;
27
+ private readonly resolveProjectPath;
28
+ private readonly reviewBackupStoreFactory;
29
+ private readonly transport;
30
+ constructor(deps: ReviewHandlerDeps);
31
+ setup(): void;
32
+ private handleDecideTask;
33
+ private handlePending;
34
+ }
35
+ export {};
@@ -0,0 +1,162 @@
1
+ import { mkdir, unlink, writeFile } from 'node:fs/promises';
2
+ import { dirname, join, relative } from 'node:path';
3
+ import { ReviewEvents, } from '../../../../shared/transport/events/review-events.js';
4
+ import { BRV_DIR, CONTEXT_TREE_DIR } from '../../../constants.js';
5
+ import { resolveRequiredProjectPath } from './handler-types.js';
6
+ // ── Helpers ──────────────────────────────────────────────────────────────────
7
+ async function writeFileWithDirs(absolutePath, content) {
8
+ await mkdir(dirname(absolutePath), { recursive: true });
9
+ await writeFile(absolutePath, content, 'utf8');
10
+ }
11
+ // ── Handler ──────────────────────────────────────────────────────────────────
12
+ /**
13
+ * Handles review:decideTask — approves or rejects all pending review operations
14
+ * for a given task ID in a single transport request.
15
+ *
16
+ * Mirrors the per-file logic in review-api-handler.ts but operates at task scope.
17
+ */
18
+ export class ReviewHandler {
19
+ curateLogStoreFactory;
20
+ onResolved;
21
+ resolveProjectPath;
22
+ reviewBackupStoreFactory;
23
+ transport;
24
+ constructor(deps) {
25
+ this.curateLogStoreFactory = deps.curateLogStoreFactory;
26
+ this.onResolved = deps.onResolved;
27
+ this.resolveProjectPath = deps.resolveProjectPath;
28
+ this.reviewBackupStoreFactory = deps.reviewBackupStoreFactory;
29
+ this.transport = deps.transport;
30
+ }
31
+ setup() {
32
+ this.transport.onRequest(ReviewEvents.DECIDE_TASK, (data, clientId) => this.handleDecideTask(data, clientId));
33
+ this.transport.onRequest(ReviewEvents.PENDING, (_data, clientId) => this.handlePending(clientId));
34
+ }
35
+ async handleDecideTask({ decision, filePaths: filterPaths, taskId }, clientId) {
36
+ const projectPath = resolveRequiredProjectPath(this.resolveProjectPath, clientId);
37
+ const contextTreeDir = join(projectPath, BRV_DIR, CONTEXT_TREE_DIR);
38
+ const store = this.curateLogStoreFactory(projectPath);
39
+ const backupStore = this.reviewBackupStoreFactory(projectPath);
40
+ const entries = await store.list();
41
+ // Collect pending ops grouped by relative file path for this taskId
42
+ const pendingByPath = new Map();
43
+ for (const entry of entries) {
44
+ if (entry.taskId !== taskId)
45
+ continue;
46
+ for (let i = 0; i < entry.operations.length; i++) {
47
+ const op = entry.operations[i];
48
+ if (op.reviewStatus !== 'pending' || !op.filePath)
49
+ continue;
50
+ const rel = relative(contextTreeDir, op.filePath);
51
+ if (rel.startsWith('..'))
52
+ continue;
53
+ let ops = pendingByPath.get(rel);
54
+ if (!ops) {
55
+ ops = [];
56
+ pendingByPath.set(rel, ops);
57
+ }
58
+ ops.push({ additionalFilePaths: op.additionalFilePaths, logId: entry.id, operationIndex: i });
59
+ }
60
+ }
61
+ // If filePaths filter is provided, only process those files
62
+ if (filterPaths?.length) {
63
+ const filterSet = new Set(filterPaths);
64
+ const keysToDelete = [...pendingByPath.keys()].filter((key) => !filterSet.has(key));
65
+ for (const key of keysToDelete)
66
+ pendingByPath.delete(key);
67
+ }
68
+ // Apply decision for each affected file in parallel.
69
+ // allSettled so a single file failure does not block log updates for files that succeeded.
70
+ const settled = await Promise.allSettled([...pendingByPath.entries()].map(async ([relPath, ops]) => {
71
+ let reverted = false;
72
+ const allAdditionalPaths = [...new Set(ops.flatMap((o) => o.additionalFilePaths ?? []))];
73
+ if (decision === 'rejected') {
74
+ const absolutePath = join(contextTreeDir, relPath);
75
+ const backupContent = await backupStore.read(relPath);
76
+ // null backup = ADD operation (new file) → remove it; existing backup → restore
77
+ await (backupContent === null
78
+ ? unlink(absolutePath).catch(() => { })
79
+ : writeFileWithDirs(absolutePath, backupContent));
80
+ // Restore additional paths (MERGE source, folder DELETE contents).
81
+ // Best-effort: partial failures must not block the log update below.
82
+ await Promise.allSettled(allAdditionalPaths.map(async (absPath) => {
83
+ const rel = relative(contextTreeDir, absPath);
84
+ const content = await backupStore.read(rel);
85
+ if (content !== null)
86
+ await writeFileWithDirs(absPath, content);
87
+ }));
88
+ reverted = true;
89
+ }
90
+ // Clear backups for both approve and reject (current state becomes new baseline)
91
+ await backupStore.delete(relPath);
92
+ await Promise.allSettled(allAdditionalPaths.map((absPath) => backupStore.delete(relative(contextTreeDir, absPath))));
93
+ return { ops, path: relPath, reverted };
94
+ }));
95
+ // Only update log entries for files that were successfully processed.
96
+ // Files that failed remain pending and can be retried.
97
+ const fileResults = settled
98
+ .filter((r) => r.status === 'fulfilled')
99
+ .map((r) => r.value);
100
+ // Batch-update review status grouped by logId (one read+write per entry file)
101
+ const byLogId = new Map();
102
+ for (const { ops } of fileResults) {
103
+ for (const { logId, operationIndex } of ops) {
104
+ let batch = byLogId.get(logId);
105
+ if (!batch) {
106
+ batch = [];
107
+ byLogId.set(logId, batch);
108
+ }
109
+ batch.push({ operationIndex, reviewStatus: decision });
110
+ }
111
+ }
112
+ await Promise.all([...byLogId.entries()].map(([logId, updates]) => store.batchUpdateOperationReviewStatus(logId, updates)));
113
+ try {
114
+ this.onResolved?.({ projectPath, taskId });
115
+ }
116
+ catch {
117
+ // Best-effort notification — never block the response
118
+ }
119
+ const totalCount = fileResults.reduce((sum, { ops }) => sum + ops.length, 0);
120
+ return { files: fileResults.map(({ path, reverted }) => ({ path, reverted })), totalCount };
121
+ }
122
+ async handlePending(clientId) {
123
+ const projectPath = resolveRequiredProjectPath(this.resolveProjectPath, clientId);
124
+ const contextTreeDir = join(projectPath, BRV_DIR, CONTEXT_TREE_DIR);
125
+ const store = this.curateLogStoreFactory(projectPath);
126
+ const entries = await store.list({ status: ['completed'] });
127
+ const taskMap = new Map();
128
+ for (const entry of entries) {
129
+ for (const op of entry.operations) {
130
+ // Skip failed ops (e.g. validation errors) — they were never applied to disk
131
+ if (op.reviewStatus !== 'pending' || op.status === 'failed')
132
+ continue;
133
+ let ops = taskMap.get(entry.taskId);
134
+ if (!ops) {
135
+ ops = [];
136
+ taskMap.set(entry.taskId, ops);
137
+ }
138
+ const pendingOp = { path: op.path, type: op.type };
139
+ if (op.filePath) {
140
+ const rel = relative(contextTreeDir, op.filePath);
141
+ if (!rel.startsWith('..'))
142
+ pendingOp.filePath = rel;
143
+ }
144
+ if (op.impact)
145
+ pendingOp.impact = op.impact;
146
+ if (op.reason)
147
+ pendingOp.reason = op.reason;
148
+ if (op.previousSummary)
149
+ pendingOp.previousSummary = op.previousSummary;
150
+ if (op.summary)
151
+ pendingOp.summary = op.summary;
152
+ ops.push(pendingOp);
153
+ }
154
+ }
155
+ const tasks = [...taskMap.entries()].map(([taskId, operations]) => ({
156
+ operations,
157
+ taskId,
158
+ }));
159
+ const pendingCount = tasks.reduce((sum, t) => sum + t.operations.length, 0);
160
+ return { pendingCount, tasks };
161
+ }
162
+ }