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
@@ -1,10 +1,12 @@
1
- import { existsSync } from 'node:fs';
2
- import { join, resolve } from 'node:path';
1
+ import { basename, dirname, join, relative, resolve } from 'node:path';
3
2
  import { z } from 'zod';
3
+ import { REVIEW_BACKUPS_DIR } from '../../../../server/constants.js';
4
4
  import { DirectoryManager } from '../../../../server/core/domain/knowledge/directory-manager.js';
5
5
  import { MarkdownWriter, parseFrontmatterScoring } from '../../../../server/core/domain/knowledge/markdown-writer.js';
6
6
  import { applyDefaultScoring, determineTier, recordCurateUpdate, } from '../../../../server/core/domain/knowledge/memory-scoring.js';
7
7
  import { toSnakeCase } from '../../../../server/utils/file-helpers.js';
8
+ import { deriveImpactFromLoss, detectStructuralLoss } from '../../../core/domain/knowledge/conflict-detector.js';
9
+ import { resolveStructuralLoss } from '../../../core/domain/knowledge/conflict-resolver.js';
8
10
  import { ToolName } from '../../../core/domain/tools/constants.js';
9
11
  /**
10
12
  * Operation types for curating knowledge topics.
@@ -16,14 +18,23 @@ const OperationType = z.enum(['ADD', 'UPDATE', 'UPSERT', 'MERGE', 'DELETE']);
16
18
  */
