@jlongo78/agent-spaces 0.7.5 → 0.7.6

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 (630) hide show
  1. package/.next/standalone/.claude/settings.local.json +55 -0
  2. package/.next/standalone/.claude/spaces-env.json +1 -0
  3. package/.next/standalone/.next/BUILD_ID +1 -1
  4. package/.next/standalone/.next/app-path-routes-manifest.json +2 -1
  5. package/.next/standalone/.next/build-manifest.json +5 -5
  6. package/.next/standalone/.next/prerender-manifest.json +27 -3
  7. package/.next/standalone/.next/required-server-files.json +19 -19
  8. package/.next/standalone/.next/routes-manifest.json +6 -0
  9. package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page/build-manifest.json +3 -3
  10. package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page_client-reference-manifest.js +1 -1
  11. package/.next/standalone/.next/server/app/(desktop)/admin/users/page/build-manifest.json +3 -3
  12. package/.next/standalone/.next/server/app/(desktop)/admin/users/page_client-reference-manifest.js +1 -1
  13. package/.next/standalone/.next/server/app/(desktop)/analytics/page/build-manifest.json +3 -3
  14. package/.next/standalone/.next/server/app/(desktop)/analytics/page_client-reference-manifest.js +1 -1
  15. package/.next/standalone/.next/server/app/(desktop)/cortex/page/build-manifest.json +3 -3
  16. package/.next/standalone/.next/server/app/(desktop)/cortex/page/react-loadable-manifest.json +3 -3
  17. package/.next/standalone/.next/server/app/(desktop)/cortex/page.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/(desktop)/cortex/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/.next/server/app/(desktop)/network/page/build-manifest.json +3 -3
  20. package/.next/standalone/.next/server/app/(desktop)/network/page_client-reference-manifest.js +1 -1
  21. package/.next/standalone/.next/server/app/(desktop)/page/build-manifest.json +3 -3
  22. package/.next/standalone/.next/server/app/(desktop)/page_client-reference-manifest.js +1 -1
  23. package/.next/standalone/.next/server/app/(desktop)/projects/page/build-manifest.json +3 -3
  24. package/.next/standalone/.next/server/app/(desktop)/projects/page_client-reference-manifest.js +1 -1
  25. package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page/build-manifest.json +3 -3
  26. package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page_client-reference-manifest.js +1 -1
  27. package/.next/standalone/.next/server/app/(desktop)/sessions/page/build-manifest.json +3 -3
  28. package/.next/standalone/.next/server/app/(desktop)/sessions/page_client-reference-manifest.js +1 -1
  29. package/.next/standalone/.next/server/app/(desktop)/settings/page/build-manifest.json +3 -3
  30. package/.next/standalone/.next/server/app/(desktop)/settings/page_client-reference-manifest.js +1 -1
  31. package/.next/standalone/.next/server/app/(desktop)/terminal/page/build-manifest.json +3 -3
  32. package/.next/standalone/.next/server/app/(desktop)/terminal/page.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/(desktop)/terminal/page_client-reference-manifest.js +1 -1
  34. package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page/build-manifest.json +3 -3
  35. package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page_client-reference-manifest.js +1 -1
  37. package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page/build-manifest.json +3 -3
  38. package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/(desktop)/workspaces/page/build-manifest.json +3 -3
  41. package/.next/standalone/.next/server/app/(desktop)/workspaces/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  43. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  44. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  45. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  46. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  47. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  48. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  49. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  50. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  51. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  53. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  54. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  55. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  56. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  57. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  58. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  59. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  60. package/.next/standalone/.next/server/app/admin/analytics.html +1 -1
  61. package/.next/standalone/.next/server/app/admin/analytics.rsc +7 -6
  62. package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics/__PAGE__.segment.rsc +2 -2
  63. package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics.segment.rsc +1 -1
  64. package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
  65. package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  66. package/.next/standalone/.next/server/app/admin/analytics.segments/_full.segment.rsc +7 -6
  67. package/.next/standalone/.next/server/app/admin/analytics.segments/_head.segment.rsc +1 -1
  68. package/.next/standalone/.next/server/app/admin/analytics.segments/_index.segment.rsc +2 -2
  69. package/.next/standalone/.next/server/app/admin/analytics.segments/_tree.segment.rsc +2 -2
  70. package/.next/standalone/.next/server/app/admin/users.html +1 -1
  71. package/.next/standalone/.next/server/app/admin/users.rsc +2 -2
  72. package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users/__PAGE__.segment.rsc +1 -1
  73. package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users.segment.rsc +1 -1
  74. package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
  75. package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  76. package/.next/standalone/.next/server/app/admin/users.segments/_full.segment.rsc +2 -2
  77. package/.next/standalone/.next/server/app/admin/users.segments/_head.segment.rsc +1 -1
  78. package/.next/standalone/.next/server/app/admin/users.segments/_index.segment.rsc +2 -2
  79. package/.next/standalone/.next/server/app/admin/users.segments/_tree.segment.rsc +2 -2
  80. package/.next/standalone/.next/server/app/analytics.html +1 -1
  81. package/.next/standalone/.next/server/app/analytics.rsc +3 -3
  82. package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics/__PAGE__.segment.rsc +2 -2
  83. package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics.segment.rsc +1 -1
  84. package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  85. package/.next/standalone/.next/server/app/analytics.segments/_full.segment.rsc +3 -3
  86. package/.next/standalone/.next/server/app/analytics.segments/_head.segment.rsc +1 -1
  87. package/.next/standalone/.next/server/app/analytics.segments/_index.segment.rsc +2 -2
  88. package/.next/standalone/.next/server/app/analytics.segments/_tree.segment.rsc +2 -2
  89. package/.next/standalone/.next/server/app/api/analytics/overview/route.js.nft.json +1 -1
  90. package/.next/standalone/.next/server/app/api/bulk/route.js.nft.json +1 -1
  91. package/.next/standalone/.next/server/app/api/config/route.js.nft.json +1 -1
  92. package/.next/standalone/.next/server/app/api/cortex/context/route.js.nft.json +1 -1
  93. package/.next/standalone/.next/server/app/api/cortex/curation/assess/route.js.nft.json +1 -1
  94. package/.next/standalone/.next/server/app/api/cortex/curation/publish/route.js.nft.json +1 -1
  95. package/.next/standalone/.next/server/app/api/cortex/curation/refine/route.js.nft.json +1 -1
  96. package/.next/standalone/.next/server/app/api/cortex/curation/review/route.js.nft.json +1 -1
  97. package/.next/standalone/.next/server/app/api/cortex/curation/seed/route.js.nft.json +1 -1
  98. package/.next/standalone/.next/server/app/api/cortex/export/route.js.nft.json +1 -1
  99. package/.next/standalone/.next/server/app/api/cortex/federation/pending/route.js.nft.json +1 -1
  100. package/.next/standalone/.next/server/app/api/cortex/federation/resolve/route.js.nft.json +1 -1
  101. package/.next/standalone/.next/server/app/api/cortex/federation/search/route.js.nft.json +1 -1
  102. package/.next/standalone/.next/server/app/api/cortex/federation/teach/route.js.nft.json +1 -1
  103. package/.next/standalone/.next/server/app/api/cortex/graph/edges/route.js.nft.json +1 -1
  104. package/.next/standalone/.next/server/app/api/cortex/graph/entities/[id]/route.js.nft.json +1 -1
  105. package/.next/standalone/.next/server/app/api/cortex/graph/entities/route.js.nft.json +1 -1
  106. package/.next/standalone/.next/server/app/api/cortex/graph/populate/route.js.nft.json +1 -1
  107. package/.next/standalone/.next/server/app/api/cortex/import/route.js.nft.json +1 -1
  108. package/.next/standalone/.next/server/app/api/cortex/import/status/route.js.nft.json +1 -1
  109. package/.next/standalone/.next/server/app/api/cortex/ingest/bootstrap/route.js.nft.json +1 -1
  110. package/.next/standalone/.next/server/app/api/cortex/ingest/status/route.js.nft.json +1 -1
  111. package/.next/standalone/.next/server/app/api/cortex/knowledge/[id]/route.js.nft.json +1 -1
  112. package/.next/standalone/.next/server/app/api/cortex/knowledge/route.js.nft.json +1 -1
  113. package/.next/standalone/.next/server/app/api/cortex/lobes/[id]/route.js.nft.json +1 -1
  114. package/.next/standalone/.next/server/app/api/cortex/lobes/route.js.nft.json +1 -1
  115. package/.next/standalone/.next/server/app/api/cortex/lobes/share/route.js.nft.json +1 -1
  116. package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route.js.nft.json +1 -1
  117. package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route.js.nft.json +1 -1
  118. package/.next/standalone/.next/server/app/api/cortex/mcp/call/route.js.nft.json +1 -1
  119. package/.next/standalone/.next/server/app/api/cortex/mcp/tools/route.js.nft.json +1 -1
  120. package/.next/standalone/.next/server/app/api/cortex/search/route.js.nft.json +1 -1
  121. package/.next/standalone/.next/server/app/api/cortex/settings/route.js.nft.json +1 -1
  122. package/.next/standalone/.next/server/app/api/cortex/status/route.js.nft.json +1 -1
  123. package/.next/standalone/.next/server/app/api/cortex/timeline/route.js.nft.json +1 -1
  124. package/.next/standalone/.next/server/app/api/cortex/usage/route.js.nft.json +1 -1
  125. package/.next/standalone/.next/server/app/api/cortex/workspace/[id]/context/route.js.nft.json +1 -1
  126. package/.next/standalone/.next/server/app/api/events/route.js.nft.json +1 -1
  127. package/.next/standalone/.next/server/app/api/folders/route.js.nft.json +1 -1
  128. package/.next/standalone/.next/server/app/api/network/handshake/route.js.nft.json +1 -1
  129. package/.next/standalone/.next/server/app/api/network/projects/route.js.nft.json +1 -1
  130. package/.next/standalone/.next/server/app/api/network/search/route.js.nft.json +1 -1
  131. package/.next/standalone/.next/server/app/api/network/sessions/[id]/messages/route.js.nft.json +1 -1
  132. package/.next/standalone/.next/server/app/api/network/sessions/[id]/route.js.nft.json +1 -1
  133. package/.next/standalone/.next/server/app/api/network/sessions/route.js.nft.json +1 -1
  134. package/.next/standalone/.next/server/app/api/network/workspaces/[id]/route.js.nft.json +1 -1
  135. package/.next/standalone/.next/server/app/api/network/workspaces/route.js.nft.json +1 -1
  136. package/.next/standalone/.next/server/app/api/panes/[id]/route.js.nft.json +1 -1
  137. package/.next/standalone/.next/server/app/api/panes/route.js.nft.json +1 -1
  138. package/.next/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  139. package/.next/standalone/.next/server/app/api/search/route.js.nft.json +1 -1
  140. package/.next/standalone/.next/server/app/api/sessions/[id]/chat/route.js.nft.json +1 -1
  141. package/.next/standalone/.next/server/app/api/sessions/[id]/messages/route.js.nft.json +1 -1
  142. package/.next/standalone/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  143. package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
  144. package/.next/standalone/.next/server/app/api/sync/route.js.nft.json +1 -1
  145. package/.next/standalone/.next/server/app/api/tags/route.js.nft.json +1 -1
  146. package/.next/standalone/.next/server/app/api/tier/route.js.nft.json +1 -1
  147. package/.next/standalone/.next/server/app/api/workspaces/[id]/context/[key]/route.js.nft.json +1 -1
  148. package/.next/standalone/.next/server/app/api/workspaces/[id]/context/route.js.nft.json +1 -1
  149. package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/[msgId]/route.js.nft.json +1 -1
  150. package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/route.js.nft.json +1 -1
  151. package/.next/standalone/.next/server/app/api/workspaces/[id]/route.js.nft.json +1 -1
  152. package/.next/standalone/.next/server/app/api/workspaces/[id]/sessions/route.js.nft.json +1 -1
  153. package/.next/standalone/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  154. package/.next/standalone/.next/server/app/cortex.html +1 -1
  155. package/.next/standalone/.next/server/app/cortex.rsc +3 -3
  156. package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex/__PAGE__.segment.rsc +2 -2
  157. package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex.segment.rsc +1 -1
  158. package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  159. package/.next/standalone/.next/server/app/cortex.segments/_full.segment.rsc +3 -3
  160. package/.next/standalone/.next/server/app/cortex.segments/_head.segment.rsc +1 -1
  161. package/.next/standalone/.next/server/app/cortex.segments/_index.segment.rsc +2 -2
  162. package/.next/standalone/.next/server/app/cortex.segments/_tree.segment.rsc +2 -2
  163. package/.next/standalone/.next/server/app/login/page/build-manifest.json +3 -3
  164. package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  165. package/.next/standalone/.next/server/app/login.html +1 -1
  166. package/.next/standalone/.next/server/app/login.rsc +2 -2
  167. package/.next/standalone/.next/server/app/login.segments/_full.segment.rsc +2 -2
  168. package/.next/standalone/.next/server/app/login.segments/_head.segment.rsc +1 -1
  169. package/.next/standalone/.next/server/app/login.segments/_index.segment.rsc +2 -2
  170. package/.next/standalone/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  171. package/.next/standalone/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
  172. package/.next/standalone/.next/server/app/login.segments/login.segment.rsc +1 -1
  173. package/.next/standalone/.next/server/app/m/page/build-manifest.json +3 -3
  174. package/.next/standalone/.next/server/app/m/page_client-reference-manifest.js +1 -1
  175. package/.next/standalone/.next/server/app/m/projects/page/build-manifest.json +3 -3
  176. package/.next/standalone/.next/server/app/m/projects/page_client-reference-manifest.js +1 -1
  177. package/.next/standalone/.next/server/app/m/projects.html +1 -1
  178. package/.next/standalone/.next/server/app/m/projects.rsc +2 -2
  179. package/.next/standalone/.next/server/app/m/projects.segments/_full.segment.rsc +2 -2
  180. package/.next/standalone/.next/server/app/m/projects.segments/_head.segment.rsc +1 -1
  181. package/.next/standalone/.next/server/app/m/projects.segments/_index.segment.rsc +2 -2
  182. package/.next/standalone/.next/server/app/m/projects.segments/_tree.segment.rsc +2 -2
  183. package/.next/standalone/.next/server/app/m/projects.segments/m/projects/__PAGE__.segment.rsc +1 -1
  184. package/.next/standalone/.next/server/app/m/projects.segments/m/projects.segment.rsc +1 -1
  185. package/.next/standalone/.next/server/app/m/projects.segments/m.segment.rsc +1 -1
  186. package/.next/standalone/.next/server/app/m/sessions/[id]/page/build-manifest.json +3 -3
  187. package/.next/standalone/.next/server/app/m/sessions/[id]/page_client-reference-manifest.js +1 -1
  188. package/.next/standalone/.next/server/app/m/sessions/page/build-manifest.json +3 -3
  189. package/.next/standalone/.next/server/app/m/sessions/page_client-reference-manifest.js +1 -1
  190. package/.next/standalone/.next/server/app/m/sessions.html +1 -1
  191. package/.next/standalone/.next/server/app/m/sessions.rsc +2 -2
  192. package/.next/standalone/.next/server/app/m/sessions.segments/_full.segment.rsc +2 -2
  193. package/.next/standalone/.next/server/app/m/sessions.segments/_head.segment.rsc +1 -1
  194. package/.next/standalone/.next/server/app/m/sessions.segments/_index.segment.rsc +2 -2
  195. package/.next/standalone/.next/server/app/m/sessions.segments/_tree.segment.rsc +2 -2
  196. package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions/__PAGE__.segment.rsc +1 -1
  197. package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions.segment.rsc +1 -1
  198. package/.next/standalone/.next/server/app/m/sessions.segments/m.segment.rsc +1 -1
  199. package/.next/standalone/.next/server/app/m/settings/page/build-manifest.json +3 -3
  200. package/.next/standalone/.next/server/app/m/settings/page_client-reference-manifest.js +1 -1
  201. package/.next/standalone/.next/server/app/m/settings.html +1 -1
  202. package/.next/standalone/.next/server/app/m/settings.rsc +2 -2
  203. package/.next/standalone/.next/server/app/m/settings.segments/_full.segment.rsc +2 -2
  204. package/.next/standalone/.next/server/app/m/settings.segments/_head.segment.rsc +1 -1
  205. package/.next/standalone/.next/server/app/m/settings.segments/_index.segment.rsc +2 -2
  206. package/.next/standalone/.next/server/app/m/settings.segments/_tree.segment.rsc +2 -2
  207. package/.next/standalone/.next/server/app/m/settings.segments/m/settings/__PAGE__.segment.rsc +1 -1
  208. package/.next/standalone/.next/server/app/m/settings.segments/m/settings.segment.rsc +1 -1
  209. package/.next/standalone/.next/server/app/m/settings.segments/m.segment.rsc +1 -1
  210. package/.next/standalone/.next/server/app/m/terminal/page/build-manifest.json +3 -3
  211. package/.next/standalone/.next/server/app/m/terminal/page_client-reference-manifest.js +1 -1
  212. package/.next/standalone/.next/server/app/m/terminal.html +1 -1
  213. package/.next/standalone/.next/server/app/m/terminal.rsc +2 -2
  214. package/.next/standalone/.next/server/app/m/terminal.segments/_full.segment.rsc +2 -2
  215. package/.next/standalone/.next/server/app/m/terminal.segments/_head.segment.rsc +1 -1
  216. package/.next/standalone/.next/server/app/m/terminal.segments/_index.segment.rsc +2 -2
  217. package/.next/standalone/.next/server/app/m/terminal.segments/_tree.segment.rsc +2 -2
  218. package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal/__PAGE__.segment.rsc +1 -1
  219. package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal.segment.rsc +1 -1
  220. package/.next/standalone/.next/server/app/m/terminal.segments/m.segment.rsc +1 -1
  221. package/.next/standalone/.next/server/app/m.html +1 -1
  222. package/.next/standalone/.next/server/app/m.rsc +2 -2
  223. package/.next/standalone/.next/server/app/m.segments/_full.segment.rsc +2 -2
  224. package/.next/standalone/.next/server/app/m.segments/_head.segment.rsc +1 -1
  225. package/.next/standalone/.next/server/app/m.segments/_index.segment.rsc +2 -2
  226. package/.next/standalone/.next/server/app/m.segments/_tree.segment.rsc +2 -2
  227. package/.next/standalone/.next/server/app/m.segments/m/__PAGE__.segment.rsc +1 -1
  228. package/.next/standalone/.next/server/app/m.segments/m.segment.rsc +1 -1
  229. package/.next/standalone/.next/server/app/network.html +1 -1
  230. package/.next/standalone/.next/server/app/network.rsc +2 -2
  231. package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network/__PAGE__.segment.rsc +1 -1
  232. package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network.segment.rsc +1 -1
  233. package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  234. package/.next/standalone/.next/server/app/network.segments/_full.segment.rsc +2 -2
  235. package/.next/standalone/.next/server/app/network.segments/_head.segment.rsc +1 -1
  236. package/.next/standalone/.next/server/app/network.segments/_index.segment.rsc +2 -2
  237. package/.next/standalone/.next/server/app/network.segments/_tree.segment.rsc +2 -2
  238. package/.next/standalone/.next/server/app/projects.html +1 -1
  239. package/.next/standalone/.next/server/app/projects.rsc +2 -2
  240. package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects/__PAGE__.segment.rsc +1 -1
  241. package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects.segment.rsc +1 -1
  242. package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  243. package/.next/standalone/.next/server/app/projects.segments/_full.segment.rsc +2 -2
  244. package/.next/standalone/.next/server/app/projects.segments/_head.segment.rsc +1 -1
  245. package/.next/standalone/.next/server/app/projects.segments/_index.segment.rsc +2 -2
  246. package/.next/standalone/.next/server/app/projects.segments/_tree.segment.rsc +2 -2
  247. package/.next/standalone/.next/server/app/sessions.html +1 -1
  248. package/.next/standalone/.next/server/app/sessions.rsc +2 -2
  249. package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions/__PAGE__.segment.rsc +1 -1
  250. package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions.segment.rsc +1 -1
  251. package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  252. package/.next/standalone/.next/server/app/sessions.segments/_full.segment.rsc +2 -2
  253. package/.next/standalone/.next/server/app/sessions.segments/_head.segment.rsc +1 -1
  254. package/.next/standalone/.next/server/app/sessions.segments/_index.segment.rsc +2 -2
  255. package/.next/standalone/.next/server/app/sessions.segments/_tree.segment.rsc +2 -2
  256. package/.next/standalone/.next/server/app/settings.html +1 -1
  257. package/.next/standalone/.next/server/app/settings.rsc +2 -2
  258. package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings/__PAGE__.segment.rsc +1 -1
  259. package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings.segment.rsc +1 -1
  260. package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  261. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +2 -2
  262. package/.next/standalone/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  263. package/.next/standalone/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  264. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  265. package/.next/standalone/.next/server/app/terminal.html +1 -1
  266. package/.next/standalone/.next/server/app/terminal.rsc +3 -3
  267. package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal/__PAGE__.segment.rsc +2 -2
  268. package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal.segment.rsc +1 -1
  269. package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  270. package/.next/standalone/.next/server/app/terminal.segments/_full.segment.rsc +3 -3
  271. package/.next/standalone/.next/server/app/terminal.segments/_head.segment.rsc +1 -1
  272. package/.next/standalone/.next/server/app/terminal.segments/_index.segment.rsc +2 -2
  273. package/.next/standalone/.next/server/app/terminal.segments/_tree.segment.rsc +2 -2
  274. package/.next/standalone/.next/server/app/vr/page/app-paths-manifest.json +3 -0
  275. package/.next/standalone/.next/server/app/vr/page/build-manifest.json +18 -0
  276. package/.next/standalone/.next/server/app/vr/page/next-font-manifest.json +11 -0
  277. package/.next/standalone/.next/server/app/vr/page/react-loadable-manifest.json +11 -0
  278. package/.next/standalone/.next/server/app/vr/page/server-reference-manifest.json +4 -0
  279. package/.next/standalone/.next/server/app/vr/page.js +17 -0
  280. package/.next/standalone/.next/server/app/vr/page.js.map +5 -0
  281. package/.next/standalone/.next/server/app/vr/page.js.nft.json +1 -0
  282. package/.next/standalone/.next/server/app/vr/page_client-reference-manifest.js +2 -0
  283. package/.next/standalone/.next/server/app/vr.html +1 -0
  284. package/.next/standalone/.next/server/app/vr.meta +15 -0
  285. package/.next/standalone/.next/server/app/vr.rsc +21 -0
  286. package/.next/standalone/.next/server/app/vr.segments/_full.segment.rsc +21 -0
  287. package/.next/standalone/.next/server/app/vr.segments/_head.segment.rsc +6 -0
  288. package/.next/standalone/.next/server/app/vr.segments/_index.segment.rsc +6 -0
  289. package/.next/standalone/.next/server/app/vr.segments/_tree.segment.rsc +4 -0
  290. package/.next/standalone/.next/server/app/vr.segments/vr/__PAGE__.segment.rsc +9 -0
  291. package/.next/standalone/.next/server/app/vr.segments/vr.segment.rsc +4 -0
  292. package/.next/standalone/.next/server/app/workspaces.html +1 -1
  293. package/.next/standalone/.next/server/app/workspaces.rsc +2 -2
  294. package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces/__PAGE__.segment.rsc +1 -1
  295. package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces.segment.rsc +1 -1
  296. package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
  297. package/.next/standalone/.next/server/app/workspaces.segments/_full.segment.rsc +2 -2
  298. package/.next/standalone/.next/server/app/workspaces.segments/_head.segment.rsc +1 -1
  299. package/.next/standalone/.next/server/app/workspaces.segments/_index.segment.rsc +2 -2
  300. package/.next/standalone/.next/server/app/workspaces.segments/_tree.segment.rsc +2 -2
  301. package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
  302. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0041efe4._.js +2 -2
  303. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00bf0ace._.js +2 -2
  304. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08a68343._.js +1 -1
  305. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0add852f._.js +1 -1
  306. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0c113ed0._.js +1 -1
  307. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e1a27e0._.js +1 -1
  308. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e71d908._.js +3 -3
  309. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e9142f3._.js +2 -2
  310. package/.next/standalone/.next/server/chunks/[root-of-the-server]__10e47926._.js +2 -2
  311. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1194f2c1._.js +1 -1
  312. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1665dc78._.js +2 -2
  313. package/.next/standalone/.next/server/chunks/[root-of-the-server]__175cbabf._.js +2 -2
  314. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19c2d094._.js +1 -1
  315. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adae357._.js +2 -2
  316. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d359752._.js +2 -2
  317. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1e8fabeb._.js +3 -3
  318. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1f8deca0._.js +8 -8
  319. package/.next/standalone/.next/server/chunks/[root-of-the-server]__253fdda1._.js +2 -2
  320. package/.next/standalone/.next/server/chunks/[root-of-the-server]__28e6434f._.js +2 -2
  321. package/.next/standalone/.next/server/chunks/[root-of-the-server]__2a386564._.js +3 -3
  322. package/.next/standalone/.next/server/chunks/[root-of-the-server]__2acbd703._.js +1 -1
  323. package/.next/standalone/.next/server/chunks/[root-of-the-server]__2acefabb._.js +1 -1
  324. package/.next/standalone/.next/server/chunks/[root-of-the-server]__2c20fb38._.js +2 -2
  325. package/.next/standalone/.next/server/chunks/[root-of-the-server]__309132cd._.js +1 -1
  326. package/.next/standalone/.next/server/chunks/[root-of-the-server]__33fec964._.js +3 -3
  327. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3786d8ae._.js +2 -2
  328. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3ae92407._.js +2 -2
  329. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3beda9fe._.js +2 -2
  330. package/.next/standalone/.next/server/chunks/[root-of-the-server]__3e3f25a1._.js +1 -1
  331. package/.next/standalone/.next/server/chunks/[root-of-the-server]__4619e9bd._.js +1 -1
  332. package/.next/standalone/.next/server/chunks/[root-of-the-server]__4a051043._.js +2 -2
  333. package/.next/standalone/.next/server/chunks/[root-of-the-server]__50208a5f._.js +1 -1
  334. package/.next/standalone/.next/server/chunks/[root-of-the-server]__508002e4._.js +2 -2
  335. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5086c373._.js +2 -2
  336. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5913e097._.js +2 -2
  337. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5b5f68d2._.js +2 -2
  338. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5c1f2459._.js +2 -2
  339. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5ec8c977._.js +2 -2
  340. package/.next/standalone/.next/server/chunks/[root-of-the-server]__5f8c694a._.js +1 -1
  341. package/.next/standalone/.next/server/chunks/[root-of-the-server]__63cebc6c._.js +2 -2
  342. package/.next/standalone/.next/server/chunks/[root-of-the-server]__64d30d4d._.js +2 -2
  343. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c54fc2e._.js +2 -2
  344. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6dc1fb7e._.js +2 -2
  345. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6e568102._.js +2 -2
  346. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6faa04c0._.js +2 -2
  347. package/.next/standalone/.next/server/chunks/[root-of-the-server]__727d05f1._.js +1 -1
  348. package/.next/standalone/.next/server/chunks/[root-of-the-server]__74a34dc3._.js +2 -2
  349. package/.next/standalone/.next/server/chunks/[root-of-the-server]__75d12b32._.js +1 -1
  350. package/.next/standalone/.next/server/chunks/[root-of-the-server]__7e7250a4._.js +2 -2
  351. package/.next/standalone/.next/server/chunks/[root-of-the-server]__8309e0a4._.js +2 -2
  352. package/.next/standalone/.next/server/chunks/[root-of-the-server]__86cc0e2b._.js +6 -6
  353. package/.next/standalone/.next/server/chunks/[root-of-the-server]__8915603e._.js +1 -1
  354. package/.next/standalone/.next/server/chunks/[root-of-the-server]__89c2565a._.js +2 -2
  355. package/.next/standalone/.next/server/chunks/[root-of-the-server]__8d178ad9._.js +2 -2
  356. package/.next/standalone/.next/server/chunks/[root-of-the-server]__93ee06f3._.js +3 -3
  357. package/.next/standalone/.next/server/chunks/[root-of-the-server]__9e4c154a._.js +2 -2
  358. package/.next/standalone/.next/server/chunks/[root-of-the-server]__a1fbc199._.js +1 -1
  359. package/.next/standalone/.next/server/chunks/[root-of-the-server]__a9d2e1d3._.js +2 -2
  360. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ae53d343._.js +2 -2
  361. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b3a04cef._.js +2 -2
  362. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b4270b77._.js +1 -1
  363. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b6b6ce60._.js +1 -1
  364. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b9545dd9._.js +1 -1
  365. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c200e21a._.js +1 -1
  366. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c3c74ca4._.js +1 -1
  367. package/.next/standalone/.next/server/chunks/[root-of-the-server]__c88b63f7._.js +2 -2
  368. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cba5f007._.js +1 -1
  369. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cbf4ceb0._.js +2 -2
  370. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cefdba2f._.js +2 -2
  371. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cf9e82bb._.js +2 -2
  372. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d15515e3._.js +1 -1
  373. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d2897392._.js +2 -2
  374. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d3b2d856._.js +2 -2
  375. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d73273ca._.js +2 -2
  376. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d8417eb6._.js +2 -2
  377. package/.next/standalone/.next/server/chunks/[root-of-the-server]__db4726bc._.js +1 -1
  378. package/.next/standalone/.next/server/chunks/[root-of-the-server]__dc2a55de._.js +2 -2
  379. package/.next/standalone/.next/server/chunks/[root-of-the-server]__dc6e2e5f._.js +1 -1
  380. package/.next/standalone/.next/server/chunks/[root-of-the-server]__e0d4690b._.js +3 -3
  381. package/.next/standalone/.next/server/chunks/[root-of-the-server]__e3ecfd17._.js +3 -3
  382. package/.next/standalone/.next/server/chunks/[root-of-the-server]__e678dd53._.js +1 -1
  383. package/.next/standalone/.next/server/chunks/[root-of-the-server]__e9223f55._.js +2 -2
  384. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ea630076._.js +3 -3
  385. package/.next/standalone/.next/server/chunks/[root-of-the-server]__eb8acb65._.js +1 -1
  386. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f26ca49d._.js +1 -1
  387. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f33e1101._.js +2 -2
  388. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f3a4c668._.js +1 -1
  389. package/.next/standalone/.next/server/chunks/[root-of-the-server]__f515f865._.js +2 -2
  390. package/.next/standalone/.next/server/chunks/[root-of-the-server]__fceb5d60._.js +2 -2
  391. package/.next/standalone/.next/server/chunks/[root-of-the-server]__fed41403._.js +2 -2
  392. package/.next/standalone/.next/server/chunks/[root-of-the-server]__ff2e98c2._.js +2 -2
  393. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_339169c8.js +1 -1
  394. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_97dac613.js +1 -1
  395. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0d8d81ca._.js +1 -1
  396. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1425c64f._.js +1 -1
  397. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1d2ce8f1._.js +1 -1
  398. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__31137509._.js +1 -1
  399. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__3633a587._.js +1 -1
  400. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__3c79441b._.js +1 -1
  401. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__4ca0f26b._.js +1 -1
  402. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__5b90d3ad._.js +3 -0
  403. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__62a0b363._.js +1 -1
  404. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__66aca5d4._.js +1 -1
  405. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__68205a46._.js +1 -1
  406. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__69fd2efa._.js +1 -1
  407. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__85dcf0f7._.js +1 -1
  408. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__8c53a5da._.js +1 -1
  409. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__aecb1873._.js +1 -1
  410. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__b02cd143._.js +1 -1
  411. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__b9bcde11._.js +3 -0
  412. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__cac90169._.js +1 -1
  413. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__d25de2f0._.js +1 -1
  414. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__e2f86be8._.js +1 -1
  415. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ee626b5b._.js +1 -1
  416. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__f39a9e98._.js +1 -1
  417. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__f3c566cd._.js +1 -1
  418. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__f76aa221._.js +1 -1
  419. package/.next/standalone/.next/server/chunks/ssr/_149d7fd4._.js +1 -1
  420. package/.next/standalone/.next/server/chunks/ssr/_2e0dd6a7._.js +1 -1
  421. package/.next/standalone/.next/server/chunks/ssr/_3cd2355c._.js +1 -1
  422. package/.next/standalone/.next/server/chunks/ssr/_3d206597._.js +4 -0
  423. package/.next/standalone/.next/server/chunks/ssr/_47cc9af0._.js +1 -1
  424. package/.next/standalone/.next/server/chunks/ssr/_5cf334fd._.js +3 -0
  425. package/.next/standalone/.next/server/chunks/ssr/_7082788b._.js +1 -1
  426. package/.next/standalone/.next/server/chunks/ssr/_7154d8ae._.js +1 -1
  427. package/.next/standalone/.next/server/chunks/ssr/_75bb1b9a._.js +1 -1
  428. package/.next/standalone/.next/server/chunks/ssr/{_aeeff784._.js → _81abf587._.js} +2 -2
  429. package/.next/standalone/.next/server/chunks/ssr/_8acf81e2._.js +1 -1
  430. package/.next/standalone/.next/server/chunks/ssr/_8c36feb8._.js +1 -1
  431. package/.next/standalone/.next/server/chunks/ssr/_91e9bb86._.js +1 -1
  432. package/.next/standalone/.next/server/chunks/ssr/_ac4c1838._.js +1 -1
  433. package/.next/standalone/.next/server/chunks/ssr/_ad8515fc._.js +1 -1
  434. package/.next/standalone/.next/server/chunks/ssr/_b1f49e81._.js +1 -1
  435. package/.next/standalone/.next/server/chunks/ssr/_c0fe7614._.js +1 -1
  436. package/.next/standalone/.next/server/chunks/ssr/_d4825f5a._.js +1 -1
  437. package/.next/standalone/.next/server/chunks/ssr/_da10a9f4._.js +1 -1
  438. package/.next/standalone/.next/server/chunks/ssr/_db0abd0a._.js +3 -0
  439. package/.next/standalone/.next/server/chunks/ssr/_dee5d4a1._.js +1 -1
  440. package/.next/standalone/.next/server/chunks/ssr/_ef482c0c._.js +1 -1
  441. package/.next/standalone/.next/server/chunks/ssr/_efe43d2f._.js +1 -1
  442. package/.next/standalone/.next/server/chunks/ssr/_f4a4e116._.js +1 -1
  443. package/.next/standalone/.next/server/chunks/ssr/_f4d525d2._.js +1 -1
  444. package/.next/standalone/.next/server/chunks/ssr/_f4e57187._.js +3 -0
  445. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_vr_page_actions_3fb70d92.js +3 -0
  446. package/.next/standalone/.next/server/chunks/ssr/node_modules_32f9d62f._.js +1 -1
  447. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_02f39477.js +1 -1
  448. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_eedfc1fd._.js +1 -1
  449. package/.next/standalone/.next/server/chunks/ssr/src_40fa36ce._.js +7 -0
  450. package/.next/standalone/.next/server/chunks/ssr/src_app_(desktop)_cortex_page_tsx_0f33d8b3._.js +3 -0
  451. package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__32a0045c._.js +1 -1
  452. package/.next/standalone/.next/server/edge/chunks/_d73df637._.js +1 -1
  453. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  454. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  455. package/.next/standalone/.next/server/next-font-manifest.js +1 -1
  456. package/.next/standalone/.next/server/next-font-manifest.json +4 -0
  457. package/.next/standalone/.next/server/pages/404.html +1 -1
  458. package/.next/standalone/.next/server/pages/500.html +2 -2
  459. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  460. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  461. package/.next/standalone/.next/static/chunks/045c83caa4d15373.js +1 -0
  462. package/.next/standalone/.next/static/chunks/07ea09e6024a523b.js +1 -0
  463. package/.next/standalone/.next/static/chunks/232d8aae4fefab70.js +1 -0
  464. package/.next/standalone/.next/static/chunks/2ad22562bb37ecad.js +1011 -0
  465. package/.next/standalone/.next/static/chunks/396eac60f496f178.js +1 -0
  466. package/.next/standalone/.next/static/chunks/{a4e5c700421eaa46.js → 412140a02893327a.js} +1 -1
  467. package/.next/standalone/.next/static/chunks/481cc11ae80b08b1.js +1 -0
  468. package/.next/standalone/.next/static/chunks/5325351ef49cb65f.js +1 -0
  469. package/.next/standalone/.next/static/chunks/559735e598ca3cbb.js +1 -0
  470. package/.next/standalone/.next/static/chunks/59c63d5af5cf3daf.js +1 -0
  471. package/.next/standalone/.next/static/chunks/5d5d7b0095dd52ae.js +1 -0
  472. package/.next/standalone/.next/static/chunks/6ae575967d091df4.js +1 -0
  473. package/.next/standalone/.next/static/chunks/7a7c0d9d875332a3.js +1 -0
  474. package/.next/standalone/.next/static/chunks/7f8455bb855a6c84.js +1 -0
  475. package/.next/standalone/.next/static/chunks/898f380eba90427a.js +1 -0
  476. package/.next/standalone/.next/static/chunks/95339e55722bb4ca.js +5 -0
  477. package/.next/standalone/.next/static/chunks/9cd594813c539df9.js +1 -0
  478. package/.next/standalone/.next/static/chunks/ad1423eed05d129b.js +1 -0
  479. package/.next/standalone/.next/static/chunks/ae7b146884c67d2a.js +1 -0
  480. package/.next/standalone/.next/static/chunks/b84072d72aa86417.js +1 -0
  481. package/.next/standalone/.next/static/chunks/c1a95aebf6725f64.css +3 -0
  482. package/.next/standalone/.next/static/chunks/c515eb77d9410aa0.js +5 -0
  483. package/.next/standalone/.next/static/chunks/{9899cf4c2bdbe61d.js → d9ae203a7f123546.js} +2 -2
  484. package/.next/standalone/.next/static/chunks/e23f20b51a75a5bb.js +757 -0
  485. package/.next/standalone/.next/static/chunks/fdc09bd135846960.js +1 -0
  486. package/.next/standalone/.next/static/chunks/ff0196911449e745.js +1 -0
  487. package/.next/standalone/.next/static/chunks/{turbopack-4c21186b79fb4c10.js → turbopack-e1a0994ed4af988c.js} +1 -1
  488. package/.next/standalone/.spaces/cortex-context.md +70 -0
  489. package/.next/standalone/bin/cortex-hook.sh +62 -62
  490. package/.next/standalone/bin/cortex-mcp.js +60 -60
  491. package/.next/standalone/docs/superpowers/plans/2026-03-13-cortex-wiring.md +1387 -1387
  492. package/.next/standalone/docs/superpowers/plans/2026-03-14-cortex-v2-entity-graph.md +1923 -1923
  493. package/.next/standalone/docs/superpowers/plans/2026-03-14-cortex-v2-knowledge-evolution.md +1113 -1113
  494. package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-boundary-engine.md +853 -853
  495. package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-context-engine.md +1274 -1274
  496. package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-signal-ingestion.md +933 -933
  497. package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-lobes.md +1080 -1080
  498. package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-v2-gravity-system.md +768 -768
  499. package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-v2-ui.md +1108 -1108
  500. package/.next/standalone/docs/superpowers/plans/2026-03-18-cortex-ui-integration.md +1846 -1846
  501. package/.next/standalone/docs/superpowers/specs/2026-03-13-cortex-wiring-design.md +268 -268
  502. package/.next/standalone/docs/superpowers/specs/2026-03-14-cortex-v2-design.md +623 -623
  503. package/.next/standalone/docs/superpowers/specs/2026-03-16-cortex-lobes-design.md +263 -263
  504. package/.next/standalone/docs/superpowers/specs/2026-03-16-cortex-v2-ui-design.md +240 -240
  505. package/.next/standalone/docs/superpowers/specs/2026-03-18-cortex-ui-integration-design.md +341 -341
  506. package/.next/standalone/node_modules/@img/sharp-win32-x64/lib/sharp-win32-x64.node +0 -0
  507. package/.next/standalone/node_modules/@img/{sharp-linux-x64 → sharp-win32-x64}/package.json +39 -46
  508. package/.next/standalone/package.json +103 -102
  509. package/.next/standalone/server.js +1 -1
  510. package/.next/standalone/src/app/(desktop)/cortex/page.tsx +78 -78
  511. package/.next/standalone/src/app/api/cortex/context/route.ts +78 -78
  512. package/.next/standalone/src/app/api/cortex/curation/assess/route.ts +27 -27
  513. package/.next/standalone/src/app/api/cortex/curation/publish/route.ts +23 -23
  514. package/.next/standalone/src/app/api/cortex/curation/refine/route.ts +23 -23
  515. package/.next/standalone/src/app/api/cortex/curation/review/route.ts +29 -29
  516. package/.next/standalone/src/app/api/cortex/curation/seed/route.ts +23 -23
  517. package/.next/standalone/src/app/api/cortex/export/route.ts +40 -40
  518. package/.next/standalone/src/app/api/cortex/federation/pending/route.ts +20 -20
  519. package/.next/standalone/src/app/api/cortex/federation/resolve/route.ts +43 -43
  520. package/.next/standalone/src/app/api/cortex/federation/search/route.ts +35 -35
  521. package/.next/standalone/src/app/api/cortex/federation/teach/route.ts +76 -76
  522. package/.next/standalone/src/app/api/cortex/graph/edges/route.ts +112 -112
  523. package/.next/standalone/src/app/api/cortex/graph/entities/[id]/route.ts +73 -73
  524. package/.next/standalone/src/app/api/cortex/graph/entities/route.ts +75 -75
  525. package/.next/standalone/src/app/api/cortex/graph/populate/route.ts +203 -203
  526. package/.next/standalone/src/app/api/cortex/import/route.ts +75 -75
  527. package/.next/standalone/src/app/api/cortex/import/status/route.ts +15 -15
  528. package/.next/standalone/src/app/api/cortex/ingest/bootstrap/route.ts +29 -29
  529. package/.next/standalone/src/app/api/cortex/ingest/status/route.ts +15 -15
  530. package/.next/standalone/src/app/api/cortex/knowledge/[id]/route.ts +91 -91
  531. package/.next/standalone/src/app/api/cortex/knowledge/route.ts +93 -93
  532. package/.next/standalone/src/app/api/cortex/lobes/[id]/route.ts +67 -67
  533. package/.next/standalone/src/app/api/cortex/lobes/route.ts +22 -22
  534. package/.next/standalone/src/app/api/cortex/lobes/share/route.ts +80 -80
  535. package/.next/standalone/src/app/api/cortex/marketplace/browse/route.ts +43 -43
  536. package/.next/standalone/src/app/api/cortex/marketplace/preview/route.ts +46 -46
  537. package/.next/standalone/src/app/api/cortex/mcp/call/route.ts +11 -11
  538. package/.next/standalone/src/app/api/cortex/mcp/tools/route.ts +6 -6
  539. package/.next/standalone/src/app/api/cortex/search/route.ts +43 -43
  540. package/.next/standalone/src/app/api/cortex/settings/route.ts +33 -33
  541. package/.next/standalone/src/app/api/cortex/status/route.ts +169 -169
  542. package/.next/standalone/src/app/api/cortex/timeline/route.ts +42 -42
  543. package/.next/standalone/src/app/api/cortex/usage/route.ts +31 -31
  544. package/.next/standalone/src/app/api/cortex/workspace/[id]/context/route.ts +41 -41
  545. package/.next/standalone/src/components/cortex/constants.ts +29 -29
  546. package/.next/standalone/src/components/cortex/cortex-dashboard.tsx +304 -304
  547. package/.next/standalone/src/components/cortex/cortex-indicator.tsx +44 -44
  548. package/.next/standalone/src/components/cortex/cortex-panel.tsx +140 -140
  549. package/.next/standalone/src/components/cortex/cortex-settings.tsx +221 -221
  550. package/.next/standalone/src/components/cortex/curation-tab.tsx +810 -810
  551. package/.next/standalone/src/components/cortex/entity-detail.tsx +101 -101
  552. package/.next/standalone/src/components/cortex/entity-graph.tsx +382 -382
  553. package/.next/standalone/src/components/cortex/import-dialog.tsx +212 -212
  554. package/.next/standalone/src/components/cortex/injection-badge.tsx +72 -72
  555. package/.next/standalone/src/components/cortex/knowledge-card.tsx +109 -109
  556. package/.next/standalone/src/components/cortex/knowledge-tab.tsx +158 -158
  557. package/.next/standalone/src/components/cortex/lobe-settings.tsx +215 -215
  558. package/.next/standalone/src/components/cortex/marketplace-card.tsx +126 -126
  559. package/.next/standalone/src/components/cortex/marketplace-tab.tsx +113 -113
  560. package/.next/standalone/src/lib/cortex/config.ts +40 -40
  561. package/.next/standalone/src/lib/cortex/debug.ts +10 -10
  562. package/.next/standalone/src/lib/cortex/distillation/usage-store.ts +18 -18
  563. package/.next/standalone/src/lib/cortex/graph/resolver.ts +10 -10
  564. package/.next/standalone/src/lib/cortex/graph/types.ts +22 -22
  565. package/.next/standalone/src/lib/cortex/index.ts +56 -56
  566. package/.next/standalone/src/lib/cortex/ingestion/bootstrap.ts +14 -14
  567. package/.next/standalone/src/lib/cortex/knowledge/compat.ts +14 -14
  568. package/.next/standalone/src/lib/cortex/knowledge/contradiction.ts +10 -10
  569. package/.next/standalone/src/lib/cortex/knowledge/types.ts +67 -67
  570. package/.next/standalone/src/lib/cortex/lobes/config.ts +16 -16
  571. package/.next/standalone/src/lib/cortex/lobes/resolver.ts +8 -8
  572. package/.next/standalone/src/lib/cortex/lobes/shares.ts +14 -14
  573. package/.next/standalone/src/lib/cortex/mcp/server.ts +8 -8
  574. package/.next/standalone/src/lib/cortex/portability/exporter.ts +6 -6
  575. package/.next/standalone/src/lib/cortex/portability/importer.ts +10 -10
  576. package/.next/standalone/src/lib/cortex/retrieval/context-engine.ts +10 -10
  577. package/.next/standalone/src/lib/cortex/types.ts +39 -39
  578. package/.next/standalone/tsconfig.json +34 -34
  579. package/LICENSE +661 -661
  580. package/README.md +131 -131
  581. package/bin/cortex-hook.sh +62 -62
  582. package/bin/cortex-mcp.js +60 -60
  583. package/bin/fix-standalone-externals.js +79 -79
  584. package/bin/lib/auto-setup.js +110 -110
  585. package/bin/mdns-service.js +171 -171
  586. package/bin/postinstall.js +35 -35
  587. package/bin/setup-admin.js +195 -195
  588. package/bin/spaces-dev.js +208 -208
  589. package/bin/spaces-install.js +599 -599
  590. package/bin/spaces-reset-totp.js +50 -50
  591. package/bin/spaces-service.js +1020 -1020
  592. package/bin/spaces-setup.js +253 -253
  593. package/bin/spaces.js +776 -776
  594. package/bin/ssh-auth-keys.sh +68 -68
  595. package/bin/terminal-server.js +1683 -1649
  596. package/package.json +103 -102
  597. package/.next/standalone/.next/server/chunks/ssr/_078dd64d._.js +0 -3
  598. package/.next/standalone/.next/server/chunks/ssr/_701606d5._.js +0 -3
  599. package/.next/standalone/.next/server/chunks/ssr/_72b1de37._.js +0 -3
  600. package/.next/standalone/.next/server/chunks/ssr/_950142a4._.js +0 -3
  601. package/.next/standalone/.next/server/chunks/ssr/src_components_terminal_terminal-pane_tsx_803c5e2c._.js +0 -7
  602. package/.next/standalone/.next/static/chunks/18f168665aef1aab.js +0 -1
  603. package/.next/standalone/.next/static/chunks/25b7a243a404a1a7.js +0 -1
  604. package/.next/standalone/.next/static/chunks/4a50d2a3e9bc9b41.js +0 -1
  605. package/.next/standalone/.next/static/chunks/6c78a1dfa7ec2959.css +0 -3
  606. package/.next/standalone/.next/static/chunks/7e0091ab6c5ee8bd.js +0 -1
  607. package/.next/standalone/.next/static/chunks/869f562dc32e55f4.js +0 -1
  608. package/.next/standalone/.next/static/chunks/8b3f4572fec83caa.js +0 -5
  609. package/.next/standalone/.next/static/chunks/8d5419afc4b9116b.js +0 -1
  610. package/.next/standalone/.next/static/chunks/9b2c5451f0b67975.js +0 -1
  611. package/.next/standalone/.next/static/chunks/ac339e970df82fa5.js +0 -5
  612. package/.next/standalone/.next/static/chunks/e7772d64463868eb.js +0 -1
  613. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/README.md +0 -46
  614. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  615. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/index.js +0 -1
  616. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  617. package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/package.json +0 -42
  618. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +0 -46
  619. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  620. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +0 -1
  621. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  622. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
  623. package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +0 -30
  624. package/.next/standalone/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
  625. package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
  626. package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
  627. /package/.next/standalone/.next/static/{77VYbwIoyxFNr5xevTrCu → ncDe4k4gvD0788HAnq_3G}/_buildManifest.js +0 -0
  628. /package/.next/standalone/.next/static/{77VYbwIoyxFNr5xevTrCu → ncDe4k4gvD0788HAnq_3G}/_clientMiddlewareManifest.json +0 -0
  629. /package/.next/standalone/.next/static/{77VYbwIoyxFNr5xevTrCu → ncDe4k4gvD0788HAnq_3G}/_ssgManifest.js +0 -0
  630. /package/.next/standalone/node_modules/@img/{sharp-libvips-linux-x64 → sharp-win32-x64}/versions.json +0 -0