17
19
  const RawConceptSchema = z.object({
18
20
  author: z.string().optional().describe('Author or source attribution (e.g., "meowso", "Team Security")'),
19
- changes: z.array(z.string()).optional().describe('What changes are induced by this concept (e.g., code changes, process updates, market shifts)'),
20
- files: z.array(z.string()).optional().describe('Related documents, source files, or resources (e.g., source code paths, reports, data files)'),
21
+ changes: z
22
+ .array(z.string())
23
+ .optional()
24
+ .describe('What changes are induced by this concept (e.g., code changes, process updates, market shifts)'),
25
+ files: z
26
+ .array(z.string())
27
+ .optional()
28
+ .describe('Related documents, source files, or resources (e.g., source code paths, reports, data files)'),
21
29
  flow: z.string().optional().describe('The process flow or workflow described by this concept'),
22
- patterns: z.array(z.object({
30
+ patterns: z
31
+ .array(z.object({
23
32
  description: z.string().describe('What this pattern matches or validates'),
24
33
  flags: z.string().optional().describe('Pattern flags (e.g., "gi" for regex)'),
25
- pattern: z.string().describe('The exact pattern string (e.g., regex pattern)')
26
- })).optional().describe('Regex or validation patterns related to this concept'),
34
+ pattern: z.string().describe('The exact pattern string (e.g., regex pattern)'),
35
+ }))
36
+ .optional()
37
+ .describe('Regex or validation patterns related to this concept'),
27
38
  task: z.string().optional().describe('What is the task related to this concept'),
28
39
  timestamp: z
29
40
  .string()
@@ -38,18 +49,26 @@ const NarrativeSchema = z.object({
38
49
  .string()
39
50
  .optional()
40
51
  .describe('Dependency or relationship information (e.g., prerequisite systems, required inputs, related components)'),
41
- diagrams: z.array(z.object({
42
- content: z.string().describe('The full diagram content (Mermaid code, PlantUML code, or ASCII art) - preserved verbatim'),
52
+ diagrams: z
53
+ .array(z.object({
54
+ content: z
55
+ .string()
56
+ .describe('The full diagram content (Mermaid code, PlantUML code, or ASCII art) - preserved verbatim'),
43
57
  title: z.string().optional().describe('Optional title or label for the diagram'),
44
58
  type: z.enum(['mermaid', 'plantuml', 'ascii', 'other']).describe('Diagram type for proper rendering'),
45
- })).optional().describe('Diagrams found in source content - Mermaid, PlantUML, ASCII art, sequence diagrams. Preserve verbatim.'),
59
+ }))
60
+ .optional()
61
+ .describe('Diagrams found in source content - Mermaid, PlantUML, ASCII art, sequence diagrams. Preserve verbatim.'),
46
62
  examples: z.string().optional().describe('Concrete examples and use cases demonstrating the concept'),
47
63
  highlights: z
48
64
  .string()
49
65
  .optional()
50
66
  .describe('Key highlights, capabilities, deliverables, or notable outcomes (e.g., "User permission can be stale for up to 300 seconds due to Redis cache")'),
51
67
  rules: z.string().optional().describe('Exact rules, constraints, or guidelines - preserved verbatim from source'),
52
- structure: z.string().optional().describe('Structural or organizational documentation (e.g., file layout, data schema, process hierarchy)'),
68
+ structure: z
69
+ .string()
70
+ .optional()
71
+ .describe('Structural or organizational documentation (e.g., file layout, data schema, process hierarchy)'),
53
72
  });
54
73
  /**
55
74
  * Fact schema for structured factual statements extracted during curation.
@@ -59,24 +78,25 @@ const FactSchema = z.object({
59
78
  .enum(['personal', 'project', 'preference', 'convention', 'team', 'environment', 'other'])
60
79
  .optional()
61
80
  .describe('Category of the fact (e.g., "personal", "project", "preference", "convention", "team", "environment")'),
62
- statement: z
63
- .string()
64
- .describe('The full factual statement (e.g., "My name is Andy", "We use PostgreSQL 15")'),
81
+ statement: z.string().describe('The full factual statement (e.g., "My name is Andy", "We use PostgreSQL 15")'),
65
82
  subject: z
66
83
  .string()
67
84
  .optional()
68
85
  .describe('What the fact is about in snake_case (e.g., "user_name", "database", "sprint_duration")'),
69
- value: z
70
- .string()
71
- .optional()
72
- .describe('The extracted value (e.g., "Andy", "PostgreSQL 15", "2 weeks")'),
86
+ value: z.string().optional().describe('The extracted value (e.g., "Andy", "PostgreSQL 15", "2 weeks")'),
73
87
  });
74
88
  /**
75
89
  * Content structure for ADD and UPDATE operations.
76
90
  */
77
91
  const ContentSchema = z.object({
78
- facts: z.array(FactSchema).optional().describe('Factual statements extracted from content (e.g., personal info, project facts, preferences, conventions)'),
79
- keywords: z.array(z.string()).default([]).describe('Keywords for search and discovery (e.g., ["jwt", "refresh_token", "rotation"])'),
92
+ facts: z
93
+ .array(FactSchema)
94
+ .optional()
95
+ .describe('Factual statements extracted from content (e.g., personal info, project facts, preferences, conventions)'),
96
+ keywords: z
97
+ .array(z.string())
98
+ .default([])
99
+ .describe('Keywords for search and discovery (e.g., ["jwt", "refresh_token", "rotation"])'),
80
100
  narrative: NarrativeSchema.optional().describe('Narrative section with descriptive and structural context'),
81
101
  rawConcept: RawConceptSchema.optional().describe('Raw concept section with metadata and technical footprint'),
82
102
  relations: z
@@ -84,7 +104,10 @@ const ContentSchema = z.object({
84
104
  .optional()
85
105
  .describe('Related topics using domain/topic/title.md or domain/topic/subtopic/title.md notation'),
86
106
  snippets: z.array(z.string()).optional().describe('Code/text snippets'),
87
- tags: z.array(z.string()).default([]).describe('Tags for categorization and filtering (e.g., ["authentication", "security", "jwt"])'),
107
+ tags: z
108
+ .array(z.string())
109
+ .default([])
110
+ .describe('Tags for categorization and filtering (e.g., ["authentication", "security", "jwt"])'),
88
111
  });
89
112
  /**
90
113
  * Domain context schema for domain-level context.md files.
@@ -137,13 +160,25 @@ const SubtopicContextSchema = z.object({
137
160
  * Single operation schema for curating knowledge.
138
161
  */
139
162
  const OperationSchema = z.object({
163
+ confidence: z
164
+ .enum(['high', 'low'])
165
+ .describe('Your confidence in the accuracy and completeness of this operation. Use "high" when you have direct evidence from the source material; use "low" when the information is inferred, uncertain, or incomplete.'),
140
166
  content: ContentSchema.optional().describe('Content for ADD/UPDATE operations'),
141
167
  domainContext: DomainContextSchema.optional().describe('Domain-level context for new domains. When creating content in a NEW domain, provide this to auto-generate domain/context.md with purpose, scope, ownership, and usage. Only needed when the domain does not exist yet.'),
168
+ impact: z
169
+ .enum(['high', 'low'])
170
+ .describe('Estimated scope of impact of this knowledge change. "high": Changes that alter core decisions, strategies, tools, or established approaches. Any change that contradicts or reverses previously curated knowledge. Updates to existing knowledge that change its core substance. Deletions are always high impact. "low": New additions to previously undocumented topics, minor corrections, supplementary details like examples and clarifications, or updates that extend existing knowledge without changing its core substance.'),
142
171
  mergeTarget: z.string().optional().describe('Target path for MERGE operation'),
143
172
  mergeTargetTitle: z.string().optional().describe('Title of the target file for MERGE operation'),
144
173
  path: z.string().describe('Path: domain/topic/title.md or domain/topic/subtopic/title.md'),
145
- reason: z.string().describe('Reasoning for this operation'),
174
+ reason: z
175
+ .string()
176
+ .describe('The motivation and context behind this curation — the WHY, not the what. Describe the decision, event, conversation, or observation that made this knowledge worth capturing. Write it for a human reviewer: they will read this in the web inbox to decide whether to approve or modify the change. Example of a good reason: "After debating caching strategies in PR #42, the team chose Redis with a 5-minute TTL as a deliberate performance/freshness trade-off — future agents should know this was intentional." Bad example: "Updating caching documentation."'),
146
177
  subtopicContext: SubtopicContextSchema.optional().describe('Subtopic-level context for new subtopics. When creating content in a NEW subtopic, provide this to auto-generate subtopic/context.md with focus and parent relation. Only needed when the subtopic does not exist yet.'),
178
+ summary: z
179
+ .string()
180
+ .optional()
181
+ .describe('One-line semantic summary of what this knowledge file contains after this operation. For human reviewers to quickly grasp the content without reading the full document. Example: "Caching strategy using Redis with 5-min TTL and write-through invalidation". Required for ADD/UPDATE/UPSERT/MERGE, not needed for DELETE.'),
147
182
  title: z
148
183
  .string()
149
184
  .optional()
@@ -155,21 +190,20 @@ const OperationSchema = z.object({
155
190
  * Filter out non-existent files from rawConcept.files.
156
191
  * Returns a new content object with only valid file paths.
157
192
  */
158
- function filterValidFiles(content) {
193
+ async function filterValidFiles(content) {
159
194
  if (!content.rawConcept?.files || content.rawConcept.files.length === 0) {
160
195
  return content;
161
196
  }
162
- const validFiles = content.rawConcept.files.filter((filePath) => {
197
+ const checks = await Promise.all(content.rawConcept.files.map(async (filePath) => {
163
198
  // Skip filesystem validation for URLs and document references
164
- if (filePath.includes('://')) {
199
+ if (filePath.includes('://'))
165
200
  return true;
166
- }
167
201
  // Skip entries that look like document references (no path separators, contain spaces)
168
- if (!filePath.includes('/') && !filePath.includes('\\') && filePath.includes(' ')) {
202
+ if (!filePath.includes('/') && !filePath.includes('\\') && filePath.includes(' '))
169
203
  return true;
170
- }
171
- return existsSync(filePath);
172
- });
204
+ return DirectoryManager.fileExists(filePath);
205
+ }));
206
+ const validFiles = content.rawConcept.files.filter((_, i) => checks[i]);
173
207
  // Return content with filtered files (empty array if none exist)
174
208
  return {
175
209
  ...content,
@@ -187,6 +221,45 @@ export const CurateInputSchema = z.object({
187
221
  basePath: z.string().default('.brv/context-tree').describe('Base path for knowledge storage'),
188
222
  operations: z.array(OperationSchema).describe('Array of curate operations to apply'),
189
223
  });
224
+ /**
225
+ * Derive review metadata for a curate operation.
226
+ * confidence and impact are LLM-provided (schema defaults applied by Zod when omitted).
227
+ * needsReview:
228
+ * - DELETE always (irreversible)
229
+ * - high impact always (core decisions, strategy changes, contradictions)
230
+ * - low impact → no review (minor additions/corrections)
231
+ */
232
+ function deriveReviewMetadata(type, confidence, impact) {
233
+ const needsReview = type === 'DELETE' || impact === 'high';
234
+ return { confidence, impact, needsReview };
235
+ }
236
+ /**
237
+ * Back up a file's content before curate overwrites or deletes it.
238
+ *
239
+ * First-write-wins: if a backup already exists for this path, this is a no-op.
240
+ * This ensures the backup always reflects the snapshot version (state at last push),
241
+ * even when multiple curate operations modify the same file between pushes.
242
+ *
243
+ * @param filePath - Absolute path to the context tree file being modified
244
+ * @param basePath - Context tree base path (e.g., '.brv/context-tree')
245
+ */
246
+ async function backupBeforeWrite(filePath, basePath) {
247
+ try {
248
+ const brvDir = dirname(resolve(basePath));
249
+ const relativePath = relative(resolve(basePath), resolve(filePath));
250
+ const backupPath = join(brvDir, REVIEW_BACKUPS_DIR, relativePath);
251
+ // First-write-wins: skip if backup already exists
252
+ const backupExists = await DirectoryManager.fileExists(backupPath);
253
+ if (backupExists)
254
+ return;
255
+ // Read current content and save as backup
256
+ const content = await DirectoryManager.readFile(filePath);
257
+ await DirectoryManager.writeFileAtomic(backupPath, content);
258
+ }
259
+ catch {
260
+ // Best-effort: backup failure must never block curate operations
261
+ }
262
+ }
190
263
  function generateDomainContextMarkdown(domainName, context) {
191
264
  const sections = [`# Domain: ${domainName}`, '', '## Purpose', context.purpose, '', '## Scope'];
192
265
  if (context.scope.included.length > 0) {
@@ -360,19 +433,24 @@ function buildFullPath(basePath, knowledgePath) {
360
433
  * Execute ADD operation - create new domain/topic/subtopic with {title}.md
361
434
  */
362
435
  async function executeAdd(basePath, operation) {
363
- const { content, domainContext, path, reason, subtopicContext, title, topicContext } = operation;
436
+ const { confidence, content, domainContext, impact, path, reason, subtopicContext, summary, title, topicContext } = operation;
437
+ const reviewMeta = deriveReviewMetadata('ADD', confidence, impact);
364
438
  if (!title) {
365
439
  return {
440
+ ...reviewMeta,
366
441
  message: 'ADD operation requires a title',
367
442
  path,
443
+ reason,
368
444
  status: 'failed',
369
445
  type: 'ADD',
370
446
  };
371
447
  }
372
448
  if (!content) {
373
449
  return {
450
+ ...reviewMeta,
374
451
  message: 'ADD operation requires content',
375
452
  path,
453
+ reason,
376
454
  status: 'failed',
377
455
  type: 'ADD',
378
456
  };
@@ -381,8 +459,10 @@ async function executeAdd(basePath, operation) {
381
459
  const parsed = parsePath(path);
382
460
  if (!parsed) {
383
461
  return {
462
+ ...reviewMeta,
384
463
  message: `Invalid path format: ${path}. Expected domain/topic or domain/topic/subtopic`,
385
464
  path,
465
+ reason,
386
466
  status: 'failed',
387
467
  type: 'ADD',
388
468
  };
@@ -390,8 +470,10 @@ async function executeAdd(basePath, operation) {
390
470
  const domainValidation = validateDomain(parsed.domain);
391
471
  if (!domainValidation.allowed) {
392
472
  return {
473
+ ...reviewMeta,
393
474
  message: domainValidation.reason,
394
475
  path,
476
+ reason,
395
477
  status: 'failed',
396
478
  type: 'ADD',
397
479
  };
@@ -401,16 +483,18 @@ async function executeAdd(basePath, operation) {
401
483
  const topicPath = join(domainPath, toSnakeCase(parsed.topic));
402
484
  const finalPath = parsed.subtopic ? join(topicPath, toSnakeCase(parsed.subtopic)) : topicPath;
403
485
  // Filter out non-existent files from rawConcept.files
404
- const filteredContent = filterValidFiles(content);
486
+ const filteredContent = await filterValidFiles(content);
405
487
  const contextContent = MarkdownWriter.generateContext({
406
488
  facts: filteredContent.facts,
407
489
  keywords: filteredContent.keywords,
408
490
  name: title,
409
491
  narrative: filteredContent.narrative,
410
492
  rawConcept: filteredContent.rawConcept,
493
+ reason,
411
494
  relations: filteredContent.relations,
412
495
  scoring: applyDefaultScoring(),
413
496
  snippets: filteredContent.snippets ?? [],
497
+ summary,
414
498
  tags: filteredContent.tags,
415
499
  });
416
500
  const filename = `${toSnakeCase(title)}.md`;
@@ -418,39 +502,57 @@ async function executeAdd(basePath, operation) {
418
502
  await DirectoryManager.writeFileAtomic(contextPath, contextContent);
419
503
  await ensureContextMd(basePath, parsed, topicContext, subtopicContext);
420
504
  return {
505
+ ...reviewMeta,
421
506
  filePath: contextPath,
422
507
  message: `Created ${path}/${filename} with ${content.snippets?.length || 0} snippets. Reason: ${reason}`,
423
508
  path,
509
+ reason,
424
510
  status: 'success',
511
+ summary,
425
512
  type: 'ADD',
426
513
  };
427
514
  }
428
515
  catch (error) {
429
516
  return {
517
+ ...reviewMeta,
430
518
  message: error instanceof Error ? error.message : String(error),
431
519
  path,
520
+ reason,
432
521
  status: 'failed',
433
522
  type: 'ADD',
434
523
  };
435
524
  }
436
525
  }
526
+ /**
527
+ * Compute the maximum of two impact levels.
528
+ */
529
+ function maxImpact(a, b) {
530
+ const rank = { high: 1, low: 0 };
531
+ return rank[a] >= rank[b] ? a : b;
532
+ }
437
533
  /**
438
534
  * Execute UPDATE operation - modify existing {title}.md
439
535
  */
440
536
  async function executeUpdate(basePath, operation) {
441
- const { content, domainContext, path, reason, subtopicContext, title, topicContext } = operation;
537
+ const { confidence, content, domainContext, impact, path, reason, subtopicContext, summary, title, topicContext } = operation;
538
+ // Used for early-exit validation failures (before structural loss can be assessed)
539
+ const baseReviewMeta = deriveReviewMetadata('UPDATE', confidence, impact);
442
540
  if (!title) {
443
541
  return {
542
+ ...baseReviewMeta,
444
543
  message: 'UPDATE operation requires a title',
445
544
  path,
545
+ reason,
446
546
  status: 'failed',
447
547
  type: 'UPDATE',
448
548
  };
449
549
  }
450
550
  if (!content) {
451
551
  return {
552
+ ...baseReviewMeta,
452
553
  message: 'UPDATE operation requires content',
453
554
  path,
555
+ reason,
454
556
  status: 'failed',
455
557
  type: 'UPDATE',
456
558
  };
@@ -459,8 +561,10 @@ async function executeUpdate(basePath, operation) {
459
561
  const parsed = parsePath(path);
460
562
  if (!parsed) {
461
563
  return {
564
+ ...baseReviewMeta,
462
565
  message: `Invalid path format: ${path}. Expected domain/topic or domain/topic/subtopic`,
463
566
  path,
567
+ reason,
464
568
  status: 'failed',
465
569
  type: 'UPDATE',
466
570
  };
@@ -471,46 +575,73 @@ async function executeUpdate(basePath, operation) {
471
575
  const exists = await DirectoryManager.fileExists(contextPath);
472
576
  if (!exists) {
473
577
  return {
578
+ ...baseReviewMeta,
474
579
  message: `File does not exist: ${path}/${filename}`,
475
580
  path,
581
+ reason,
476
582
  status: 'failed',
477
583
  type: 'UPDATE',
478
584
  };
479
585
  }
480
586
  await createDomainContextIfMissing(basePath, parsed.domain, domainContext);
481
- // Read existing file to preserve and update scoring metadata
587
+ // Read existing file to preserve scoring metadata and detect structural loss
482
588
  const existingContent = await DirectoryManager.readFile(contextPath);
483
589
  const existingScoring = existingContent ? parseFrontmatterScoring(existingContent) : undefined;
484
590
  const updatedScoring = existingScoring ? recordCurateUpdate(existingScoring) : applyDefaultScoring();
485
591
  const newTier = determineTier(updatedScoring.importance ?? 50, (updatedScoring.maturity ?? 'draft'));
486
592
  const finalScoring = { ...updatedScoring, maturity: newTier };
487
593
  // Filter out non-existent files from rawConcept.files
488
- const filteredContent = filterValidFiles(content);
489
- const contextContent = MarkdownWriter.generateContext({
594
+ const filteredContent = await filterValidFiles(content);
595
+ // Extract previous summary from existing file's frontmatter (for review UI)
596
+ const existingParsed = existingContent ? MarkdownWriter.parseContent(existingContent, title) : null;
597
+ const previousSummary = existingParsed?.summary;
598
+ // Detect structural loss and auto-resolve: merge back anything the LLM dropped
599
+ const proposedContextData = {
490
600
  facts: filteredContent.facts,
491
601
  keywords: filteredContent.keywords,
492
602
  name: title,
493
603
  narrative: filteredContent.narrative,
494
604
  rawConcept: filteredContent.rawConcept,
495
605
  relations: filteredContent.relations,
496
- scoring: finalScoring,
497
606
  snippets: filteredContent.snippets ?? [],
498
607
  tags: filteredContent.tags,
608
+ };
609
+ let resolvedContextData = proposedContextData;
610
+ let elevatedImpact = impact;
611
+ if (existingParsed) {
612
+ const loss = detectStructuralLoss(existingParsed, proposedContextData);
613
+ const structuralImpact = deriveImpactFromLoss(loss);
614
+ elevatedImpact = maxImpact(impact, structuralImpact);
615
+ resolvedContextData = resolveStructuralLoss(existingParsed, proposedContextData, loss);
616
+ }
617
+ const reviewMeta = deriveReviewMetadata('UPDATE', confidence, elevatedImpact);
618
+ const contextContent = MarkdownWriter.generateContext({
619
+ ...resolvedContextData,
620
+ reason,
621
+ scoring: finalScoring,
622
+ summary,
499
623
  });
624
+ await backupBeforeWrite(contextPath, basePath);
500
625
  await DirectoryManager.writeFileAtomic(contextPath, contextContent);
501
626
  await ensureContextMd(basePath, parsed, topicContext, subtopicContext);
502
627
  return {
628
+ ...reviewMeta,
503
629
  filePath: contextPath,
504
630
  message: `Updated ${path}/${filename}. Reason: ${reason}`,
505
631
  path,
632
+ previousSummary,
633
+ reason,
506
634
  status: 'success',
635
+ summary,
507
636
  type: 'UPDATE',
508
637
  };
509
638
  }
510
639
  catch (error) {
511
640
  return {
641
+ ...baseReviewMeta,
512
642
  message: error instanceof Error ? error.message : String(error),
513
643
  path,
644
+ reason,
514
645
  status: 'failed',
515
646
  type: 'UPDATE',
516
647
  };
@@ -521,19 +652,24 @@ async function executeUpdate(basePath, operation) {
521
652
  * This is the recommended operation type as it eliminates the need for pre-checks.
522
653
  */
523
654
  async function executeUpsert(basePath, operation) {
524
- const { path, title } = operation;
655
+ const { path, reason, title } = operation;
656
+ const reviewMeta = deriveReviewMetadata('UPSERT', operation.confidence, operation.impact);
525
657
  if (!title) {
526
658
  return {
659
+ ...reviewMeta,
527
660
  message: 'UPSERT operation requires a title',
528
661
  path,
662
+ reason,
529
663
  status: 'failed',
530
664
  type: 'UPSERT',
531
665
  };
532
666
  }
533
667
  if (!operation.content) {
534
668
  return {
669
+ ...reviewMeta,
535
670
  message: 'UPSERT operation requires content',
536
671
  path,
672
+ reason,
537
673
  status: 'failed',
538
674
  type: 'UPSERT',
539
675
  };
@@ -542,8 +678,10 @@ async function executeUpsert(basePath, operation) {
542
678
  const parsed = parsePath(path);
543
679
  if (!parsed) {
544
680
  return {
681
+ ...reviewMeta,
545
682
  message: `Invalid path format: ${path}. Expected domain/topic or domain/topic/subtopic`,
546
683
  path,
684
+ reason,
547
685
  status: 'failed',
548
686
  type: 'UPSERT',
549
687
  };
@@ -574,8 +712,10 @@ async function executeUpsert(basePath, operation) {
574
712
  }
575
713
  catch (error) {
576
714
  return {
715
+ ...reviewMeta,
577
716
  message: error instanceof Error ? error.message : String(error),
578
717
  path,
718
+ reason,
579
719
  status: 'failed',
580
720
  type: 'UPSERT',
581
721
  };
@@ -585,27 +725,34 @@ async function executeUpsert(basePath, operation) {
585
725
  * Execute MERGE operation - combine source file into target file, delete source file
586
726
  */
587
727
  async function executeMerge(basePath, operation) {
588
- const { domainContext, mergeTarget, mergeTargetTitle, path, reason, subtopicContext, title, topicContext } = operation;
728
+ const { confidence, domainContext, impact, mergeTarget, mergeTargetTitle, path, reason, subtopicContext, summary, title, topicContext, } = operation;
729
+ const reviewMeta = deriveReviewMetadata('MERGE', confidence, impact);
589
730
  if (!title) {
590
731
  return {
732
+ ...reviewMeta,
591
733
  message: 'MERGE operation requires a title (source file)',
592
734
  path,
735
+ reason,
593
736
  status: 'failed',
594
737
  type: 'MERGE',
595
738
  };
596
739
  }
597
740
  if (!mergeTarget) {
598
741
  return {
742
+ ...reviewMeta,
599
743
  message: 'MERGE operation requires mergeTarget',
600
744
  path,
745
+ reason,
601
746
  status: 'failed',
602
747
  type: 'MERGE',
603
748
  };
604
749
  }
605
750
  if (!mergeTargetTitle) {
606
751
  return {
752
+ ...reviewMeta,
607
753
  message: 'MERGE operation requires mergeTargetTitle',
608
754
  path,
755
+ reason,
609
756
  status: 'failed',
610
757
  type: 'MERGE',
611
758
  };
@@ -615,8 +762,10 @@ async function executeMerge(basePath, operation) {
615
762
  const targetParsed = parsePath(mergeTarget);
616
763
  if (!sourceParsed || !targetParsed) {
617
764
  return {
765
+ ...reviewMeta,
618
766
  message: `Invalid path format. Expected domain/topic or domain/topic/subtopic`,
619
767
  path,
768
+ reason,
620
769
  status: 'failed',
621
770
  type: 'MERGE',
622
771
  };
@@ -631,16 +780,20 @@ async function executeMerge(basePath, operation) {
631
780
  const targetExists = await DirectoryManager.fileExists(targetContextPath);
632
781
  if (!sourceExists) {
633
782
  return {
783
+ ...reviewMeta,
634
784
  message: `Source file does not exist: ${path}/${sourceFilename}`,
635
785
  path,
786
+ reason,
636
787
  status: 'failed',
637
788
  type: 'MERGE',
638
789
  };
639
790
  }
640
791
  if (!targetExists) {
641
792
  return {
793
+ ...reviewMeta,
642
794
  message: `Target file does not exist: ${mergeTarget}/${targetFilename}`,
643
795
  path,
796
+ reason,
644
797
  status: 'failed',
645
798
  type: 'MERGE',
646
799
  };
@@ -649,23 +802,36 @@ async function executeMerge(basePath, operation) {
649
802
  await createDomainContextIfMissing(basePath, targetParsed.domain, domainContext);
650
803
  const sourceContent = await DirectoryManager.readFile(sourceContextPath);
651
804
  const targetContent = await DirectoryManager.readFile(targetContextPath);
652
- const mergedContent = MarkdownWriter.mergeContexts(sourceContent, targetContent);
805
+ // Extract previous summary from target file (for review UI)
806
+ const targetParsedContent = MarkdownWriter.parseContent(targetContent, mergeTargetTitle);
807
+ const previousSummary = targetParsedContent.summary;
808
+ // Backup both files before merge modifies target and deletes source
809
+ await backupBeforeWrite(targetContextPath, basePath);
810
+ await backupBeforeWrite(sourceContextPath, basePath);
811
+ const mergedContent = MarkdownWriter.mergeContexts(sourceContent, targetContent, reason, summary);
653
812
  await DirectoryManager.writeFileAtomic(targetContextPath, mergedContent);
654
813
  await DirectoryManager.deleteFile(sourceContextPath);
655
814
  await ensureContextMd(basePath, sourceParsed, topicContext, subtopicContext);
656
815
  await ensureContextMd(basePath, targetParsed, topicContext, subtopicContext);
657
816
  return {
817
+ ...reviewMeta,
818
+ additionalFilePaths: [sourceContextPath],
658
819
  filePath: targetContextPath,
659
820
  message: `Merged ${path}/${sourceFilename} into ${mergeTarget}/${targetFilename}. Reason: ${reason}`,
660
821
  path,
822
+ previousSummary,
823
+ reason,
661
824
  status: 'success',
825
+ summary,
662
826
  type: 'MERGE',
663
827
  };
664
828
  }
665
829
  catch (error) {
666
830
  return {
831
+ ...reviewMeta,
667
832
  message: error instanceof Error ? error.message : String(error),
668
833
  path,
834
+ reason,
669
835
  status: 'failed',
670
836
  type: 'MERGE',
671
837
  };
@@ -677,6 +843,7 @@ async function executeMerge(basePath, operation) {
677
843
  */
678
844
  async function executeDelete(basePath, operation) {
679
845
  const { path, reason, title } = operation;
846
+ const reviewMeta = deriveReviewMetadata('DELETE', operation.confidence, operation.impact);
680
847
  try {
681
848
  const fullPath = buildFullPath(basePath, path);
682
849
  if (title) {
@@ -686,16 +853,34 @@ async function executeDelete(basePath, operation) {
686
853
  const exists = await DirectoryManager.fileExists(filePath);
687
854
  if (!exists) {
688
855
  return {
856
+ ...reviewMeta,
689
857
  message: `File does not exist: ${path}/${filename}`,
690
858
  path,
859
+ reason,
691
860
  status: 'failed',
692
861
  type: 'DELETE',
693
862
  };
694
863
  }
864
+ // Extract previous summary from file being deleted (for review UI)
865
+ let previousSummary;
866
+ try {
867
+ const existingContent = await DirectoryManager.readFile(filePath);
868
+ if (existingContent) {
869
+ previousSummary = MarkdownWriter.parseContent(existingContent, title).summary;
870
+ }
871
+ }
872
+ catch {
873
+ // Best-effort: summary extraction failure must never block delete
874
+ }
875
+ await backupBeforeWrite(filePath, basePath);
695
876
  await DirectoryManager.deleteFile(filePath);
696
877
  return {
878
+ ...reviewMeta,
879
+ filePath,
697
880
  message: `Deleted ${path}/${filename}. Reason: ${reason}`,
698
881
  path,
882
+ previousSummary,
883
+ reason,
699
884
  status: 'success',
700
885
  type: 'DELETE',
701
886
  };
@@ -704,24 +889,56 @@ async function executeDelete(basePath, operation) {
704
889
  const exists = await DirectoryManager.folderExists(fullPath);
705
890
  if (!exists) {
706
891
  return {
892
+ ...reviewMeta,
707
893
  message: `Folder does not exist: ${path}`,
708
894
  path,
895
+ reason,
709
896
  status: 'failed',
710
897
  type: 'DELETE',
711
898
  };
712
899
  }
900
+ // Backup all markdown files in the folder before deleting
901
+ const mdFiles = await DirectoryManager.listMarkdownFiles(fullPath);
902
+ // Extract previous summary as bullet list of individual file summaries (for review UI)
903
+ let previousSummary;
904
+ try {
905
+ const contentFiles = mdFiles.filter((f) => {
906
+ const name = basename(f);
907
+ return name !== '_index.md' && name !== 'context.md';
908
+ });
909
+ const contents = await Promise.all(contentFiles.map(async (f) => ({ content: await DirectoryManager.readFile(f), name: basename(f) })));
910
+ const bullets = contents
911
+ .filter((c) => c.content !== null && c.content !== undefined)
912
+ .map((c) => ({ name: c.name, summary: MarkdownWriter.parseContent(c.content, c.name.replace(/\.md$/, '')).summary }))
913
+ .filter((c) => c.summary !== undefined)
914
+ .map((c) => `- ${c.name.replace(/\.md$/, '').replaceAll('_', ' ')}: ${c.summary}`);
915
+ if (bullets.length > 0) {
916
+ previousSummary = bullets.join('\n');
917
+ }
918
+ }
919
+ catch {
920
+ // Best-effort: summary extraction failure must never block delete
921
+ }
922
+ await Promise.all(mdFiles.map((f) => backupBeforeWrite(f, basePath)));
713
923
  await DirectoryManager.deleteTopicRecursive(fullPath);
714
924
  return {
925
+ ...reviewMeta,
926
+ additionalFilePaths: mdFiles,
927
+ filePath: fullPath,
715
928
  message: `Deleted folder ${path}. Reason: ${reason}`,
716
929
  path,
930
+ previousSummary,
931
+ reason,
717
932
  status: 'success',
718
933
  type: 'DELETE',
719
934
  };
720
935
  }
721
936
  catch (error) {
722
937
  return {
938
+ ...reviewMeta,
723
939
  message: error instanceof Error ? error.message : String(error),
724
940
  path,
941
+ reason,
725
942
  status: 'failed',
726
943
  type: 'DELETE',
727
944
  };
@@ -737,8 +954,12 @@ export async function executeCurate(input, _context) {
737
954
  return {
738
955
  applied: [
739
956
  {
957
+ confidence: 'high',
958
+ impact: 'low',
740
959
  message: `Invalid input: ${parseResult.error.message}`,
960
+ needsReview: false,
741
961
  path: '',
962
+ reason: '',
742
963
  status: 'failed',
743
964
  type: 'ADD',
744
965
  },
@@ -806,8 +1027,12 @@ export async function executeCurate(input, _context) {
806
1027
  // Exhaustive type check - TypeScript will error if any case is missed
807
1028
  const exhaustiveCheck = operation.type;
808
1029
  result = {
1030
+ confidence: 'high',
1031
+ impact: 'low',
809
1032
  message: `Unknown operation type: ${exhaustiveCheck}`,
1033
+ needsReview: false,
810
1034
  path: operation.path,
1035
+ reason: operation.reason,
811
1036
  status: 'failed',
812
1037
  type: operation.type,
813
1038
  };
@@ -847,13 +1072,15 @@ export function createCurateTool(workingDirectory) {
847
1072
 
848
1073
  **Operations:**
849
1074
  1. **ADD** - Create new titled context file in domain/topic/subtopic
850
- - Requires: path, title, content (snippets and/or relations), reason
1075
+ - Requires: path, title, content, confidence, impact, reason
851
1076
  - Relations must be in the format of "domain/topic/title.md" or "domain/topic/subtopic/title.md"
852
1077
  - Example with Raw Concept + Narrative:
853
1078
  {
854
1079
  type: "ADD",
855
1080
  path: "structure/caching",
856
1081
  title: "Redis User Permissions",
1082
+ confidence: "high",
1083
+ impact: "medium",
857
1084
  content: {
858
1085
  rawConcept: {
859
1086
  task: "Introduce Redis cache for getUserPermissions(userId)",
@@ -869,26 +1096,46 @@ export function createCurateTool(workingDirectory) {
869
1096
  },
870
1097
  relations: ["structure/api-endpoints/validation.md", "structure/api-endpoints/error-handling/retry-logic.md"]
871
1098
  },
872
- reason: "New caching pattern"
1099
+ reason: "Introduced after team discussion in PR #42: chose Redis over in-process cache to share state across replicas. The 5-minute staleness was a deliberate trade-off, not an oversight — future agents should not 'fix' it."
873
1100
  }
874
1101
  - Creates: structure/caching/redis_user_permissions.md
875
1102
 
876
1103
  2. **UPDATE** - Modify existing titled context file (full replacement)
877
- - Requires: path, title, content, reason
1104
+ - Requires: path, title, content, confidence, impact, reason
878
1105
  - Relations must be in the format of "domain/topic/title.md" or "domain/topic/subtopic/title.md"
879
1106
  - Supports same content structure as ADD
1107
+ - reason example: \`"Token expiry was changed from 1h to 15min in the security audit (Jira SEC-204). Updated to reflect the new default and the reasoning: shorter TTL reduces blast radius of leaked tokens."\`
880
1108
 
881
1109
  3. **MERGE** - Combine source file into target file, delete source
882
- - Requires: path (source), title (source file), mergeTarget (destination path), mergeTargetTitle (destination file), reason
883
- - Example: { type: "MERGE", path: "code_style/old_topic", title: "Old Guide", mergeTarget: "code_style/new_topic", mergeTargetTitle: "New Guide", reason: "Consolidating" }
1110
+ - Requires: path (source), title (source file), mergeTarget (destination path), mergeTargetTitle (destination file), confidence, impact, reason
1111
+ - Example: { type: "MERGE", path: "code_style/old_topic", title: "Old Guide", mergeTarget: "code_style/new_topic", mergeTargetTitle: "New Guide", confidence: "high", impact: "medium", reason: "Both files cover the same conventions; merging keeps a single source of truth and avoids contradictions." }
884
1112
  - Raw concepts and narratives are intelligently merged
885
1113
 
886
1114
  4. **DELETE** - Remove specific file or entire folder
887
- - Requires: path, title (optional), reason
1115
+ - Requires: path, title (optional), confidence, impact, reason
888
1116
  - With title: deletes specific file; without title: deletes entire folder
1117
+ - Example: { type: "DELETE", path: "auth/legacy", title: "Session Token Flow", confidence: "high", impact: "high", reason: "Session-based auth was fully replaced by JWT in v2.0 (PR #88). Keeping this would mislead future agents into thinking sessions are still in use." }
1118
+
1119
+ **Review Metadata (per operation — always provide these):**
1120
+ - **confidence**: How confident you are in the accuracy/completeness of this knowledge.
1121
+ - \`"high"\`: You have direct evidence from the source material, codebase, or conversation.
1122
+ - \`"low"\`: The information is inferred, partially known, or uncertain.
1123
+ - **impact**: The scope of this knowledge change.
1124
+ - \`"high"\`: A deletion, or a major architectural/structural change.
1125
+ - \`"medium"\`: A significant update to existing knowledge.
1126
+ - \`"low"\`: A new addition or minor update.
1127
+ - **reason**: The human-readable motivation for this curation. This is the most important review field — a human reviewer will read it in the web inbox to decide whether to approve, edit, or reject the change.
1128
+ - **Capture the WHY, not the what.** The what is already encoded in type, path, title, and content. The reason should answer: *What triggered this? What decision was made? What context would be lost without this knowledge?*
1129
+ - **Write for a future human or agent reader**, not for yourself in the moment. Ask: "If someone reads this 6 months from now with no context, will they understand why this knowledge exists and why it should not be changed?"
1130
+ - **Include trade-offs and intent.** If a decision was deliberate (a performance trade-off, a rejected alternative, a known limitation), say so explicitly — this prevents future agents from "fixing" something that was intentional.
1131
+ - Good: \`"Decided in PR #42 to use Redis over in-process cache to share state across replicas. The 5-min staleness is a deliberate trade-off — do not optimize it away."\`
1132
+ - Good: \`"Session auth was fully removed in v2.0; keeping this doc would mislead future agents into thinking it's still active."\`
1133
+ - Bad: \`"Updating caching docs"\` — describes the operation, not the motivation
1134
+ - Bad: \`"New pattern"\` — vague, gives a reviewer nothing to evaluate
1135
+ - Low-confidence or DELETE operations are automatically flagged for human review in the web inbox after \`brv push\`.
889
1136
 
890
1137
  **CRITICAL - Path vs Title separation:**
891
- - "path" = folder location only (domain/topic or domain/topic/subtopic) - NEVER include file extension suffixes
1138
+ - "path" = folder location only (domain/topic or domain/topic/subtopic) - NEVER include file extension suffixes
892
1139
  - "title" = the context name (becomes {title}.md file automatically)
893
1140
  - The system auto-generates the .md file from title - DO NOT put .md or _md anywhere in path
894
1141