@@ -1,1274 +1,1274 @@
1
- # Cortex v2 — Pillar 3: Context Assembly Engine
2
-
3
- > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** Replace the flat layer-iteration search with a 6-stage context assembly pipeline that detects intent, resolves entities, computes graph-aware weights, searches multiple scopes in parallel, fuses results with evidence scoring, and surfaces conflicts — all within 150ms.
6
-
7
- **Architecture:** A new `ContextEngine` class wraps the existing `CortexStore` (for low-level vector search) and `EntityGraph` (for graph distance/proximity). It does NOT replace `CortexSearch` — instead it provides the higher-level retrieval interface that the RAG hook calls. The existing `CortexSearch` remains for backward-compatible simple searches. The RAG hook (`cortex-hook.js`) switches from calling the search API to calling a new context-assembly API endpoint.
8
-
9
- **Tech Stack:** TypeScript, vitest, LanceDB, SQLite (entity graph)
10
-
11
- **Spec:** `docs/superpowers/specs/2026-03-14-cortex-v2-design.md` — Pillar 3
12
-
13
- **Depends on:** Pillar 1 (Entity Graph) + Pillar 2 (Knowledge Unit Evolution) — both completed
14
-
15
- ---
16
-
17
- ## File Structure
18
-
19
- ```
20
- New files:
21
- ├── src/lib/cortex/retrieval/intent.ts — Intent detection (regex + keyword)
22
- ├── src/lib/cortex/retrieval/weight.ts — Weight computation (graph × intent × freshness × authority)
23
- ├── src/lib/cortex/retrieval/conflict.ts — Conflict detection in results
24
- ├── src/lib/cortex/retrieval/formatter.ts — Context formatting for RAG injection
25
- ├── src/lib/cortex/retrieval/context-engine.ts — Main 6-stage ContextEngine class
26
- ├── src/app/api/cortex/context/route.ts — API endpoint for context assembly
27
-
28
- Modified files:
29
- ├── bin/cortex-hook.js — Switch to context-assembly endpoint
30
-
31
- Test files:
32
- ├── tests/lib/cortex/retrieval/intent.test.ts
33
- ├── tests/lib/cortex/retrieval/weight.test.ts
34
- ├── tests/lib/cortex/retrieval/conflict.test.ts
35
- ├── tests/lib/cortex/retrieval/formatter.test.ts
36
- ├── tests/lib/cortex/retrieval/context-engine.test.ts
37
- ```
38
-
39
- ---
40
-
41
- ## Chunk 1: Intent Detection and Weight Computation
42
-
43
- ### Task 1: Intent detection
44
-
45
- **Files:**
46
- - Create: `src/lib/cortex/retrieval/intent.ts`
47
- - Create: `tests/lib/cortex/retrieval/intent.test.ts`
48
-
49
- - [ ] **Step 1: Write failing tests**
50
-
51
- ```typescript
52
- // tests/lib/cortex/retrieval/intent.test.ts
53
- import { describe, it, expect } from 'vitest';
54
- import { detectIntent, INTENTS } from '@/lib/cortex/retrieval/intent';
55
- import type { IntentResult } from '@/lib/cortex/retrieval/intent';
56
-
57
- describe('detectIntent', () => {
58
- it('detects debugging intent', () => {
59
- const result = detectIntent('why does the auth service throw a timeout error?');
60
- expect(result.intent).toBe('debugging');
61
- expect(result.confidence).toBeGreaterThan(0.5);
62
- });
63
-
64
- it('detects architecture intent', () => {
65
- const result = detectIntent('what architecture pattern should we use for the new service?');
66
- expect(result.intent).toBe('architecture');
67
- });
68
-
69
- it('detects how-to intent', () => {
70
- const result = detectIntent('how do I deploy this service to production?');
71
- expect(result.intent).toBe('how-to');
72
- });
73
-
74
- it('detects security intent', () => {
75
- const result = detectIntent('is there a vulnerability in our authentication flow?');
76
- expect(result.intent).toBe('security');
77
- });
78
-
79
- it('defaults to general for ambiguous queries', () => {
80
- const result = detectIntent('tell me about the project');
81
- expect(result.intent).toBe('general');
82
- });
83
-
84
- it('returns bias config for the detected intent', () => {
85
- const result = detectIntent('fix this bug in the login page');
86
- expect(result.biases).toBeDefined();
87
- expect(result.biases.scope_boost).toBeDefined();
88
- expect(result.biases.type_boost).toBeDefined();
89
- });
90
-
91
- it('exports all intent definitions', () => {
92
- expect(Object.keys(INTENTS)).toContain('debugging');
93
- expect(Object.keys(INTENTS)).toContain('architecture');
94
- expect(Object.keys(INTENTS)).toContain('general');
95
- expect(Object.keys(INTENTS).length).toBe(8);
96
- });
97
- });
98
- ```
99
-
100
- - [ ] **Step 2: Run tests to verify they fail**
101
-
102
- Run: `npx vitest run tests/lib/cortex/retrieval/intent.test.ts`
103
-
104
- - [ ] **Step 3: Implement intent detection**
105
-
106
- ```typescript
107
- // src/lib/cortex/retrieval/intent.ts
108
-
109
- export interface IntentBiases {
110
- scope_boost: Record<string, number>; // scope level → multiplier
111
- type_boost: Record<string, number>; // knowledge type → multiplier
112
- recency_boost: number; // extra recency multiplier
113
- }
114
-
115
- export interface IntentResult {
116
- intent: string;
117
- confidence: number;
118
- biases: IntentBiases;
119
- }
120
-
121
- interface IntentDef {
122
- patterns: RegExp[];
123
- keywords: string[];
124
- biases: IntentBiases;
125
- }
126
-
127
- export const INTENTS: Record<string, IntentDef> = {
128
- debugging: {
129
- patterns: [
130
- /\b(error|bug|fix|crash|fail|broken|throw|exception|timeout|issue)\b/i,
131
- /\bwhy\s+(does|is|did|do)\b/i,
132
- /\bnot\s+work/i,
133
- ],
134
- keywords: ['error', 'bug', 'fix', 'debug', 'crash', 'fail', 'broken', 'throw', 'exception', 'timeout', 'issue', 'stack trace'],
135
- biases: {
136
- scope_boost: { personal: 1.2, team: 1.0, department: 0.9, organization: 0.8 },
137
- type_boost: { error_fix: 1.3, pattern: 1.0, decision: 0.8, conversation: 0.7 },
138
- recency_boost: 1.1,
139
- },
140
- },
141
- architecture: {
142
- patterns: [
143
- /\b(architect|design|pattern|structure|approach)\b/i,
144
- /\bshould\s+we\s+(use|adopt|switch|migrate)\b/i,
145
- ],
146
- keywords: ['architecture', 'design', 'pattern', 'structure', 'approach', 'decision', 'migration', 'refactor'],
147
- biases: {
148
- scope_boost: { personal: 0.9, team: 1.1, department: 1.2, organization: 1.0 },
149
- type_boost: { decision: 1.5, pattern: 1.2, error_fix: 0.7, conversation: 0.5 },
150
- recency_boost: 1.0,
151
- },
152
- },
153
- onboarding: {
154
- patterns: [
155
- /\b(how\s+does|explain|what\s+is|overview|getting\s+started)\b/i,
156
- /\bnew\s+to\b/i,
157
- ],
158
- keywords: ['explain', 'overview', 'introduction', 'getting started', 'onboarding', 'how does'],
159
- biases: {
160
- scope_boost: { personal: 0.7, team: 1.0, department: 1.1, organization: 1.2 },
161
- type_boost: { pattern: 1.3, decision: 1.2, summary: 1.2, conversation: 0.5 },
162
- recency_boost: 0.9,
163
- },
164
- },
165
- policy: {
166
- patterns: [
167
- /\b(policy|compliance|regulation|standard|rule|requirement)\b/i,
168
- /\ballowed\s+to\b/i,
169
- ],
170
- keywords: ['policy', 'compliance', 'standard', 'regulation', 'rule', 'requirement', 'allowed'],
171
- biases: {
172
- scope_boost: { personal: 0.6, team: 0.8, department: 1.0, organization: 1.3 },
173
- type_boost: { decision: 1.5, preference: 1.2, pattern: 0.8, conversation: 0.3 },
174
- recency_boost: 1.0,
175
- },
176
- },
177
- 'how-to': {
178
- patterns: [
179
- /\bhow\s+(do|can|to|should)\s+I?\b/i,
180
- /\bsteps?\s+(to|for)\b/i,
181
- /\bwhat('s| is)\s+the\s+(command|way|process)\b/i,
182
- ],
183
- keywords: ['how to', 'steps', 'command', 'run', 'deploy', 'install', 'configure', 'setup'],
184
- biases: {
185
- scope_boost: { personal: 1.2, team: 1.0, department: 0.8, organization: 0.7 },
186
- type_boost: { command: 1.3, pattern: 1.2, error_fix: 1.0, conversation: 0.6 },
187
- recency_boost: 1.05,
188
- },
189
- },
190
- review: {
191
- patterns: [
192
- /\b(review|feedback|improve|quality|best\s+practice)\b/i,
193
- /\bis\s+this\s+(good|correct|right)\b/i,
194
- ],
195
- keywords: ['review', 'feedback', 'quality', 'improve', 'best practice', 'convention'],
196
- biases: {
197
- scope_boost: { personal: 0.9, team: 1.2, department: 1.0, organization: 0.8 },
198
- type_boost: { preference: 1.3, pattern: 1.2, code_pattern: 1.2, decision: 1.0 },
199
- recency_boost: 1.0,
200
- },
201
- },
202
- security: {
203
- patterns: [
204
- /\b(security|vulnerab|exploit|attack|auth|cve|injection|xss)\b/i,
205
- /\bsecure\b/i,
206
- ],
207
- keywords: ['security', 'vulnerability', 'exploit', 'attack', 'authentication', 'authorization', 'cve', 'injection', 'xss', 'csrf'],
208
- biases: {
209
- scope_boost: { personal: 0.8, team: 1.0, department: 1.2, organization: 1.0 },
210
- type_boost: { error_fix: 1.3, decision: 1.2, pattern: 1.0, conversation: 0.5 },
211
- recency_boost: 1.1,
212
- },
213
- },
214
- general: {
215
- patterns: [],
216
- keywords: [],
217
- biases: {
218
- scope_boost: { personal: 1.0, team: 1.0, department: 1.0, organization: 1.0 },
219
- type_boost: {},
220
- recency_boost: 1.0,
221
- },
222
- },
223
- };
224
-
225
- /**
226
- * Detect the intent of a query using regex patterns and keyword scoring.
227
- * No LLM call — fast and deterministic.
228
- */
229
- export function detectIntent(query: string): IntentResult {
230
- const lower = query.toLowerCase();
231
- let bestIntent = 'general';
232
- let bestScore = 0;
233
-
234
- for (const [name, def] of Object.entries(INTENTS)) {
235
- if (name === 'general') continue;
236
-
237
- let score = 0;
238
-
239
- // Regex pattern matches (high weight)
240
- for (const pattern of def.patterns) {
241
- if (pattern.test(query)) score += 2;
242
- }
243
-
244
- // Keyword matches (lower weight)
245
- for (const keyword of def.keywords) {
246
- if (lower.includes(keyword)) score += 1;
247
- }
248
-
249
- if (score > bestScore) {
250
- bestScore = score;
251
- bestIntent = name;
252
- }
253
- }
254
-
255
- return {
256
- intent: bestIntent,
257
- confidence: bestScore > 0 ? Math.min(1.0, bestScore / 6) : 0.5,
258
- biases: INTENTS[bestIntent].biases,
259
- };
260
- }
261
- ```
262
-
263
- - [ ] **Step 4: Run tests to verify they pass**
264
-
265
- Run: `npx vitest run tests/lib/cortex/retrieval/intent.test.ts`
266
- Expected: PASS (7 tests)
267
-
268
- - [ ] **Step 5: Commit**
269
-
270
- ```bash
271
- git add src/lib/cortex/retrieval/intent.ts tests/lib/cortex/retrieval/intent.test.ts
272
- git commit -m "feat(cortex): add intent detection for context assembly"
273
- ```
274
-
275
- ---
276
-
277
- ### Task 2: Weight computation
278
-
279
- **Files:**
280
- - Create: `src/lib/cortex/retrieval/weight.ts`
281
- - Create: `tests/lib/cortex/retrieval/weight.test.ts`
282
-
283
- - [ ] **Step 1: Write failing tests**
284
-
285
- ```typescript
286
- // tests/lib/cortex/retrieval/weight.test.ts
287
- import { describe, it, expect } from 'vitest';
288
- import { computeScopeWeight } from '@/lib/cortex/retrieval/weight';
289
- import type { IntentBiases } from '@/lib/cortex/retrieval/intent';
290
-
291
- describe('computeScopeWeight', () => {
292
- const neutralBiases: IntentBiases = {
293
- scope_boost: { personal: 1.0, team: 1.0, department: 1.0, organization: 1.0 },
294
- type_boost: {},
295
- recency_boost: 1.0,
296
- };
297
-
298
- it('returns 1.0 for self (distance 0)', () => {
299
- const weight = computeScopeWeight({
300
- graphProximity: 1.0, // 1/(1+0)
301
- scopeLevel: 'personal',
302
- intentBiases: neutralBiases,
303
- authorityFactor: 1.0,
304
- });
305
- expect(weight).toBeCloseTo(1.0);
306
- });
307
-
308
- it('decreases with graph distance', () => {
309
- const close = computeScopeWeight({
310
- graphProximity: 0.5, // distance 1
311
- scopeLevel: 'team',
312
- intentBiases: neutralBiases,
313
- authorityFactor: 1.0,
314
- });
315
- const far = computeScopeWeight({
316
- graphProximity: 0.25, // distance 3
317
- scopeLevel: 'organization',
318
- intentBiases: neutralBiases,
319
- authorityFactor: 1.0,
320
- });
321
- expect(close).toBeGreaterThan(far);
322
- });
323
-
324
- it('is boosted by intent biases', () => {
325
- const debugBiases: IntentBiases = {
326
- scope_boost: { personal: 1.2, team: 0.8 },
327
- type_boost: {},
328
- recency_boost: 1.0,
329
- };
330
- const personal = computeScopeWeight({
331
- graphProximity: 0.5,
332
- scopeLevel: 'personal',
333
- intentBiases: debugBiases,
334
- authorityFactor: 1.0,
335
- });
336
- const team = computeScopeWeight({
337
- graphProximity: 0.5,
338
- scopeLevel: 'team',
339
- intentBiases: debugBiases,
340
- authorityFactor: 1.0,
341
- });
342
- expect(personal).toBeGreaterThan(team);
343
- });
344
-
345
- it('is boosted by authority factor', () => {
346
- const low = computeScopeWeight({
347
- graphProximity: 0.5,
348
- scopeLevel: 'team',
349
- intentBiases: neutralBiases,
350
- authorityFactor: 1.0,
351
- });
352
- const high = computeScopeWeight({
353
- graphProximity: 0.5,
354
- scopeLevel: 'team',
355
- intentBiases: neutralBiases,
356
- authorityFactor: 1.2,
357
- });
358
- expect(high).toBeGreaterThan(low);
359
- });
360
-
361
- it('never returns negative', () => {
362
- const weight = computeScopeWeight({
363
- graphProximity: 0,
364
- scopeLevel: 'organization',
365
- intentBiases: neutralBiases,
366
- authorityFactor: 1.0,
367
- });
368
- expect(weight).toBeGreaterThanOrEqual(0);
369
- });
370
- });
371
- ```
372
-
373
- - [ ] **Step 2: Run tests to verify they fail**
374
-
375
- Run: `npx vitest run tests/lib/cortex/retrieval/weight.test.ts`
376
-
377
- - [ ] **Step 3: Implement weight computation**
378
-
379
- ```typescript
380
- // src/lib/cortex/retrieval/weight.ts
381
- import type { IntentBiases } from './intent';
382
- import type { ScopeLevel } from '../knowledge/types';
383
-
384
- export interface ScopeWeightInput {
385
- graphProximity: number; // 0-1, from EntityGraph.proximity()
386
- scopeLevel: ScopeLevel | string;
387
- intentBiases: IntentBiases;
388
- authorityFactor: number; // 1.0 default, higher for experts/docs
389
- }
390
-
391
- /**
392
- * Compute the retrieval weight for a knowledge scope.
393
- *
394
- * weight = graphProximity × intentBias × authorityFactor
395
- *
396
- * Per spec: weight(scope) = graph_proximity × intent_bias × freshness_bonus × authority
397
- * Freshness is applied per-result in the fusion stage, not per-scope.
398
- */
399
- export function computeScopeWeight(input: ScopeWeightInput): number {
400
- const { graphProximity, scopeLevel, intentBiases, authorityFactor } = input;
401
-
402
- const intentBias = intentBiases.scope_boost[scopeLevel] ?? 1.0;
403
-
404
- return Math.max(0, graphProximity * intentBias * authorityFactor);
405
- }
406
-
407
- /**
408
- * Compute per-result type boost from intent biases.
409
- */
410
- export function computeTypeBoost(knowledgeType: string, intentBiases: IntentBiases): number {
411
- return intentBiases.type_boost[knowledgeType] ?? 1.0;
412
- }
413
-
414
- /**
415
- * Authority factor for a source based on role and expertise.
416
- *
417
- * role_boost: 0.0 member, 0.1 lead, 0.15 senior/principal, 0.2 director+
418
- * expertise_weight: EXPERT_IN edge weight (0-1)
419
- * Documents get 1.2 base authority.
420
- */
421
- export function computeAuthority(params: {
422
- role?: string;
423
- expertiseWeight?: number;
424
- isDocument?: boolean;
425
- }): number {
426
- const { role, expertiseWeight = 0, isDocument = false } = params;
427
-
428
- if (isDocument) return 1.2;
429
-
430
- const roleBoosts: Record<string, number> = {
431
- member: 0.0,
432
- lead: 0.1,
433
- senior: 0.15,
434
- principal: 0.15,
435
- director: 0.2,
436
- vp: 0.2,
437
- cto: 0.2,
438
- };
439
-
440
- const roleBoost = roleBoosts[role?.toLowerCase() ?? 'member'] ?? 0.0;
441
- return Math.max(1.0, roleBoost + expertiseWeight);
442
- }
443
- ```
444
-
445
- - [ ] **Step 4: Run tests to verify they pass**
446
-
447
- Run: `npx vitest run tests/lib/cortex/retrieval/weight.test.ts`
448
- Expected: PASS (5 tests)
449
-
450
- - [ ] **Step 5: Commit**
451
-
452
- ```bash
453
- git add src/lib/cortex/retrieval/weight.ts tests/lib/cortex/retrieval/weight.test.ts
454
- git commit -m "feat(cortex): add scope weight computation for context assembly"
455
- ```
456
-
457
- ---
458
-
459
- ## Chunk 2: Conflict Detection and Context Formatting
460
-
461
- ### Task 3: Conflict detection
462
-
463
- **Files:**
464
- - Create: `src/lib/cortex/retrieval/conflict.ts`
465
- - Create: `tests/lib/cortex/retrieval/conflict.test.ts`
466
-
467
- - [ ] **Step 1: Write failing tests**
468
-
469
- ```typescript
470
- // tests/lib/cortex/retrieval/conflict.test.ts
471
- import { describe, it, expect } from 'vitest';
472
- import { detectConflicts } from '@/lib/cortex/retrieval/conflict';
473
- import type { ScoredKnowledge } from '@/lib/cortex/knowledge/types';
474
-
475
- function makeResult(overrides: Partial<ScoredKnowledge> = {}): ScoredKnowledge {
476
- return {
477
- id: 'r1', vector: [], text: 'test', type: 'decision', layer: 'personal',
478
- workspace_id: null, session_id: null, agent_type: 'claude',
479
- project_path: null, file_refs: [], confidence: 0.8,
480
- created: new Date().toISOString(), source_timestamp: new Date().toISOString(),
481
- stale_score: 0, access_count: 0, last_accessed: null, metadata: {},
482
- relevance_score: 0.9, similarity: 0.9,
483
- contradiction_refs: [], ...overrides,
484
- };
485
- }
486
-
487
- describe('detectConflicts', () => {
488
- it('returns no conflicts when no contradiction_refs', () => {
489
- const results = [makeResult({ id: 'a' }), makeResult({ id: 'b' })];
490
- const conflicts = detectConflicts(results);
491
- expect(conflicts).toHaveLength(0);
492
- });
493
-
494
- it('detects conflict between two results', () => {
495
- const results = [
496
- makeResult({ id: 'a', text: 'use pool size 50', contradiction_refs: ['b'] }),
497
- makeResult({ id: 'b', text: 'scale horizontally', contradiction_refs: ['a'] }),
498
- ];
499
- const conflicts = detectConflicts(results);
500
- expect(conflicts).toHaveLength(1);
501
- expect(conflicts[0].unitA.id).toBe('a');
502
- expect(conflicts[0].unitB.id).toBe('b');
503
- });
504
-
505
- it('ignores contradiction_refs pointing to results not in the set', () => {
506
- const results = [
507
- makeResult({ id: 'a', contradiction_refs: ['z'] }), // z is not in results
508
- ];
509
- const conflicts = detectConflicts(results);
510
- expect(conflicts).toHaveLength(0);
511
- });
512
-
513
- it('deduplicates symmetric conflicts', () => {
514
- // A contradicts B and B contradicts A should produce one conflict, not two
515
- const results = [
516
- makeResult({ id: 'a', contradiction_refs: ['b'] }),
517
- makeResult({ id: 'b', contradiction_refs: ['a'] }),
518
- ];
519
- const conflicts = detectConflicts(results);
520
- expect(conflicts).toHaveLength(1);
521
- });
522
- });
523
- ```
524
-
525
- - [ ] **Step 2: Implement conflict detection**
526
-
527
- ```typescript
528
- // src/lib/cortex/retrieval/conflict.ts
529
- import type { ScoredKnowledge } from '../knowledge/types';
530
-
531
- export interface ConflictPair {
532
- unitA: ScoredKnowledge;
533
- unitB: ScoredKnowledge;
534
- }
535
-
536
- /**
537
- * Detect conflicts among search results by checking contradiction_refs.
538
- * Returns deduplicated conflict pairs (A↔B counted once, not twice).
539
- */
540
- export function detectConflicts(results: ScoredKnowledge[]): ConflictPair[] {
541
- const resultMap = new Map(results.map(r => [r.id, r]));
542
- const seen = new Set<string>();
543
- const conflicts: ConflictPair[] = [];
544
-
545
- for (const result of results) {
546
- const refs = result.contradiction_refs ?? [];
547
- for (const refId of refs) {
548
- const other = resultMap.get(refId);
549
- if (!other) continue;
550
-
551
- const key = [result.id, refId].sort().join('|');
552
- if (seen.has(key)) continue;
553
- seen.add(key);
554
-
555
- conflicts.push({ unitA: result, unitB: other });
556
- }
557
- }
558
-
559
- return conflicts;
560
- }
561
- ```
562
-
563
- - [ ] **Step 3: Run tests, commit**
564
-
565
- Run: `npx vitest run tests/lib/cortex/retrieval/conflict.test.ts`
566
-
567
- ```bash
568
- git add src/lib/cortex/retrieval/conflict.ts tests/lib/cortex/retrieval/conflict.test.ts
569
- git commit -m "feat(cortex): add conflict detection for search results"
570
- ```
571
-
572
- ---
573
-
574
- ### Task 4: Context formatter
575
-
576
- **Files:**
577
- - Create: `src/lib/cortex/retrieval/formatter.ts`
578
- - Create: `tests/lib/cortex/retrieval/formatter.test.ts`
579
-
580
- - [ ] **Step 1: Write failing tests**
581
-
582
- ```typescript
583
- // tests/lib/cortex/retrieval/formatter.test.ts
584
- import { describe, it, expect } from 'vitest';
585
- import { formatContext } from '@/lib/cortex/retrieval/formatter';
586
- import type { ScoredKnowledge } from '@/lib/cortex/knowledge/types';
587
- import type { ConflictPair } from '@/lib/cortex/retrieval/conflict';
588
-
589
- function makeResult(overrides: Partial<ScoredKnowledge> = {}): ScoredKnowledge {
590
- return {
591
- id: 'r1', vector: [], text: 'test knowledge', type: 'decision', layer: 'personal',
592
- workspace_id: null, session_id: null, agent_type: 'claude',
593
- project_path: null, file_refs: [], confidence: 0.8,
594
- created: '2026-03-15T00:00:00.000Z', source_timestamp: '2026-03-15T00:00:00.000Z',
595
- stale_score: 0, access_count: 5, last_accessed: null, metadata: {},
596
- relevance_score: 0.9, similarity: 0.9,
597
- ...overrides,
598
- };
599
- }
600
-
601
- describe('formatContext', () => {
602
- it('wraps results in cortex-context tags', () => {
603
- const output = formatContext([makeResult()], []);
604
- expect(output).toContain('<cortex-context>');
605
- expect(output).toContain('</cortex-context>');
606
- });
607
-
608
- it('includes type labels and dates', () => {
609
- const output = formatContext([makeResult({ type: 'error_fix', source_timestamp: '2026-03-10T00:00:00.000Z' })], []);
610
- expect(output).toContain('[Error Fix]');
611
- expect(output).toContain('2026-03-10');
612
- });
613
-
614
- it('includes source attribution when origin is present', () => {
615
- const output = formatContext([makeResult({
616
- origin: { source_type: 'conversation', source_ref: 'sess-1', creator_entity_id: 'person-alice' },
617
- })], []);
618
- expect(output).toContain('person-alice');
619
- });
620
-
621
- it('includes conflict callout when conflicts exist', () => {
622
- const a = makeResult({ id: 'a', text: 'use pool size 50' });
623
- const b = makeResult({ id: 'b', text: 'scale horizontally' });
624
- const conflicts: ConflictPair[] = [{ unitA: a, unitB: b }];
625
- const output = formatContext([a, b], conflicts);
626
- expect(output).toContain('Conflicting');
627
- });
628
-
629
- it('respects max token budget', () => {
630
- const bigResults = Array.from({ length: 20 }, (_, i) =>
631
- makeResult({ id: `r${i}`, text: 'x'.repeat(500) })
632
- );
633
- const output = formatContext(bigResults, [], { maxTokens: 500 });
634
- // Should not include all 20 results (would be ~2500 tokens)
635
- expect(output.length).toBeLessThan(3000);
636
- });
637
-
638
- it('returns empty string when no results', () => {
639
- expect(formatContext([], [])).toBe('');
640
- });
641
- });
642
- ```
643
-
644
- - [ ] **Step 2: Implement context formatter**
645
-
646
- ```typescript
647
- // src/lib/cortex/retrieval/formatter.ts
648
- import type { ScoredKnowledge } from '../knowledge/types';
649
- import type { ConflictPair } from './conflict';
650
-
651
- const TYPE_LABELS: Record<string, string> = {
652
- decision: 'Decision', pattern: 'Pattern', preference: 'Preference',
653
- error_fix: 'Error Fix', context: 'Context', code_pattern: 'Code',
654
- command: 'Command', conversation: 'Conversation', summary: 'Summary',
655
- };
656
-
657
- export interface FormatOptions {
658
- maxTokens?: number;
659
- }
660
-
661
- /**
662
- * Format search results + conflicts as annotated <cortex-context> for RAG injection.
663
- */
664
- export function formatContext(
665
- results: ScoredKnowledge[],
666
- conflicts: ConflictPair[],
667
- options: FormatOptions = {},
668
- ): string {
669
- if (results.length === 0) return '';
670
-
671
- const maxTokens = options.maxTokens ?? 1500;
672
- const entries: string[] = [];
673
- let tokens = 20; // overhead for tags
674
-
675
- // Format each result with attribution
676
- for (const unit of results) {
677
- const label = TYPE_LABELS[unit.type] || unit.type;
678
- const date = (unit.source_timestamp || '').slice(0, 10);
679
- const creator = unit.origin?.creator_entity_id ?? '';
680
- const sourceInfo = creator ? ` (${creator})` : '';
681
-
682
- let entry = `[${label}] ${date}${sourceInfo}:\n ${unit.text}`;
683
-
684
- const entryTokens = Math.ceil(entry.length / 4);
685
- if (tokens + entryTokens > maxTokens) break;
686
-
687
- entries.push(entry);
688
- tokens += entryTokens;
689
- }
690
-
691
- if (entries.length === 0) return '';
692
-
693
- // Build conflict section
694
- let conflictSection = '';
695
- if (conflicts.length > 0) {
696
- const conflictLines = conflicts.map(c =>
697
- ` - "${c.unitA.text.slice(0, 80)}..." vs "${c.unitB.text.slice(0, 80)}..."`
698
- );
699
- conflictSection = `\nConflicting perspectives (${conflicts.length}):\n${conflictLines.join('\n')}\n`;
700
- }
701
-
702
- const sourceCount = entries.length;
703
- const header = `Relevant knowledge (${sourceCount} source${sourceCount > 1 ? 's' : ''}${conflicts.length > 0 ? `, ${conflicts.length} conflict${conflicts.length > 1 ? 's' : ''}` : ''}):`;
704
-
705
- return [
706
- '<cortex-context>',
707
- header,
708
- '',
709
- ...entries,
710
- conflictSection,
711
- '</cortex-context>',
712
- ].join('\n');
713
- }
714
- ```
715
-
716
- - [ ] **Step 3: Run tests, commit**
717
-
718
- Run: `npx vitest run tests/lib/cortex/retrieval/formatter.test.ts`
719
-
720
- ```bash
721
- git add src/lib/cortex/retrieval/formatter.ts tests/lib/cortex/retrieval/formatter.test.ts
722
- git commit -m "feat(cortex): add context formatter for RAG injection"
723
- ```
724
-
725
- ---
726
-
727
- ## Chunk 3: Context Assembly Engine
728
-
729
- ### Task 5: ContextEngine — the 6-stage pipeline
730
-
731
- **Files:**
732
- - Create: `src/lib/cortex/retrieval/context-engine.ts`
733
- - Create: `tests/lib/cortex/retrieval/context-engine.test.ts`
734
-
735
- - [ ] **Step 1: Write failing tests**
736
-
737
- ```typescript
738
- // tests/lib/cortex/retrieval/context-engine.test.ts
739
- import { describe, it, expect, vi, beforeEach } from 'vitest';
740
- import { ContextEngine } from '@/lib/cortex/retrieval/context-engine';
741
-
742
- // Create mocks for dependencies
743
- const mockStore = {
744
- search: vi.fn().mockResolvedValue([]),
745
- };
746
-
747
- const mockGraph = {
748
- proximity: vi.fn().mockReturnValue(0.5),
749
- neighborhood: vi.fn().mockReturnValue([]),
750
- getEntity: vi.fn().mockReturnValue(null),
751
- };
752
-
753
- const mockResolver = {
754
- extractEntities: vi.fn().mockReturnValue([]),
755
- };
756
-
757
- const mockEmbedding = {
758
- embed: vi.fn().mockResolvedValue([[0.1, 0.2, 0.3]]),
759
- dimensions: 3,
760
- name: 'mock',
761
- init: vi.fn(),
762
- };
763
-
764
- describe('ContextEngine', () => {
765
- let engine: ContextEngine;
766
-
767
- beforeEach(() => {
768
- vi.clearAllMocks();
769
- engine = new ContextEngine({
770
- store: mockStore as any,
771
- graph: mockGraph as any,
772
- resolver: mockResolver as any,
773
- embedding: mockEmbedding as any,
774
- requesterId: 'person-alice',
775
- });
776
- });
777
-
778
- it('returns empty context for empty results', async () => {
779
- const result = await engine.assemble('some query');
780
- expect(result.results).toHaveLength(0);
781
- expect(result.context).toBe('');
782
- });
783
-
784
- it('calls embedding.embed with the query', async () => {
785
- await engine.assemble('test query');
786
- expect(mockEmbedding.embed).toHaveBeenCalledWith(['test query']);
787
- });
788
-
789
- it('detects intent from the query', async () => {
790
- const result = await engine.assemble('why does auth throw an error?');
791
- expect(result.intent.intent).toBe('debugging');
792
- });
793
-
794
- it('extracts entities from the query', async () => {
795
- mockResolver.extractEntities.mockReturnValue([
796
- { entity: { id: 'system-auth', type: 'system', name: 'Auth' }, confidence: 0.9, method: 'alias' },
797
- ]);
798
- const result = await engine.assemble('fix the auth service');
799
- expect(mockResolver.extractEntities).toHaveBeenCalledWith('fix the auth service');
800
- expect(result.entities).toHaveLength(1);
801
- });
802
-
803
- it('searches store with embedded query vector', async () => {
804
- mockStore.search.mockResolvedValue([{
805
- id: 'k1', text: 'test knowledge', type: 'decision', layer: 'personal',
806
- confidence: 0.8, stale_score: 0, created: new Date().toISOString(),
807
- source_timestamp: new Date().toISOString(), evidence_score: 0.7,
808
- contradiction_refs: [], _distance: 0.2,
809
- workspace_id: null, session_id: null, agent_type: 'claude',
810
- project_path: null, file_refs: [], access_count: 0, last_accessed: null,
811
- metadata: {},
812
- }]);
813
-
814
- const result = await engine.assemble('test query');
815
- expect(result.results.length).toBeGreaterThanOrEqual(1);
816
- expect(result.context).toContain('<cortex-context>');
817
- });
818
-
819
- it('completes within performance budget', async () => {
820
- const start = Date.now();
821
- await engine.assemble('test query');
822
- const elapsed = Date.now() - start;
823
- expect(elapsed).toBeLessThan(500); // generous for CI, target is 150ms
824
- });
825
- });
826
- ```
827
-
828
- - [ ] **Step 2: Implement ContextEngine**
829
-
830
- ```typescript
831
- // src/lib/cortex/retrieval/context-engine.ts
832
- import type { CortexStore } from '../store';
833
- import type { EntityGraph } from '../graph/entity-graph';
834
- import type { EntityResolver, ResolvedEntity } from '../graph/resolver';
835
- import type { EmbeddingProvider } from '../embeddings';
836
- import type { ScoredKnowledge } from '../knowledge/types';
837
- import { detectIntent } from './intent';
838
- import type { IntentResult } from './intent';
839
- import { computeScopeWeight, computeTypeBoost } from './weight';
840
- import { detectConflicts } from './conflict';
841
- import type { ConflictPair } from './conflict';
842
- import { formatContext } from './formatter';
843
- import { computeRelevanceScore } from './scoring';
844
-
845
- export interface ContextEngineDeps {
846
- store: CortexStore;
847
- graph: EntityGraph;
848
- resolver: EntityResolver;
849
- embedding: EmbeddingProvider;
850
- requesterId: string; // entity ID of the person making the query
851
- }
852
-
853
- export interface AssemblyResult {
854
- results: ScoredKnowledge[];
855
- conflicts: ConflictPair[];
856
- context: string; // formatted <cortex-context> string
857
- intent: IntentResult;
858
- entities: ResolvedEntity[];
859
- timing: {
860
- intentMs: number;
861
- entityMs: number;
862
- searchMs: number;
863
- totalMs: number;
864
- };
865
- }
866
-
867
- interface SearchSource {
868
- layerKey: string;
869
- weight: number;
870
- limit: number;
871
- }
872
-
873
- const DEFAULT_LAYERS = ['personal', 'workspace', 'team'] as const;
874
- const SEARCH_TIMEOUT_MS = 100;
875
-
876
- export class ContextEngine {
877
- constructor(private deps: ContextEngineDeps) {}
878
-
879
- async assemble(query: string, options: { limit?: number; workspaceId?: number | null; maxTokens?: number } = {}): Promise<AssemblyResult> {
880
- const totalStart = Date.now();
881
- const { limit = 5, workspaceId = null, maxTokens = 1500 } = options;
882
-
883
- // Stage 1: Intent Detection
884
- const intentStart = Date.now();
885
- const intent = detectIntent(query);
886
- const intentMs = Date.now() - intentStart;
887
-
888
- // Stage 2: Entity Resolution
889
- const entityStart = Date.now();
890
- const entities = this.deps.resolver.extractEntities(query);
891
- const entityMs = Date.now() - entityStart;
892
-
893
- // Embed the query
894
- const [queryVector] = await this.deps.embedding.embed([query]);
895
-
896
- // Stage 3: Weight Computation
897
- const sources = this.computeSourceWeights(intent, workspaceId);
898
-
899
- // Stage 4: Parallel Multi-Source Search
900
- const searchStart = Date.now();
901
- const allResults = await this.parallelSearch(queryVector, sources, limit);
902
- const searchMs = Date.now() - searchStart;
903
-
904
- // Stage 5: Fusion + Re-Ranking
905
- const fused = this.fuseAndRank(allResults, intent, limit);
906
-
907
- // Stage 6: Conflict Detection + Formatting
908
- const conflicts = detectConflicts(fused);
909
- const context = formatContext(fused, conflicts, { maxTokens });
910
-
911
- return {
912
- results: fused,
913
- conflicts,
914
- context,
915
- intent,
916
- entities,
917
- timing: {
918
- intentMs,
919
- entityMs,
920
- searchMs,
921
- totalMs: Date.now() - totalStart,
922
- },
923
- };
924
- }
925
-
926
- private computeSourceWeights(intent: IntentResult, workspaceId: number | null): SearchSource[] {
927
- const sources: SearchSource[] = [];
928
-
929
- for (const layer of DEFAULT_LAYERS) {
930
- const layerKey = layer === 'workspace' && workspaceId
931
- ? `workspace/${workspaceId}` : layer;
932
-
933
- // Map v1 layer to scope level for intent bias lookup
934
- const scopeLevel = layer === 'personal' ? 'personal'
935
- : layer === 'workspace' ? 'team' : 'organization';
936
-
937
- // Use graph proximity if available, else fall back to fixed weights
938
- let graphProximity: number;
939
- try {
940
- // For personal, distance is 0; for workspace, 1; for team, 2
941
- const layerEntity = layer === 'personal' ? this.deps.requesterId
942
- : layer === 'workspace' ? 'team-default' : 'organization-default';
943
- graphProximity = this.deps.graph.proximity(this.deps.requesterId, layerEntity);
944
- } catch {
945
- // Graph not populated, use fallback
946
- graphProximity = layer === 'personal' ? 1.0 : layer === 'workspace' ? 0.5 : 0.33;
947
- }
948
-
949
- // If graph returns 0 (unreachable/not found), use fallback
950
- if (graphProximity === 0) {
951
- graphProximity = layer === 'personal' ? 1.0 : layer === 'workspace' ? 0.5 : 0.33;
952
- }
953
-
954
- const weight = computeScopeWeight({
955
- graphProximity,
956
- scopeLevel,
957
- intentBiases: intent.biases,
958
- authorityFactor: 1.0,
959
- });
960
-
961
- sources.push({
962
- layerKey,
963
- weight,
964
- limit: Math.max(3, Math.round(weight * 10)), // proportional slots
965
- });
966
- }
967
-
968
- return sources.sort((a, b) => b.weight - a.weight);
969
- }
970
-
971
- private async parallelSearch(
972
- queryVector: number[],
973
- sources: SearchSource[],
974
- limit: number,
975
- ): Promise<Array<ScoredKnowledge & { sourceWeight: number }>> {
976
- const searchPromises = sources.map(async (source) => {
977
- try {
978
- const results = await Promise.race([
979
- this.deps.store.search(source.layerKey, queryVector, source.limit),
980
- new Promise<never>((_, reject) =>
981
- setTimeout(() => reject(new Error('timeout')), SEARCH_TIMEOUT_MS)
982
- ),
983
- ]);
984
-
985
- return results.map(unit => {
986
- const similarity = 1 - ((unit as any)._distance ?? 0);
987
- return {
988
- ...unit,
989
- similarity,
990
- relevance_score: 0, // computed in fusion
991
- sourceWeight: source.weight,
992
- } as ScoredKnowledge & { sourceWeight: number };
993
- });
994
- } catch {
995
- return []; // source failed or timed out
996
- }
997
- });
998
-
999
- const settled = await Promise.allSettled(searchPromises);
1000
- const allResults: Array<ScoredKnowledge & { sourceWeight: number }> = [];
1001
-
1002
- for (const result of settled) {
1003
- if (result.status === 'fulfilled') {
1004
- allResults.push(...result.value);
1005
- }
1006
- }
1007
-
1008
- return allResults;
1009
- }
1010
-
1011
- private fuseAndRank(
1012
- results: Array<ScoredKnowledge & { sourceWeight: number }>,
1013
- intent: IntentResult,
1014
- limit: number,
1015
- ): ScoredKnowledge[] {
1016
- // Score each result
1017
- for (const result of results) {
1018
- const typeBoost = computeTypeBoost(result.type, intent.biases);
1019
- const recencyBoost = intent.biases.recency_boost;
1020
-
1021
- result.relevance_score = computeRelevanceScore({
1022
- similarity: result.similarity,
1023
- confidence: result.confidence,
1024
- stale_score: result.stale_score,
1025
- created: result.created,
1026
- evidence_score: result.evidence_score,
1027
- }) * result.sourceWeight * typeBoost * recencyBoost;
1028
- }
1029
-
1030
- // Deduplicate: cosine > 0.9 between results (approximate via text similarity)
1031
- const deduped = this.deduplicateResults(results);
1032
-
1033
- // Sort and take top K
1034
- deduped.sort((a, b) => b.relevance_score - a.relevance_score);
1035
- return deduped.slice(0, limit);
1036
- }
1037
-
1038
- private deduplicateResults(results: ScoredKnowledge[]): ScoredKnowledge[] {
1039
- const kept: ScoredKnowledge[] = [];
1040
- const seenTexts = new Set<string>();
1041
-
1042
- // Sort by score first so we keep the better-scored version
1043
- results.sort((a, b) => b.relevance_score - a.relevance_score);
1044
-
1045
- for (const result of results) {
1046
- // Simple text-based dedup: normalize and check prefix overlap
1047
- const normalized = result.text.slice(0, 200).toLowerCase().trim();
1048
- if (seenTexts.has(normalized)) continue;
1049
-
1050
- // Check against existing kept items for high text overlap
1051
- let isDupe = false;
1052
- for (const existing of kept) {
1053
- if (result.id === existing.id) { isDupe = true; break; }
1054
- // If texts share >80% of content, consider duplicate
1055
- const existNorm = existing.text.slice(0, 200).toLowerCase().trim();
1056
- if (normalized === existNorm) { isDupe = true; break; }
1057
- }
1058
-
1059
- if (!isDupe) {
1060
- seenTexts.add(normalized);
1061
- kept.push(result);
1062
- }
1063
- }
1064
-
1065
- return kept;
1066
- }
1067
- }
1068
- ```
1069
-
1070
- - [ ] **Step 3: Run tests to verify they pass**
1071
-
1072
- Run: `npx vitest run tests/lib/cortex/retrieval/context-engine.test.ts`
1073
- Expected: PASS (6 tests)
1074
-
1075
- - [ ] **Step 4: Commit**
1076
-
1077
- ```bash
1078
- git add src/lib/cortex/retrieval/context-engine.ts tests/lib/cortex/retrieval/context-engine.test.ts
1079
- git commit -m "feat(cortex): add ContextEngine with 6-stage retrieval pipeline"
1080
- ```
1081
-
1082
- ---
1083
-
1084
- ## Chunk 4: API Endpoint and Hook Integration
1085
-
1086
- ### Task 6: Context assembly API endpoint
1087
-
1088
- **Files:**
1089
- - Create: `src/app/api/cortex/context/route.ts`
1090
-
1091
- - [ ] **Step 1: Create the endpoint**
1092
-
1093
- ```typescript
1094
- // src/app/api/cortex/context/route.ts
1095
- import { NextResponse } from 'next/server';
1096
- import type { NextRequest } from 'next/server';
1097
- import { getAuthUser, withUser } from '@/lib/auth';
1098
- import { getCortex, isCortexAvailable } from '@/lib/cortex';
1099
- import { ContextEngine } from '@/lib/cortex/retrieval/context-engine';
1100
- import { EntityResolver } from '@/lib/cortex/graph/resolver';
1101
- import { slugify } from '@/lib/cortex/graph/types';
1102
-
1103
- export async function GET(request: NextRequest) {
1104
- const user = getAuthUser(request);
1105
- return withUser(user, async () => {
1106
- if (!isCortexAvailable()) {
1107
- return NextResponse.json({ error: 'Cortex unavailable' }, { status: 403 });
1108
- }
1109
- const cortex = await getCortex();
1110
- if (!cortex) return NextResponse.json({ results: [], context: '' });
1111
-
1112
- const url = new URL(request.url);
1113
- const query = url.searchParams.get('q') || '';
1114
- const limit = parseInt(url.searchParams.get('limit') || '5', 10);
1115
- const workspaceId = url.searchParams.get('workspace_id');
1116
- const maxTokens = parseInt(url.searchParams.get('max_tokens') || '1500', 10);
1117
-
1118
- if (!query || query.length < 3) {
1119
- return NextResponse.json({ results: [], context: '' });
1120
- }
1121
-
1122
- const resolver = new EntityResolver(cortex.graph);
1123
- const requesterId = `person-${slugify(user)}`;
1124
-
1125
- const engine = new ContextEngine({
1126
- store: cortex.store,
1127
- graph: cortex.graph,
1128
- resolver,
1129
- embedding: cortex.embedding,
1130
- requesterId,
1131
- });
1132
-
1133
- const result = await engine.assemble(query, {
1134
- limit,
1135
- workspaceId: workspaceId ? parseInt(workspaceId, 10) : null,
1136
- maxTokens,
1137
- });
1138
-
1139
- return NextResponse.json({
1140
- results: result.results.map(r => ({ ...r, vector: undefined })), // strip vectors
1141
- context: result.context,
1142
- intent: result.intent,
1143
- conflicts: result.conflicts.length,
1144
- timing: result.timing,
1145
- });
1146
- });
1147
- }
1148
- ```
1149
-
1150
- - [ ] **Step 2: Commit**
1151
-
1152
- ```bash
1153
- git add src/app/api/cortex/context/route.ts
1154
- git commit -m "feat(cortex): add context assembly API endpoint"
1155
- ```
1156
-
1157
- ---
1158
-
1159
- ### Task 7: Update RAG hook to use context assembly endpoint
1160
-
1161
- **Files:**
1162
- - Modify: `bin/cortex-hook.js`
1163
-
1164
- - [ ] **Step 1: Read current cortex-hook.js**
1165
-
1166
- Read the file to understand the current flow: query → /api/cortex/search → format results → output.
1167
-
1168
- - [ ] **Step 2: Update to call context assembly endpoint**
1169
-
1170
- Change the URL from `/api/cortex/search/?q=...` to `/api/cortex/context/?q=...`. The new endpoint returns `{ context, results, intent, conflicts, timing }` — the `context` field is already pre-formatted as `<cortex-context>`, so the hook can output it directly instead of doing its own formatting.
1171
-
1172
- Key changes:
1173
- 1. URL: `/api/cortex/search/` → `/api/cortex/context/`
1174
- 2. Response handling: use `parsed.context` directly instead of formatting results manually
1175
- 3. Keep the fallback: if the new endpoint returns empty or fails, fall back to old behavior
1176
-
1177
- ```javascript
1178
- // Replace the response handling section:
1179
- const parsed = JSON.parse(body);
1180
-
1181
- // New: context is pre-formatted by the Context Assembly Engine
1182
- if (parsed.context) {
1183
- const output = JSON.stringify({
1184
- hookSpecificOutput: {
1185
- hookEventName: 'UserPromptSubmit',
1186
- additionalContext: parsed.context,
1187
- },
1188
- });
1189
- process.stdout.write(output);
1190
- process.exit(0);
1191
- }
1192
-
1193
- // Fallback: old-style results formatting (if context endpoint not available)
1194
- const results = parsed.results;
1195
- if (!results || results.length === 0) process.exit(0);
1196
- // ... existing formatting code stays as fallback ...
1197
- ```
1198
-
1199
- - [ ] **Step 3: Commit**
1200
-
1201
- ```bash
1202
- git add bin/cortex-hook.js
1203
- git commit -m "feat(cortex): switch RAG hook to context assembly endpoint"
1204
- ```
1205
-
1206
- ---
1207
-
1208
- ### Task 8: Integrate ContextEngine into CortexInstance
1209
-
1210
- **Files:**
1211
- - Modify: `src/lib/cortex/index.ts`
1212
-
1213
- - [ ] **Step 1: Read current index.ts**
1214
-
1215
- - [ ] **Step 2: Add ContextEngine to CortexInstance**
1216
-
1217
- 1. Import: `import { ContextEngine } from './retrieval/context-engine';` and `import { EntityResolver } from './graph/resolver';`
1218
- 2. Add `contextEngine?: ContextEngine` to `CortexInstance` interface (optional since it depends on graph)
1219
- 3. In `getCortex()`, after graph initialization, create the ContextEngine:
1220
-
1221
- ```typescript
1222
- const resolver = new EntityResolver(graph);
1223
- const contextEngine = new ContextEngine({
1224
- store,
1225
- graph,
1226
- resolver,
1227
- embedding,
1228
- requesterId: 'person-default-user', // default; overridden per-request in API
1229
- });
1230
- ```
1231
-
1232
- 4. Add to instance object: `contextEngine,`
1233
-
1234
- - [ ] **Step 3: Run full cortex test suite**
1235
-
1236
- Run: `npx vitest run tests/lib/cortex/`
1237
-
1238
- - [ ] **Step 4: Commit**
1239
-
1240
- ```bash
1241
- git add src/lib/cortex/index.ts
1242
- git commit -m "feat(cortex): add ContextEngine to CortexInstance"
1243
- ```
1244
-
1245
- ---
1246
-
1247
- ## Summary
1248
-
1249
- | Task | Component | Tests | Status |
1250
- |------|-----------|-------|--------|
1251
- | 1 | Intent detection | 7 | |
1252
- | 2 | Weight computation | 5 | |
1253
- | 3 | Conflict detection | 4 | |
1254
- | 4 | Context formatter | 6 | |
1255
- | 5 | ContextEngine (6-stage pipeline) | 6 | |
1256
- | 6 | Context assembly API endpoint | — | |
1257
- | 7 | RAG hook integration | — | |
1258
- | 8 | CortexInstance integration | regression | |
1259
-
1260
- **Total: 8 tasks, ~28 new tests, 4 chunks**
1261
-
1262
- **Performance budget:** The ContextEngine targets <150ms total latency:
1263
- - Intent detection: <5ms (regex, no LLM)
1264
- - Entity resolution: <5ms (alias lookup)
1265
- - Weight computation: <10ms (graph proximity, cached)
1266
- - Parallel search: <100ms (concurrent vector search with 100ms timeout)
1267
- - Fusion + formatting: <10ms
1268
-
1269
- **Key design decisions:**
1270
- - ContextEngine sits ABOVE CortexSearch — doesn't replace it, wraps it
1271
- - Graph proximity drives weights — but gracefully degrades to fixed weights when graph is empty
1272
- - Deduplication uses text prefix comparison (not cosine between result vectors — that would require N² vector ops)
1273
- - RAG hook uses pre-formatted `context` from the engine — no more client-side formatting
1274
- - Old `/api/cortex/search` endpoint still works — new `/api/cortex/context` endpoint adds the intelligence layer
1
+ # Cortex v2 — Pillar 3: Context Assembly Engine
2
+
3
+ > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Replace the flat layer-iteration search with a 6-stage context assembly pipeline that detects intent, resolves entities, computes graph-aware weights, searches multiple scopes in parallel, fuses results with evidence scoring, and surfaces conflicts — all within 150ms.
6
+
7
+ **Architecture:** A new `ContextEngine` class wraps the existing `CortexStore` (for low-level vector search) and `EntityGraph` (for graph distance/proximity). It does NOT replace `CortexSearch` — instead it provides the higher-level retrieval interface that the RAG hook calls. The existing `CortexSearch` remains for backward-compatible simple searches. The RAG hook (`cortex-hook.js`) switches from calling the search API to calling a new context-assembly API endpoint.
8
+
9
+ **Tech Stack:** TypeScript, vitest, LanceDB, SQLite (entity graph)
10
+
11
+ **Spec:** `docs/superpowers/specs/2026-03-14-cortex-v2-design.md` — Pillar 3
12
+
13
+ **Depends on:** Pillar 1 (Entity Graph) + Pillar 2 (Knowledge Unit Evolution) — both completed
14
+
15
+ ---
16
+
17
+ ## File Structure
18
+
19
+ ```
20
+ New files:
21
+ ├── src/lib/cortex/retrieval/intent.ts — Intent detection (regex + keyword)
22
+ ├── src/lib/cortex/retrieval/weight.ts — Weight computation (graph × intent × freshness × authority)
23
+ ├── src/lib/cortex/retrieval/conflict.ts — Conflict detection in results
24
+ ├── src/lib/cortex/retrieval/formatter.ts — Context formatting for RAG injection
25
+ ├── src/lib/cortex/retrieval/context-engine.ts — Main 6-stage ContextEngine class
26
+ ├── src/app/api/cortex/context/route.ts — API endpoint for context assembly
27
+
28
+ Modified files:
29
+ ├── bin/cortex-hook.js — Switch to context-assembly endpoint
30
+
31
+ Test files:
32
+ ├── tests/lib/cortex/retrieval/intent.test.ts
33
+ ├── tests/lib/cortex/retrieval/weight.test.ts
34
+ ├── tests/lib/cortex/retrieval/conflict.test.ts
35
+ ├── tests/lib/cortex/retrieval/formatter.test.ts
36
+ ├── tests/lib/cortex/retrieval/context-engine.test.ts
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Chunk 1: Intent Detection and Weight Computation
42
+
43
+ ### Task 1: Intent detection
44
+
45
+ **Files:**
46
+ - Create: `src/lib/cortex/retrieval/intent.ts`
47
+ - Create: `tests/lib/cortex/retrieval/intent.test.ts`
48
+
49
+ - [ ] **Step 1: Write failing tests**
50
+
51
+ ```typescript
52
+ // tests/lib/cortex/retrieval/intent.test.ts
53
+ import { describe, it, expect } from 'vitest';
54
+ import { detectIntent, INTENTS } from '@/lib/cortex/retrieval/intent';
55
+ import type { IntentResult } from '@/lib/cortex/retrieval/intent';
56
+
57
+ describe('detectIntent', () => {
58
+ it('detects debugging intent', () => {
59
+ const result = detectIntent('why does the auth service throw a timeout error?');
60
+ expect(result.intent).toBe('debugging');
61
+ expect(result.confidence).toBeGreaterThan(0.5);
62
+ });
63
+
64
+ it('detects architecture intent', () => {
65
+ const result = detectIntent('what architecture pattern should we use for the new service?');
66
+ expect(result.intent).toBe('architecture');
67
+ });
68
+
69
+ it('detects how-to intent', () => {
70
+ const result = detectIntent('how do I deploy this service to production?');
71
+ expect(result.intent).toBe('how-to');
72
+ });
73
+
74
+ it('detects security intent', () => {
75
+ const result = detectIntent('is there a vulnerability in our authentication flow?');
76
+ expect(result.intent).toBe('security');
77
+ });
78
+
79
+ it('defaults to general for ambiguous queries', () => {
80
+ const result = detectIntent('tell me about the project');
81
+ expect(result.intent).toBe('general');
82
+ });
83
+
84
+ it('returns bias config for the detected intent', () => {
85
+ const result = detectIntent('fix this bug in the login page');
86
+ expect(result.biases).toBeDefined();
87
+ expect(result.biases.scope_boost).toBeDefined();
88
+ expect(result.biases.type_boost).toBeDefined();
89
+ });
90
+
91
+ it('exports all intent definitions', () => {
92
+ expect(Object.keys(INTENTS)).toContain('debugging');
93
+ expect(Object.keys(INTENTS)).toContain('architecture');
94
+ expect(Object.keys(INTENTS)).toContain('general');
95
+ expect(Object.keys(INTENTS).length).toBe(8);
96
+ });
97
+ });
98
+ ```
99
+
100
+ - [ ] **Step 2: Run tests to verify they fail**
101
+
102
+ Run: `npx vitest run tests/lib/cortex/retrieval/intent.test.ts`
103
+
104
+ - [ ] **Step 3: Implement intent detection**
105
+
106
+ ```typescript
107
+ // src/lib/cortex/retrieval/intent.ts
108
+
109
+ export interface IntentBiases {
110
+ scope_boost: Record<string, number>; // scope level → multiplier
111
+ type_boost: Record<string, number>; // knowledge type → multiplier
112
+ recency_boost: number; // extra recency multiplier
113
+ }
114
+
115
+ export interface IntentResult {
116
+ intent: string;
117
+ confidence: number;
118
+ biases: IntentBiases;
119
+ }
120
+
121
+ interface IntentDef {
122
+ patterns: RegExp[];
123
+ keywords: string[];
124
+ biases: IntentBiases;
125
+ }
126
+
127
+ export const INTENTS: Record<string, IntentDef> = {
128
+ debugging: {
129
+ patterns: [
130
+ /\b(error|bug|fix|crash|fail|broken|throw|exception|timeout|issue)\b/i,
131
+ /\bwhy\s+(does|is|did|do)\b/i,
132
+ /\bnot\s+work/i,
133
+ ],
134
+ keywords: ['error', 'bug', 'fix', 'debug', 'crash', 'fail', 'broken', 'throw', 'exception', 'timeout', 'issue', 'stack trace'],
135
+ biases: {
136
+ scope_boost: { personal: 1.2, team: 1.0, department: 0.9, organization: 0.8 },
137
+ type_boost: { error_fix: 1.3, pattern: 1.0, decision: 0.8, conversation: 0.7 },
138
+ recency_boost: 1.1,
139
+ },
140
+ },
141
+ architecture: {
142
+ patterns: [
143
+ /\b(architect|design|pattern|structure|approach)\b/i,
144
+ /\bshould\s+we\s+(use|adopt|switch|migrate)\b/i,
145
+ ],
146
+ keywords: ['architecture', 'design', 'pattern', 'structure', 'approach', 'decision', 'migration', 'refactor'],
147
+ biases: {
148
+ scope_boost: { personal: 0.9, team: 1.1, department: 1.2, organization: 1.0 },
149
+ type_boost: { decision: 1.5, pattern: 1.2, error_fix: 0.7, conversation: 0.5 },
150
+ recency_boost: 1.0,
151
+ },
152
+ },
153
+ onboarding: {
154
+ patterns: [
155
+ /\b(how\s+does|explain|what\s+is|overview|getting\s+started)\b/i,
156
+ /\bnew\s+to\b/i,
157
+ ],
158
+ keywords: ['explain', 'overview', 'introduction', 'getting started', 'onboarding', 'how does'],
159
+ biases: {
160
+ scope_boost: { personal: 0.7, team: 1.0, department: 1.1, organization: 1.2 },
161
+ type_boost: { pattern: 1.3, decision: 1.2, summary: 1.2, conversation: 0.5 },
162
+ recency_boost: 0.9,
163
+ },
164
+ },
165
+ policy: {
166
+ patterns: [
167
+ /\b(policy|compliance|regulation|standard|rule|requirement)\b/i,
168
+ /\ballowed\s+to\b/i,
169
+ ],
170
+ keywords: ['policy', 'compliance', 'standard', 'regulation', 'rule', 'requirement', 'allowed'],
171
+ biases: {
172
+ scope_boost: { personal: 0.6, team: 0.8, department: 1.0, organization: 1.3 },
173
+ type_boost: { decision: 1.5, preference: 1.2, pattern: 0.8, conversation: 0.3 },
174
+ recency_boost: 1.0,
175
+ },
176
+ },
177
+ 'how-to': {
178
+ patterns: [
179
+ /\bhow\s+(do|can|to|should)\s+I?\b/i,
180
+ /\bsteps?\s+(to|for)\b/i,
181
+ /\bwhat('s| is)\s+the\s+(command|way|process)\b/i,
182
+ ],
183
+ keywords: ['how to', 'steps', 'command', 'run', 'deploy', 'install', 'configure', 'setup'],
184
+ biases: {
185
+ scope_boost: { personal: 1.2, team: 1.0, department: 0.8, organization: 0.7 },
186
+ type_boost: { command: 1.3, pattern: 1.2, error_fix: 1.0, conversation: 0.6 },
187
+ recency_boost: 1.05,
188
+ },
189
+ },
190
+ review: {
191
+ patterns: [
192
+ /\b(review|feedback|improve|quality|best\s+practice)\b/i,
193
+ /\bis\s+this\s+(good|correct|right)\b/i,
194
+ ],
195
+ keywords: ['review', 'feedback', 'quality', 'improve', 'best practice', 'convention'],
196
+ biases: {
197
+ scope_boost: { personal: 0.9, team: 1.2, department: 1.0, organization: 0.8 },
198
+ type_boost: { preference: 1.3, pattern: 1.2, code_pattern: 1.2, decision: 1.0 },
199
+ recency_boost: 1.0,
200
+ },
201
+ },
202
+ security: {
203
+ patterns: [
204
+ /\b(security|vulnerab|exploit|attack|auth|cve|injection|xss)\b/i,
205
+ /\bsecure\b/i,
206
+ ],
207
+ keywords: ['security', 'vulnerability', 'exploit', 'attack', 'authentication', 'authorization', 'cve', 'injection', 'xss', 'csrf'],
208
+ biases: {
209
+ scope_boost: { personal: 0.8, team: 1.0, department: 1.2, organization: 1.0 },
210
+ type_boost: { error_fix: 1.3, decision: 1.2, pattern: 1.0, conversation: 0.5 },
211
+ recency_boost: 1.1,
212
+ },
213
+ },
214
+ general: {
215
+ patterns: [],
216
+ keywords: [],
217
+ biases: {
218
+ scope_boost: { personal: 1.0, team: 1.0, department: 1.0, organization: 1.0 },
219
+ type_boost: {},
220
+ recency_boost: 1.0,
221
+ },
222
+ },
223
+ };
224
+
225
+ /**
226
+ * Detect the intent of a query using regex patterns and keyword scoring.
227
+ * No LLM call — fast and deterministic.
228
+ */
229
+ export function detectIntent(query: string): IntentResult {
230
+ const lower = query.toLowerCase();
231
+ let bestIntent = 'general';
232
+ let bestScore = 0;
233
+
234
+ for (const [name, def] of Object.entries(INTENTS)) {
235
+ if (name === 'general') continue;
236
+
237
+ let score = 0;
238
+
239
+ // Regex pattern matches (high weight)
240
+ for (const pattern of def.patterns) {
241
+ if (pattern.test(query)) score += 2;
242
+ }
243
+
244
+ // Keyword matches (lower weight)
245
+ for (const keyword of def.keywords) {
246
+ if (lower.includes(keyword)) score += 1;
247
+ }
248
+
249
+ if (score > bestScore) {
250
+ bestScore = score;
251
+ bestIntent = name;
252
+ }
253
+ }
254
+
255
+ return {
256
+ intent: bestIntent,
257
+ confidence: bestScore > 0 ? Math.min(1.0, bestScore / 6) : 0.5,
258
+ biases: INTENTS[bestIntent].biases,
259
+ };
260
+ }
261
+ ```
262
+
263
+ - [ ] **Step 4: Run tests to verify they pass**
264
+
265
+ Run: `npx vitest run tests/lib/cortex/retrieval/intent.test.ts`
266
+ Expected: PASS (7 tests)
267
+
268
+ - [ ] **Step 5: Commit**
269
+
270
+ ```bash
271
+ git add src/lib/cortex/retrieval/intent.ts tests/lib/cortex/retrieval/intent.test.ts
272
+ git commit -m "feat(cortex): add intent detection for context assembly"
273
+ ```
274
+
275
+ ---
276
+
277
+ ### Task 2: Weight computation
278
+
279
+ **Files:**
280
+ - Create: `src/lib/cortex/retrieval/weight.ts`
281
+ - Create: `tests/lib/cortex/retrieval/weight.test.ts`
282
+
283
+ - [ ] **Step 1: Write failing tests**
284
+
285
+ ```typescript
286
+ // tests/lib/cortex/retrieval/weight.test.ts
287
+ import { describe, it, expect } from 'vitest';
288
+ import { computeScopeWeight } from '@/lib/cortex/retrieval/weight';
289
+ import type { IntentBiases } from '@/lib/cortex/retrieval/intent';
290
+
291
+ describe('computeScopeWeight', () => {
292
+ const neutralBiases: IntentBiases = {
293
+ scope_boost: { personal: 1.0, team: 1.0, department: 1.0, organization: 1.0 },
294
+ type_boost: {},
295
+ recency_boost: 1.0,
296
+ };
297
+
298
+ it('returns 1.0 for self (distance 0)', () => {
299
+ const weight = computeScopeWeight({
300
+ graphProximity: 1.0, // 1/(1+0)
301
+ scopeLevel: 'personal',
302
+ intentBiases: neutralBiases,
303
+ authorityFactor: 1.0,
304
+ });
305
+ expect(weight).toBeCloseTo(1.0);
306
+ });
307
+
308
+ it('decreases with graph distance', () => {
309
+ const close = computeScopeWeight({
310
+ graphProximity: 0.5, // distance 1
311
+ scopeLevel: 'team',
312
+ intentBiases: neutralBiases,
313
+ authorityFactor: 1.0,
314
+ });
315
+ const far = computeScopeWeight({
316
+ graphProximity: 0.25, // distance 3
317
+ scopeLevel: 'organization',
318
+ intentBiases: neutralBiases,
319
+ authorityFactor: 1.0,
320
+ });
321
+ expect(close).toBeGreaterThan(far);
322
+ });
323
+
324
+ it('is boosted by intent biases', () => {
325
+ const debugBiases: IntentBiases = {
326
+ scope_boost: { personal: 1.2, team: 0.8 },
327
+ type_boost: {},
328
+ recency_boost: 1.0,
329
+ };
330
+ const personal = computeScopeWeight({
331
+ graphProximity: 0.5,
332
+ scopeLevel: 'personal',
333
+ intentBiases: debugBiases,
334
+ authorityFactor: 1.0,
335
+ });
336
+ const team = computeScopeWeight({
337
+ graphProximity: 0.5,
338
+ scopeLevel: 'team',
339
+ intentBiases: debugBiases,
340
+ authorityFactor: 1.0,
341
+ });
342
+ expect(personal).toBeGreaterThan(team);
343
+ });
344
+
345
+ it('is boosted by authority factor', () => {
346
+ const low = computeScopeWeight({
347
+ graphProximity: 0.5,
348
+ scopeLevel: 'team',
349
+ intentBiases: neutralBiases,
350
+ authorityFactor: 1.0,
351
+ });
352
+ const high = computeScopeWeight({
353
+ graphProximity: 0.5,
354
+ scopeLevel: 'team',
355
+ intentBiases: neutralBiases,
356
+ authorityFactor: 1.2,
357
+ });
358
+ expect(high).toBeGreaterThan(low);
359
+ });
360
+
361
+ it('never returns negative', () => {
362
+ const weight = computeScopeWeight({
363
+ graphProximity: 0,
364
+ scopeLevel: 'organization',
365
+ intentBiases: neutralBiases,
366
+ authorityFactor: 1.0,
367
+ });
368
+ expect(weight).toBeGreaterThanOrEqual(0);
369
+ });
370
+ });
371
+ ```
372
+
373
+ - [ ] **Step 2: Run tests to verify they fail**
374
+
375
+ Run: `npx vitest run tests/lib/cortex/retrieval/weight.test.ts`
376
+
377
+ - [ ] **Step 3: Implement weight computation**
378
+
379
+ ```typescript
380
+ // src/lib/cortex/retrieval/weight.ts
381
+ import type { IntentBiases } from './intent';
382
+ import type { ScopeLevel } from '../knowledge/types';
383
+
384
+ export interface ScopeWeightInput {
385
+ graphProximity: number; // 0-1, from EntityGraph.proximity()
386
+ scopeLevel: ScopeLevel | string;
387
+ intentBiases: IntentBiases;
388
+ authorityFactor: number; // 1.0 default, higher for experts/docs
389
+ }
390
+
391
+ /**
392
+ * Compute the retrieval weight for a knowledge scope.
393
+ *
394
+ * weight = graphProximity × intentBias × authorityFactor
395
+ *
396
+ * Per spec: weight(scope) = graph_proximity × intent_bias × freshness_bonus × authority
397
+ * Freshness is applied per-result in the fusion stage, not per-scope.
398
+ */
399
+ export function computeScopeWeight(input: ScopeWeightInput): number {
400
+ const { graphProximity, scopeLevel, intentBiases, authorityFactor } = input;
401
+
402
+ const intentBias = intentBiases.scope_boost[scopeLevel] ?? 1.0;
403
+
404
+ return Math.max(0, graphProximity * intentBias * authorityFactor);
405
+ }
406
+
407
+ /**
408
+ * Compute per-result type boost from intent biases.
409
+ */
410
+ export function computeTypeBoost(knowledgeType: string, intentBiases: IntentBiases): number {
411
+ return intentBiases.type_boost[knowledgeType] ?? 1.0;
412
+ }
413
+
414
+ /**
415
+ * Authority factor for a source based on role and expertise.
416
+ *
417
+ * role_boost: 0.0 member, 0.1 lead, 0.15 senior/principal, 0.2 director+
418
+ * expertise_weight: EXPERT_IN edge weight (0-1)
419
+ * Documents get 1.2 base authority.
420
+ */
421
+ export function computeAuthority(params: {
422
+ role?: string;
423
+ expertiseWeight?: number;
424
+ isDocument?: boolean;
425
+ }): number {
426
+ const { role, expertiseWeight = 0, isDocument = false } = params;
427
+
428
+ if (isDocument) return 1.2;
429
+
430
+ const roleBoosts: Record<string, number> = {
431
+ member: 0.0,
432
+ lead: 0.1,
433
+ senior: 0.15,
434
+ principal: 0.15,
435
+ director: 0.2,
436
+ vp: 0.2,
437
+ cto: 0.2,
438
+ };
439
+
440
+ const roleBoost = roleBoosts[role?.toLowerCase() ?? 'member'] ?? 0.0;
441
+ return Math.max(1.0, roleBoost + expertiseWeight);
442
+ }
443
+ ```
444
+
445
+ - [ ] **Step 4: Run tests to verify they pass**
446
+
447
+ Run: `npx vitest run tests/lib/cortex/retrieval/weight.test.ts`
448
+ Expected: PASS (5 tests)
449
+
450
+ - [ ] **Step 5: Commit**
451
+
452
+ ```bash
453
+ git add src/lib/cortex/retrieval/weight.ts tests/lib/cortex/retrieval/weight.test.ts
454
+ git commit -m "feat(cortex): add scope weight computation for context assembly"
455
+ ```
456
+
457
+ ---
458
+
459
+ ## Chunk 2: Conflict Detection and Context Formatting
460
+
461
+ ### Task 3: Conflict detection
462
+
463
+ **Files:**
464
+ - Create: `src/lib/cortex/retrieval/conflict.ts`
465
+ - Create: `tests/lib/cortex/retrieval/conflict.test.ts`
466
+
467
+ - [ ] **Step 1: Write failing tests**
468
+
469
+ ```typescript
470
+ // tests/lib/cortex/retrieval/conflict.test.ts
471
+ import { describe, it, expect } from 'vitest';
472
+ import { detectConflicts } from '@/lib/cortex/retrieval/conflict';
473
+ import type { ScoredKnowledge } from '@/lib/cortex/knowledge/types';
474
+
475
+ function makeResult(overrides: Partial<ScoredKnowledge> = {}): ScoredKnowledge {
476
+ return {
477
+ id: 'r1', vector: [], text: 'test', type: 'decision', layer: 'personal',
478
+ workspace_id: null, session_id: null, agent_type: 'claude',
479
+ project_path: null, file_refs: [], confidence: 0.8,
480
+ created: new Date().toISOString(), source_timestamp: new Date().toISOString(),
481
+ stale_score: 0, access_count: 0, last_accessed: null, metadata: {},
482
+ relevance_score: 0.9, similarity: 0.9,
483
+ contradiction_refs: [], ...overrides,
484
+ };
485
+ }
486
+
487
+ describe('detectConflicts', () => {
488
+ it('returns no conflicts when no contradiction_refs', () => {
489
+ const results = [makeResult({ id: 'a' }), makeResult({ id: 'b' })];
490
+ const conflicts = detectConflicts(results);
491
+ expect(conflicts).toHaveLength(0);
492
+ });
493
+
494
+ it('detects conflict between two results', () => {
495
+ const results = [
496
+ makeResult({ id: 'a', text: 'use pool size 50', contradiction_refs: ['b'] }),
497
+ makeResult({ id: 'b', text: 'scale horizontally', contradiction_refs: ['a'] }),
498
+ ];
499
+ const conflicts = detectConflicts(results);
500
+ expect(conflicts).toHaveLength(1);
501
+ expect(conflicts[0].unitA.id).toBe('a');
502
+ expect(conflicts[0].unitB.id).toBe('b');
503
+ });
504
+
505
+ it('ignores contradiction_refs pointing to results not in the set', () => {
506
+ const results = [
507
+ makeResult({ id: 'a', contradiction_refs: ['z'] }), // z is not in results
508
+ ];
509
+ const conflicts = detectConflicts(results);
510
+ expect(conflicts).toHaveLength(0);
511
+ });
512
+
513
+ it('deduplicates symmetric conflicts', () => {
514
+ // A contradicts B and B contradicts A should produce one conflict, not two
515
+ const results = [
516
+ makeResult({ id: 'a', contradiction_refs: ['b'] }),
517
+ makeResult({ id: 'b', contradiction_refs: ['a'] }),
518
+ ];
519
+ const conflicts = detectConflicts(results);
520
+ expect(conflicts).toHaveLength(1);
521
+ });
522
+ });
523
+ ```
524
+
525
+ - [ ] **Step 2: Implement conflict detection**
526
+
527
+ ```typescript
528
+ // src/lib/cortex/retrieval/conflict.ts
529
+ import type { ScoredKnowledge } from '../knowledge/types';
530
+
531
+ export interface ConflictPair {
532
+ unitA: ScoredKnowledge;
533
+ unitB: ScoredKnowledge;
534
+ }
535
+
536
+ /**
537
+ * Detect conflicts among search results by checking contradiction_refs.
538
+ * Returns deduplicated conflict pairs (A↔B counted once, not twice).
539
+ */
540
+ export function detectConflicts(results: ScoredKnowledge[]): ConflictPair[] {
541
+ const resultMap = new Map(results.map(r => [r.id, r]));
542
+ const seen = new Set<string>();
543
+ const conflicts: ConflictPair[] = [];
544
+
545
+ for (const result of results) {
546
+ const refs = result.contradiction_refs ?? [];
547
+ for (const refId of refs) {
548
+ const other = resultMap.get(refId);
549
+ if (!other) continue;
550
+
551
+ const key = [result.id, refId].sort().join('|');
552
+ if (seen.has(key)) continue;
553
+ seen.add(key);
554
+
555
+ conflicts.push({ unitA: result, unitB: other });
556
+ }
557
+ }
558
+
559
+ return conflicts;
560
+ }
561
+ ```
562
+
563
+ - [ ] **Step 3: Run tests, commit**
564
+
565
+ Run: `npx vitest run tests/lib/cortex/retrieval/conflict.test.ts`
566
+
567
+ ```bash
568
+ git add src/lib/cortex/retrieval/conflict.ts tests/lib/cortex/retrieval/conflict.test.ts
569
+ git commit -m "feat(cortex): add conflict detection for search results"
570
+ ```
571
+
572
+ ---
573
+
574
+ ### Task 4: Context formatter
575
+
576
+ **Files:**
577
+ - Create: `src/lib/cortex/retrieval/formatter.ts`
578
+ - Create: `tests/lib/cortex/retrieval/formatter.test.ts`
579
+
580
+ - [ ] **Step 1: Write failing tests**
581
+
582
+ ```typescript
583
+ // tests/lib/cortex/retrieval/formatter.test.ts
584
+ import { describe, it, expect } from 'vitest';
585
+ import { formatContext } from '@/lib/cortex/retrieval/formatter';
586
+ import type { ScoredKnowledge } from '@/lib/cortex/knowledge/types';
587
+ import type { ConflictPair } from '@/lib/cortex/retrieval/conflict';
588
+
589
+ function makeResult(overrides: Partial<ScoredKnowledge> = {}): ScoredKnowledge {
590
+ return {
591
+ id: 'r1', vector: [], text: 'test knowledge', type: 'decision', layer: 'personal',
592
+ workspace_id: null, session_id: null, agent_type: 'claude',
593
+ project_path: null, file_refs: [], confidence: 0.8,
594
+ created: '2026-03-15T00:00:00.000Z', source_timestamp: '2026-03-15T00:00:00.000Z',
595
+ stale_score: 0, access_count: 5, last_accessed: null, metadata: {},
596
+ relevance_score: 0.9, similarity: 0.9,
597
+ ...overrides,
598
+ };
599
+ }
600
+
601
+ describe('formatContext', () => {
602
+ it('wraps results in cortex-context tags', () => {
603
+ const output = formatContext([makeResult()], []);
604
+ expect(output).toContain('<cortex-context>');
605
+ expect(output).toContain('</cortex-context>');
606
+ });
607
+
608
+ it('includes type labels and dates', () => {
609
+ const output = formatContext([makeResult({ type: 'error_fix', source_timestamp: '2026-03-10T00:00:00.000Z' })], []);
610
+ expect(output).toContain('[Error Fix]');
611
+ expect(output).toContain('2026-03-10');
612
+ });
613
+
614
+ it('includes source attribution when origin is present', () => {
615
+ const output = formatContext([makeResult({
616
+ origin: { source_type: 'conversation', source_ref: 'sess-1', creator_entity_id: 'person-alice' },
617
+ })], []);
618
+ expect(output).toContain('person-alice');
619
+ });
620
+
621
+ it('includes conflict callout when conflicts exist', () => {
622
+ const a = makeResult({ id: 'a', text: 'use pool size 50' });
623
+ const b = makeResult({ id: 'b', text: 'scale horizontally' });
624
+ const conflicts: ConflictPair[] = [{ unitA: a, unitB: b }];
625
+ const output = formatContext([a, b], conflicts);
626
+ expect(output).toContain('Conflicting');
627
+ });
628
+
629
+ it('respects max token budget', () => {
630
+ const bigResults = Array.from({ length: 20 }, (_, i) =>
631
+ makeResult({ id: `r${i}`, text: 'x'.repeat(500) })
632
+ );
633
+ const output = formatContext(bigResults, [], { maxTokens: 500 });
634
+ // Should not include all 20 results (would be ~2500 tokens)
635
+ expect(output.length).toBeLessThan(3000);
636
+ });
637
+
638
+ it('returns empty string when no results', () => {
639
+ expect(formatContext([], [])).toBe('');
640
+ });
641
+ });
642
+ ```
643
+
644
+ - [ ] **Step 2: Implement context formatter**
645
+
646
+ ```typescript
647
+ // src/lib/cortex/retrieval/formatter.ts
648
+ import type { ScoredKnowledge } from '../knowledge/types';
649
+ import type { ConflictPair } from './conflict';
650
+
651
+ const TYPE_LABELS: Record<string, string> = {
652
+ decision: 'Decision', pattern: 'Pattern', preference: 'Preference',
653
+ error_fix: 'Error Fix', context: 'Context', code_pattern: 'Code',
654
+ command: 'Command', conversation: 'Conversation', summary: 'Summary',
655
+ };
656
+
657
+ export interface FormatOptions {
658
+ maxTokens?: number;
659
+ }
660
+
661
+ /**
662
+ * Format search results + conflicts as annotated <cortex-context> for RAG injection.
663
+ */
664
+ export function formatContext(
665
+ results: ScoredKnowledge[],
666
+ conflicts: ConflictPair[],
667
+ options: FormatOptions = {},
668
+ ): string {
669
+ if (results.length === 0) return '';
670
+
671
+ const maxTokens = options.maxTokens ?? 1500;
672
+ const entries: string[] = [];
673
+ let tokens = 20; // overhead for tags
674
+
675
+ // Format each result with attribution
676
+ for (const unit of results) {
677
+ const label = TYPE_LABELS[unit.type] || unit.type;
678
+ const date = (unit.source_timestamp || '').slice(0, 10);
679
+ const creator = unit.origin?.creator_entity_id ?? '';
680
+ const sourceInfo = creator ? ` (${creator})` : '';
681
+
682
+ let entry = `[${label}] ${date}${sourceInfo}:\n ${unit.text}`;
683
+
684
+ const entryTokens = Math.ceil(entry.length / 4);
685
+ if (tokens + entryTokens > maxTokens) break;
686
+
687
+ entries.push(entry);
688
+ tokens += entryTokens;
689
+ }
690
+
691
+ if (entries.length === 0) return '';
692
+
693
+ // Build conflict section
694
+ let conflictSection = '';
695
+ if (conflicts.length > 0) {
696
+ const conflictLines = conflicts.map(c =>
697
+ ` - "${c.unitA.text.slice(0, 80)}..." vs "${c.unitB.text.slice(0, 80)}..."`
698
+ );
699
+ conflictSection = `\nConflicting perspectives (${conflicts.length}):\n${conflictLines.join('\n')}\n`;
700
+ }
701
+
702
+ const sourceCount = entries.length;
703
+ const header = `Relevant knowledge (${sourceCount} source${sourceCount > 1 ? 's' : ''}${conflicts.length > 0 ? `, ${conflicts.length} conflict${conflicts.length > 1 ? 's' : ''}` : ''}):`;
704
+
705
+ return [
706
+ '<cortex-context>',
707
+ header,
708
+ '',
709
+ ...entries,
710
+ conflictSection,
711
+ '</cortex-context>',
712
+ ].join('\n');
713
+ }
714
+ ```
715
+
716
+ - [ ] **Step 3: Run tests, commit**
717
+
718
+ Run: `npx vitest run tests/lib/cortex/retrieval/formatter.test.ts`
719
+
720
+ ```bash
721
+ git add src/lib/cortex/retrieval/formatter.ts tests/lib/cortex/retrieval/formatter.test.ts
722
+ git commit -m "feat(cortex): add context formatter for RAG injection"
723
+ ```
724
+
725
+ ---
726
+
727
+ ## Chunk 3: Context Assembly Engine
728
+
729
+ ### Task 5: ContextEngine — the 6-stage pipeline
730
+
731
+ **Files:**
732
+ - Create: `src/lib/cortex/retrieval/context-engine.ts`
733
+ - Create: `tests/lib/cortex/retrieval/context-engine.test.ts`
734
+
735
+ - [ ] **Step 1: Write failing tests**
736
+
737
+ ```typescript
738
+ // tests/lib/cortex/retrieval/context-engine.test.ts
739
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
740
+ import { ContextEngine } from '@/lib/cortex/retrieval/context-engine';
741
+
742
+ // Create mocks for dependencies
743
+ const mockStore = {
744
+ search: vi.fn().mockResolvedValue([]),
745
+ };
746
+
747
+ const mockGraph = {
748
+ proximity: vi.fn().mockReturnValue(0.5),
749
+ neighborhood: vi.fn().mockReturnValue([]),
750
+ getEntity: vi.fn().mockReturnValue(null),
751
+ };
752
+
753
+ const mockResolver = {
754
+ extractEntities: vi.fn().mockReturnValue([]),
755
+ };
756
+
757
+ const mockEmbedding = {
758
+ embed: vi.fn().mockResolvedValue([[0.1, 0.2, 0.3]]),
759
+ dimensions: 3,
760
+ name: 'mock',
761
+ init: vi.fn(),
762
+ };
763
+
764
+ describe('ContextEngine', () => {
765
+ let engine: ContextEngine;
766
+
767
+ beforeEach(() => {
768
+ vi.clearAllMocks();
769
+ engine = new ContextEngine({
770
+ store: mockStore as any,
771
+ graph: mockGraph as any,
772
+ resolver: mockResolver as any,
773
+ embedding: mockEmbedding as any,
774
+ requesterId: 'person-alice',
775
+ });
776
+ });
777
+
778
+ it('returns empty context for empty results', async () => {
779
+ const result = await engine.assemble('some query');
780
+ expect(result.results).toHaveLength(0);
781
+ expect(result.context).toBe('');
782
+ });
783
+
784
+ it('calls embedding.embed with the query', async () => {
785
+ await engine.assemble('test query');
786
+ expect(mockEmbedding.embed).toHaveBeenCalledWith(['test query']);
787
+ });
788
+
789
+ it('detects intent from the query', async () => {
790
+ const result = await engine.assemble('why does auth throw an error?');
791
+ expect(result.intent.intent).toBe('debugging');
792
+ });
793
+
794
+ it('extracts entities from the query', async () => {
795
+ mockResolver.extractEntities.mockReturnValue([
796
+ { entity: { id: 'system-auth', type: 'system', name: 'Auth' }, confidence: 0.9, method: 'alias' },
797
+ ]);
798
+ const result = await engine.assemble('fix the auth service');
799
+ expect(mockResolver.extractEntities).toHaveBeenCalledWith('fix the auth service');
800
+ expect(result.entities).toHaveLength(1);
801
+ });
802
+
803
+ it('searches store with embedded query vector', async () => {
804
+ mockStore.search.mockResolvedValue([{
805
+ id: 'k1', text: 'test knowledge', type: 'decision', layer: 'personal',
806
+ confidence: 0.8, stale_score: 0, created: new Date().toISOString(),
807
+ source_timestamp: new Date().toISOString(), evidence_score: 0.7,
808
+ contradiction_refs: [], _distance: 0.2,
809
+ workspace_id: null, session_id: null, agent_type: 'claude',
810
+ project_path: null, file_refs: [], access_count: 0, last_accessed: null,
811
+ metadata: {},
812
+ }]);
813
+
814
+ const result = await engine.assemble('test query');
815
+ expect(result.results.length).toBeGreaterThanOrEqual(1);
816
+ expect(result.context).toContain('<cortex-context>');
817
+ });
818
+
819
+ it('completes within performance budget', async () => {
820
+ const start = Date.now();
821
+ await engine.assemble('test query');
822
+ const elapsed = Date.now() - start;
823
+ expect(elapsed).toBeLessThan(500); // generous for CI, target is 150ms
824
+ });
825
+ });
826
+ ```
827
+
828
+ - [ ] **Step 2: Implement ContextEngine**
829
+
830
+ ```typescript
831
+ // src/lib/cortex/retrieval/context-engine.ts
832
+ import type { CortexStore } from '../store';
833
+ import type { EntityGraph } from '../graph/entity-graph';
834
+ import type { EntityResolver, ResolvedEntity } from '../graph/resolver';
835
+ import type { EmbeddingProvider } from '../embeddings';
836
+ import type { ScoredKnowledge } from '../knowledge/types';
837
+ import { detectIntent } from './intent';
838
+ import type { IntentResult } from './intent';
839
+ import { computeScopeWeight, computeTypeBoost } from './weight';
840
+ import { detectConflicts } from './conflict';
841
+ import type { ConflictPair } from './conflict';
842
+ import { formatContext } from './formatter';
843
+ import { computeRelevanceScore } from './scoring';
844
+
845
+ export interface ContextEngineDeps {
846
+ store: CortexStore;
847
+ graph: EntityGraph;
848
+ resolver: EntityResolver;
849
+ embedding: EmbeddingProvider;
850
+ requesterId: string; // entity ID of the person making the query
851
+ }
852
+
853
+ export interface AssemblyResult {
854
+ results: ScoredKnowledge[];
855
+ conflicts: ConflictPair[];
856
+ context: string; // formatted <cortex-context> string
857
+ intent: IntentResult;
858
+ entities: ResolvedEntity[];
859
+ timing: {
860
+ intentMs: number;
861
+ entityMs: number;
862
+ searchMs: number;
863
+ totalMs: number;
864
+ };
865
+ }
866
+
867
+ interface SearchSource {
868
+ layerKey: string;
869
+ weight: number;
870
+ limit: number;
871
+ }
872
+
873
+ const DEFAULT_LAYERS = ['personal', 'workspace', 'team'] as const;
874
+ const SEARCH_TIMEOUT_MS = 100;
875
+
876
+ export class ContextEngine {
877
+ constructor(private deps: ContextEngineDeps) {}
878
+
879
+ async assemble(query: string, options: { limit?: number; workspaceId?: number | null; maxTokens?: number } = {}): Promise<AssemblyResult> {
880
+ const totalStart = Date.now();
881
+ const { limit = 5, workspaceId = null, maxTokens = 1500 } = options;
882
+
883
+ // Stage 1: Intent Detection
884
+ const intentStart = Date.now();
885
+ const intent = detectIntent(query);
886
+ const intentMs = Date.now() - intentStart;
887
+
888
+ // Stage 2: Entity Resolution
889
+ const entityStart = Date.now();
890
+ const entities = this.deps.resolver.extractEntities(query);
891
+ const entityMs = Date.now() - entityStart;
892
+
893
+ // Embed the query
894
+ const [queryVector] = await this.deps.embedding.embed([query]);
895
+
896
+ // Stage 3: Weight Computation
897
+ const sources = this.computeSourceWeights(intent, workspaceId);
898
+
899
+ // Stage 4: Parallel Multi-Source Search
900
+ const searchStart = Date.now();
901
+ const allResults = await this.parallelSearch(queryVector, sources, limit);
902
+ const searchMs = Date.now() - searchStart;
903
+
904
+ // Stage 5: Fusion + Re-Ranking
905
+ const fused = this.fuseAndRank(allResults, intent, limit);
906
+
907
+ // Stage 6: Conflict Detection + Formatting
908
+ const conflicts = detectConflicts(fused);
909
+ const context = formatContext(fused, conflicts, { maxTokens });
910
+
911
+ return {
912
+ results: fused,
913
+ conflicts,
914
+ context,
915
+ intent,
916
+ entities,
917
+ timing: {
918
+ intentMs,
919
+ entityMs,
920
+ searchMs,
921
+ totalMs: Date.now() - totalStart,
922
+ },
923
+ };
924
+ }
925
+
926
+ private computeSourceWeights(intent: IntentResult, workspaceId: number | null): SearchSource[] {
927
+ const sources: SearchSource[] = [];
928
+
929
+ for (const layer of DEFAULT_LAYERS) {
930
+ const layerKey = layer === 'workspace' && workspaceId
931
+ ? `workspace/${workspaceId}` : layer;
932
+
933
+ // Map v1 layer to scope level for intent bias lookup
934
+ const scopeLevel = layer === 'personal' ? 'personal'
935
+ : layer === 'workspace' ? 'team' : 'organization';
936
+
937
+ // Use graph proximity if available, else fall back to fixed weights
938
+ let graphProximity: number;
939
+ try {
940
+ // For personal, distance is 0; for workspace, 1; for team, 2
941
+ const layerEntity = layer === 'personal' ? this.deps.requesterId
942
+ : layer === 'workspace' ? 'team-default' : 'organization-default';
943
+ graphProximity = this.deps.graph.proximity(this.deps.requesterId, layerEntity);
944
+ } catch {
945
+ // Graph not populated, use fallback
946
+ graphProximity = layer === 'personal' ? 1.0 : layer === 'workspace' ? 0.5 : 0.33;
947
+ }
948
+
949
+ // If graph returns 0 (unreachable/not found), use fallback
950
+ if (graphProximity === 0) {
951
+ graphProximity = layer === 'personal' ? 1.0 : layer === 'workspace' ? 0.5 : 0.33;
952
+ }
953
+
954
+ const weight = computeScopeWeight({
955
+ graphProximity,
956
+ scopeLevel,
957
+ intentBiases: intent.biases,
958
+ authorityFactor: 1.0,
959
+ });
960
+
961
+ sources.push({
962
+ layerKey,
963
+ weight,
964
+ limit: Math.max(3, Math.round(weight * 10)), // proportional slots
965
+ });
966
+ }
967
+
968
+ return sources.sort((a, b) => b.weight - a.weight);
969
+ }
970
+
971
+ private async parallelSearch(
972
+ queryVector: number[],
973
+ sources: SearchSource[],
974
+ limit: number,
975
+ ): Promise<Array<ScoredKnowledge & { sourceWeight: number }>> {
976
+ const searchPromises = sources.map(async (source) => {
977
+ try {
978
+ const results = await Promise.race([
979
+ this.deps.store.search(source.layerKey, queryVector, source.limit),
980
+ new Promise<never>((_, reject) =>
981
+ setTimeout(() => reject(new Error('timeout')), SEARCH_TIMEOUT_MS)
982
+ ),
983
+ ]);
984
+
985
+ return results.map(unit => {
986
+ const similarity = 1 - ((unit as any)._distance ?? 0);
987
+ return {
988
+ ...unit,
989
+ similarity,
990
+ relevance_score: 0, // computed in fusion
991
+ sourceWeight: source.weight,
992
+ } as ScoredKnowledge & { sourceWeight: number };
993
+ });
994
+ } catch {
995
+ return []; // source failed or timed out
996
+ }
997
+ });
998
+
999
+ const settled = await Promise.allSettled(searchPromises);
1000
+ const allResults: Array<ScoredKnowledge & { sourceWeight: number }> = [];
1001
+
1002
+ for (const result of settled) {
1003
+ if (result.status === 'fulfilled') {
1004
+ allResults.push(...result.value);
1005
+ }
1006
+ }
1007
+
1008
+ return allResults;
1009
+ }
1010
+
1011
+ private fuseAndRank(
1012
+ results: Array<ScoredKnowledge & { sourceWeight: number }>,
1013
+ intent: IntentResult,
1014
+ limit: number,
1015
+ ): ScoredKnowledge[] {
1016
+ // Score each result
1017
+ for (const result of results) {
1018
+ const typeBoost = computeTypeBoost(result.type, intent.biases);
1019
+ const recencyBoost = intent.biases.recency_boost;
1020
+
1021
+ result.relevance_score = computeRelevanceScore({
1022
+ similarity: result.similarity,
1023
+ confidence: result.confidence,
1024
+ stale_score: result.stale_score,
1025
+ created: result.created,
1026
+ evidence_score: result.evidence_score,
1027
+ }) * result.sourceWeight * typeBoost * recencyBoost;
1028
+ }
1029
+
1030
+ // Deduplicate: cosine > 0.9 between results (approximate via text similarity)
1031
+ const deduped = this.deduplicateResults(results);
1032
+
1033
+ // Sort and take top K
1034
+ deduped.sort((a, b) => b.relevance_score - a.relevance_score);
1035
+ return deduped.slice(0, limit);
1036
+ }
1037
+
1038
+ private deduplicateResults(results: ScoredKnowledge[]): ScoredKnowledge[] {
1039
+ const kept: ScoredKnowledge[] = [];
1040
+ const seenTexts = new Set<string>();
1041
+
1042
+ // Sort by score first so we keep the better-scored version
1043
+ results.sort((a, b) => b.relevance_score - a.relevance_score);
1044
+
1045
+ for (const result of results) {
1046
+ // Simple text-based dedup: normalize and check prefix overlap
1047
+ const normalized = result.text.slice(0, 200).toLowerCase().trim();
1048
+ if (seenTexts.has(normalized)) continue;
1049
+
1050
+ // Check against existing kept items for high text overlap
1051
+ let isDupe = false;
1052
+ for (const existing of kept) {
1053
+ if (result.id === existing.id) { isDupe = true; break; }
1054
+ // If texts share >80% of content, consider duplicate
1055
+ const existNorm = existing.text.slice(0, 200).toLowerCase().trim();
1056
+ if (normalized === existNorm) { isDupe = true; break; }
1057
+ }
1058
+
1059
+ if (!isDupe) {
1060
+ seenTexts.add(normalized);
1061
+ kept.push(result);
1062
+ }
1063
+ }
1064
+
1065
+ return kept;
1066
+ }
1067
+ }
1068
+ ```
1069
+
1070
+ - [ ] **Step 3: Run tests to verify they pass**
1071
+
1072
+ Run: `npx vitest run tests/lib/cortex/retrieval/context-engine.test.ts`
1073
+ Expected: PASS (6 tests)
1074
+
1075
+ - [ ] **Step 4: Commit**
1076
+
1077
+ ```bash
1078
+ git add src/lib/cortex/retrieval/context-engine.ts tests/lib/cortex/retrieval/context-engine.test.ts
1079
+ git commit -m "feat(cortex): add ContextEngine with 6-stage retrieval pipeline"
1080
+ ```
1081
+
1082
+ ---
1083
+
1084
+ ## Chunk 4: API Endpoint and Hook Integration
1085
+
1086
+ ### Task 6: Context assembly API endpoint
1087
+
1088
+ **Files:**
1089
+ - Create: `src/app/api/cortex/context/route.ts`
1090
+
1091
+ - [ ] **Step 1: Create the endpoint**
1092
+
1093
+ ```typescript
1094
+ // src/app/api/cortex/context/route.ts
1095
+ import { NextResponse } from 'next/server';
1096
+ import type { NextRequest } from 'next/server';
1097
+ import { getAuthUser, withUser } from '@/lib/auth';
1098
+ import { getCortex, isCortexAvailable } from '@/lib/cortex';
1099
+ import { ContextEngine } from '@/lib/cortex/retrieval/context-engine';
1100
+ import { EntityResolver } from '@/lib/cortex/graph/resolver';
1101
+ import { slugify } from '@/lib/cortex/graph/types';
1102
+
1103
+ export async function GET(request: NextRequest) {
1104
+ const user = getAuthUser(request);
1105
+ return withUser(user, async () => {
1106
+ if (!isCortexAvailable()) {
1107
+ return NextResponse.json({ error: 'Cortex unavailable' }, { status: 403 });
1108
+ }
1109
+ const cortex = await getCortex();
1110
+ if (!cortex) return NextResponse.json({ results: [], context: '' });
1111
+
1112
+ const url = new URL(request.url);
1113
+ const query = url.searchParams.get('q') || '';
1114
+ const limit = parseInt(url.searchParams.get('limit') || '5', 10);
1115
+ const workspaceId = url.searchParams.get('workspace_id');
1116
+ const maxTokens = parseInt(url.searchParams.get('max_tokens') || '1500', 10);
1117
+
1118
+ if (!query || query.length < 3) {
1119
+ return NextResponse.json({ results: [], context: '' });
1120
+ }
1121
+
1122
+ const resolver = new EntityResolver(cortex.graph);
1123
+ const requesterId = `person-${slugify(user)}`;
1124
+
1125
+ const engine = new ContextEngine({
1126
+ store: cortex.store,
1127
+ graph: cortex.graph,
1128
+ resolver,
1129
+ embedding: cortex.embedding,
1130
+ requesterId,
1131
+ });
1132
+
1133
+ const result = await engine.assemble(query, {
1134
+ limit,
1135
+ workspaceId: workspaceId ? parseInt(workspaceId, 10) : null,
1136
+ maxTokens,
1137
+ });
1138
+
1139
+ return NextResponse.json({
1140
+ results: result.results.map(r => ({ ...r, vector: undefined })), // strip vectors
1141
+ context: result.context,
1142
+ intent: result.intent,
1143
+ conflicts: result.conflicts.length,
1144
+ timing: result.timing,
1145
+ });
1146
+ });
1147
+ }
1148
+ ```
1149
+
1150
+ - [ ] **Step 2: Commit**
1151
+
1152
+ ```bash
1153
+ git add src/app/api/cortex/context/route.ts
1154
+ git commit -m "feat(cortex): add context assembly API endpoint"
1155
+ ```
1156
+
1157
+ ---
1158
+
1159
+ ### Task 7: Update RAG hook to use context assembly endpoint
1160
+
1161
+ **Files:**
1162
+ - Modify: `bin/cortex-hook.js`
1163
+
1164
+ - [ ] **Step 1: Read current cortex-hook.js**
1165
+
1166
+ Read the file to understand the current flow: query → /api/cortex/search → format results → output.
1167
+
1168
+ - [ ] **Step 2: Update to call context assembly endpoint**
1169
+
1170
+ Change the URL from `/api/cortex/search/?q=...` to `/api/cortex/context/?q=...`. The new endpoint returns `{ context, results, intent, conflicts, timing }` — the `context` field is already pre-formatted as `<cortex-context>`, so the hook can output it directly instead of doing its own formatting.
1171
+
1172
+ Key changes:
1173
+ 1. URL: `/api/cortex/search/` → `/api/cortex/context/`
1174
+ 2. Response handling: use `parsed.context` directly instead of formatting results manually
1175
+ 3. Keep the fallback: if the new endpoint returns empty or fails, fall back to old behavior
1176
+
1177
+ ```javascript
1178
+ // Replace the response handling section:
1179
+ const parsed = JSON.parse(body);
1180
+
1181
+ // New: context is pre-formatted by the Context Assembly Engine
1182
+ if (parsed.context) {
1183
+ const output = JSON.stringify({
1184
+ hookSpecificOutput: {
1185
+ hookEventName: 'UserPromptSubmit',
1186
+ additionalContext: parsed.context,
1187
+ },
1188
+ });
1189
+ process.stdout.write(output);
1190
+ process.exit(0);
1191
+ }
1192
+
1193
+ // Fallback: old-style results formatting (if context endpoint not available)
1194
+ const results = parsed.results;
1195
+ if (!results || results.length === 0) process.exit(0);
1196
+ // ... existing formatting code stays as fallback ...
1197
+ ```
1198
+
1199
+ - [ ] **Step 3: Commit**
1200
+
1201
+ ```bash
1202
+ git add bin/cortex-hook.js
1203
+ git commit -m "feat(cortex): switch RAG hook to context assembly endpoint"
1204
+ ```
1205
+
1206
+ ---
1207
+
1208
+ ### Task 8: Integrate ContextEngine into CortexInstance
1209
+
1210
+ **Files:**
1211
+ - Modify: `src/lib/cortex/index.ts`
1212
+
1213
+ - [ ] **Step 1: Read current index.ts**
1214
+
1215
+ - [ ] **Step 2: Add ContextEngine to CortexInstance**
1216
+
1217
+ 1. Import: `import { ContextEngine } from './retrieval/context-engine';` and `import { EntityResolver } from './graph/resolver';`
1218
+ 2. Add `contextEngine?: ContextEngine` to `CortexInstance` interface (optional since it depends on graph)
1219
+ 3. In `getCortex()`, after graph initialization, create the ContextEngine:
1220
+
1221
+ ```typescript
1222
+ const resolver = new EntityResolver(graph);
1223
+ const contextEngine = new ContextEngine({
1224
+ store,
1225
+ graph,
1226
+ resolver,
1227
+ embedding,
1228
+ requesterId: 'person-default-user', // default; overridden per-request in API
1229
+ });
1230
+ ```
1231
+
1232
+ 4. Add to instance object: `contextEngine,`
1233
+
1234
+ - [ ] **Step 3: Run full cortex test suite**
1235
+
1236
+ Run: `npx vitest run tests/lib/cortex/`
1237
+
1238
+ - [ ] **Step 4: Commit**
1239
+
1240
+ ```bash
1241
+ git add src/lib/cortex/index.ts
1242
+ git commit -m "feat(cortex): add ContextEngine to CortexInstance"
1243
+ ```
1244
+
1245
+ ---
1246
+
1247
+ ## Summary
1248
+
1249
+ | Task | Component | Tests | Status |
1250
+ |------|-----------|-------|--------|
1251
+ | 1 | Intent detection | 7 | |
1252
+ | 2 | Weight computation | 5 | |
1253
+ | 3 | Conflict detection | 4 | |
1254
+ | 4 | Context formatter | 6 | |
1255
+ | 5 | ContextEngine (6-stage pipeline) | 6 | |
1256
+ | 6 | Context assembly API endpoint | — | |
1257
+ | 7 | RAG hook integration | — | |
1258
+ | 8 | CortexInstance integration | regression | |
1259
+
1260
+ **Total: 8 tasks, ~28 new tests, 4 chunks**
1261
+
1262
+ **Performance budget:** The ContextEngine targets <150ms total latency:
1263
+ - Intent detection: <5ms (regex, no LLM)
1264
+ - Entity resolution: <5ms (alias lookup)
1265
+ - Weight computation: <10ms (graph proximity, cached)
1266
+ - Parallel search: <100ms (concurrent vector search with 100ms timeout)
1267
+ - Fusion + formatting: <10ms
1268
+
1269
+ **Key design decisions:**
1270
+ - ContextEngine sits ABOVE CortexSearch — doesn't replace it, wraps it
1271
+ - Graph proximity drives weights — but gracefully degrades to fixed weights when graph is empty
1272
+ - Deduplication uses text prefix comparison (not cosine between result vectors — that would require N² vector ops)
1273
+ - RAG hook uses pre-formatted `context` from the engine — no more client-side formatting
1274
+ - Old `/api/cortex/search` endpoint still works — new `/api/cortex/context` endpoint adds the intelligence layer