shieldcortex 4.28.1 → 4.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
  2. package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
  3. package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
  4. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
  5. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
  6. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  9. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  10. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +2 -2
  12. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +1 -1
  13. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  14. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  15. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  16. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  17. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  18. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  19. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.html +1 -1
  20. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.rsc +1 -1
  21. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/!KGRhc2hib2FyZCk/admin/__PAGE__.segment.rsc +1 -1
  22. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/!KGRhc2hib2FyZCk/admin.segment.rsc +1 -1
  23. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  24. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_full.segment.rsc +1 -1
  25. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_head.segment.rsc +1 -1
  26. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_index.segment.rsc +1 -1
  27. package/dashboard/.next/standalone/dashboard/.next/server/app/admin.segments/_tree.segment.rsc +1 -1
  28. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.html +1 -1
  29. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.rsc +1 -1
  30. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/!KGRhc2hib2FyZCk/cloud/__PAGE__.segment.rsc +1 -1
  31. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/!KGRhc2hib2FyZCk/cloud.segment.rsc +1 -1
  32. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  33. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_full.segment.rsc +1 -1
  34. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_head.segment.rsc +1 -1
  35. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_index.segment.rsc +1 -1
  36. package/dashboard/.next/standalone/dashboard/.next/server/app/cloud.segments/_tree.segment.rsc +1 -1
  37. package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
  38. package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +1 -1
  39. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  40. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +1 -1
  41. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
  42. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +1 -1
  43. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  44. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.html +1 -1
  45. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.rsc +1 -1
  46. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk/memory/capture/__PAGE__.segment.rsc +1 -1
  47. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk/memory/capture.segment.rsc +1 -1
  48. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
  49. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  50. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_full.segment.rsc +1 -1
  51. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_head.segment.rsc +1 -1
  52. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_index.segment.rsc +1 -1
  53. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/capture.segments/_tree.segment.rsc +1 -1
  54. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.html +1 -1
  55. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.rsc +1 -1
  56. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk/memory/graph/__PAGE__.segment.rsc +1 -1
  57. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk/memory/graph.segment.rsc +1 -1
  58. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
  59. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  60. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_full.segment.rsc +1 -1
  61. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_head.segment.rsc +1 -1
  62. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_index.segment.rsc +1 -1
  63. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/graph.segments/_tree.segment.rsc +1 -1
  64. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.html +1 -1
  65. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.rsc +1 -1
  66. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk/memory/recall/__PAGE__.segment.rsc +1 -1
  67. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk/memory/recall.segment.rsc +1 -1
  68. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
  69. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  70. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_full.segment.rsc +1 -1
  71. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_head.segment.rsc +1 -1
  72. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_index.segment.rsc +1 -1
  73. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/recall.segments/_tree.segment.rsc +1 -1
  74. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.html +2 -2
  75. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.rsc +1 -1
  76. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk/memory/replay/__PAGE__.segment.rsc +1 -1
  77. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk/memory/replay.segment.rsc +1 -1
  78. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
  79. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  80. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_full.segment.rsc +1 -1
  81. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_head.segment.rsc +1 -1
  82. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_index.segment.rsc +1 -1
  83. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/replay.segments/_tree.segment.rsc +1 -1
  84. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.html +1 -1
  85. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.rsc +1 -1
  86. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk/memory/review/__PAGE__.segment.rsc +1 -1
  87. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk/memory/review.segment.rsc +1 -1
  88. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
  89. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  90. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_full.segment.rsc +1 -1
  91. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_head.segment.rsc +1 -1
  92. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_index.segment.rsc +1 -1
  93. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/review.segments/_tree.segment.rsc +1 -1
  94. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.html +1 -1
  95. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.rsc +1 -1
  96. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk/memory/timeline/__PAGE__.segment.rsc +1 -1
  97. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk/memory/timeline.segment.rsc +1 -1
  98. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
  99. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  100. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_full.segment.rsc +1 -1
  101. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_head.segment.rsc +1 -1
  102. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_index.segment.rsc +1 -1
  103. package/dashboard/.next/standalone/dashboard/.next/server/app/memory/timeline.segments/_tree.segment.rsc +1 -1
  104. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.html +2 -2
  105. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.rsc +1 -1
  106. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/!KGRhc2hib2FyZCk/memory/__PAGE__.segment.rsc +1 -1
  107. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/!KGRhc2hib2FyZCk/memory.segment.rsc +1 -1
  108. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  109. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_full.segment.rsc +1 -1
  110. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_head.segment.rsc +1 -1
  111. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_index.segment.rsc +1 -1
  112. package/dashboard/.next/standalone/dashboard/.next/server/app/memory.segments/_tree.segment.rsc +1 -1
  113. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.html +2 -2
  114. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.rsc +1 -1
  115. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/!KGRhc2hib2FyZCk/overview/__PAGE__.segment.rsc +1 -1
  116. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/!KGRhc2hib2FyZCk/overview.segment.rsc +1 -1
  117. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  118. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_full.segment.rsc +1 -1
  119. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_head.segment.rsc +1 -1
  120. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_index.segment.rsc +1 -1
  121. package/dashboard/.next/standalone/dashboard/.next/server/app/overview.segments/_tree.segment.rsc +1 -1
  122. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.html +1 -1
  123. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.rsc +1 -1
  124. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk/protection/audit/__PAGE__.segment.rsc +1 -1
  125. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk/protection/audit.segment.rsc +1 -1
  126. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
  127. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  128. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_full.segment.rsc +1 -1
  129. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_head.segment.rsc +1 -1
  130. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_index.segment.rsc +1 -1
  131. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/audit.segments/_tree.segment.rsc +1 -1
  132. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.html +1 -1
  133. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.rsc +1 -1
  134. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk/protection/intercepts/__PAGE__.segment.rsc +1 -1
  135. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk/protection/intercepts.segment.rsc +1 -1
  136. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
  137. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  138. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_full.segment.rsc +1 -1
  139. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_head.segment.rsc +1 -1
  140. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_index.segment.rsc +1 -1
  141. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/intercepts.segments/_tree.segment.rsc +1 -1
  142. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.html +1 -1
  143. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.rsc +1 -1
  144. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk/protection/iron-dome/__PAGE__.segment.rsc +1 -1
  145. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk/protection/iron-dome.segment.rsc +1 -1
  146. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
  147. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  148. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_full.segment.rsc +1 -1
  149. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_head.segment.rsc +1 -1
  150. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_index.segment.rsc +1 -1
  151. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/iron-dome.segments/_tree.segment.rsc +1 -1
  152. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.html +1 -1
  153. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.rsc +1 -1
  154. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk/protection/policies/__PAGE__.segment.rsc +1 -1
  155. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk/protection/policies.segment.rsc +1 -1
  156. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
  157. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  158. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_full.segment.rsc +1 -1
  159. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_head.segment.rsc +1 -1
  160. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_index.segment.rsc +1 -1
  161. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/policies.segments/_tree.segment.rsc +1 -1
  162. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.html +1 -1
  163. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.rsc +1 -1
  164. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk/protection/quarantine/__PAGE__.segment.rsc +1 -1
  165. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk/protection/quarantine.segment.rsc +1 -1
  166. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
  167. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  168. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_full.segment.rsc +1 -1
  169. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_head.segment.rsc +1 -1
  170. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_index.segment.rsc +1 -1
  171. package/dashboard/.next/standalone/dashboard/.next/server/app/protection/quarantine.segments/_tree.segment.rsc +1 -1
  172. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.html +2 -2
  173. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.rsc +1 -1
  174. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/!KGRhc2hib2FyZCk/protection/__PAGE__.segment.rsc +1 -1
  175. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/!KGRhc2hib2FyZCk/protection.segment.rsc +1 -1
  176. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  177. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_full.segment.rsc +1 -1
  178. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_head.segment.rsc +1 -1
  179. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_index.segment.rsc +1 -1
  180. package/dashboard/.next/standalone/dashboard/.next/server/app/protection.segments/_tree.segment.rsc +1 -1
  181. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.html +2 -2
  182. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.rsc +1 -1
  183. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/!KGRhc2hib2FyZCk/settings/__PAGE__.segment.rsc +1 -1
  184. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/!KGRhc2hib2FyZCk/settings.segment.rsc +1 -1
  185. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  186. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_full.segment.rsc +1 -1
  187. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  188. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  189. package/dashboard/.next/standalone/dashboard/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  190. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.html +1 -1
  191. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.rsc +1 -1
  192. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk/supply-chain/xray/__PAGE__.segment.rsc +1 -1
  193. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk/supply-chain/xray.segment.rsc +1 -1
  194. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk/supply-chain.segment.rsc +1 -1
  195. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  196. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_full.segment.rsc +1 -1
  197. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_head.segment.rsc +1 -1
  198. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_index.segment.rsc +1 -1
  199. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain/xray.segments/_tree.segment.rsc +1 -1
  200. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.html +1 -1
  201. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.rsc +1 -1
  202. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/!KGRhc2hib2FyZCk/supply-chain/__PAGE__.segment.rsc +1 -1
  203. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/!KGRhc2hib2FyZCk/supply-chain.segment.rsc +1 -1
  204. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  205. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_full.segment.rsc +1 -1
  206. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_head.segment.rsc +1 -1
  207. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_index.segment.rsc +1 -1
  208. package/dashboard/.next/standalone/dashboard/.next/server/app/supply-chain.segments/_tree.segment.rsc +1 -1
  209. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.html +2 -2
  210. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.rsc +1 -1
  211. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/!KGRhc2hib2FyZCk/xray/__PAGE__.segment.rsc +1 -1
  212. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/!KGRhc2hib2FyZCk/xray.segment.rsc +1 -1
  213. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/!KGRhc2hib2FyZCk.segment.rsc +1 -1
  214. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_full.segment.rsc +1 -1
  215. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_head.segment.rsc +1 -1
  216. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_index.segment.rsc +1 -1
  217. package/dashboard/.next/standalone/dashboard/.next/server/app/xray.segments/_tree.segment.rsc +1 -1
  218. package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +2 -2
  219. package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
  220. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
  221. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
  222. package/dist/cli/doctor.d.ts +17 -0
  223. package/dist/cli/doctor.js +44 -0
  224. package/dist/cli/memory.d.ts +23 -0
  225. package/dist/cli/memory.js +101 -0
  226. package/dist/database/init.d.ts +9 -0
  227. package/dist/database/init.js +32 -4
  228. package/dist/database/migrations.js +126 -0
  229. package/dist/memory/consolidate.d.ts +82 -6
  230. package/dist/memory/consolidate.js +287 -117
  231. package/dist/setup/openclaw.d.ts +26 -0
  232. package/dist/setup/openclaw.js +61 -0
  233. package/dist/tools/remember.js +24 -3
  234. package/hooks/openclaw/cortex-memory/handler.ts +223 -171
  235. package/hooks/openclaw/cortex-memory/runtime.mjs +64 -0
  236. package/package.json +1 -1
  237. package/scripts/lib/dedup.mjs +99 -0
  238. package/scripts/lib/openclaw-extract.mjs +129 -0
  239. package/scripts/lib/recall-log.mjs +16 -1
  240. package/scripts/lib/recall-relevance.mjs +191 -0
  241. package/scripts/lib/salience.mjs +8 -3
  242. package/scripts/lib/save-memory.mjs +62 -6
  243. package/scripts/lib/session-context.mjs +30 -0
  244. package/scripts/postinstall.mjs +29 -10
  245. package/scripts/prompt-recall-hook.mjs +118 -15
  246. package/scripts/session-start-hook.mjs +17 -6
  247. /package/dashboard/.next/standalone/dashboard/.next/static/{N9XiRuuRX4eTtiJFa43tY → 0HpUm8SRvm9fnWVO0OBB2}/_buildManifest.js +0 -0
  248. /package/dashboard/.next/standalone/dashboard/.next/static/{N9XiRuuRX4eTtiJFa43tY → 0HpUm8SRvm9fnWVO0OBB2}/_clientMiddlewareManifest.json +0 -0
  249. /package/dashboard/.next/standalone/dashboard/.next/static/{N9XiRuuRX4eTtiJFa43tY → 0HpUm8SRvm9fnWVO0OBB2}/_ssgManifest.js +0 -0
@@ -2,6 +2,7 @@ import { execFile } from "node:child_process";
2
2
  import fs from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
5
6
 
6
7
  export function createOpenClawRuntime({
7
8
  logPrefix = "[shieldcortex]",
@@ -77,6 +78,67 @@ export function createOpenClawRuntime({
77
78
  return resolvedServerCmd;
78
79
  }
79
80
 
81
+ // ==================== CHUNKER WRAPPER RESOLUTION ====================
82
+ //
83
+ // The OpenClaw hook extracts memories with the SAME hardened chunker the
84
+ // Claude-Code side uses, loaded from the resolved local install. We resolve
85
+ // the package root from the server binary (NOT by opening any DB), then
86
+ // dynamic-import the PURE wrapper (no native deps). Persistence still goes
87
+ // through callCortex's mcporter shell-out — never via a native DB handle in
88
+ // this long-lived process.
89
+
90
+ /**
91
+ * Resolve the ShieldCortex package root from the resolved server command.
92
+ * Returns null when there is no resolvable local install (npx fallback).
93
+ * @returns {Promise<string|null>}
94
+ */
95
+ async function resolvePackageRoot() {
96
+ const cmd = await resolveServerCmd();
97
+ if (!cmd || cmd.startsWith("npx")) return null; // no local install
98
+ const real = await fs.realpath(cmd).catch(() => cmd); // .../dist/index.js
99
+ return path.resolve(path.dirname(real), ".."); // -> package root
100
+ }
101
+
102
+ let _openClawExtract = null;
103
+ let _openClawExtractTried = false;
104
+
105
+ /**
106
+ * Dynamic-import the pure chunker wrapper from the resolved package root.
107
+ * Cached after the first attempt. Returns null on any failure (no local
108
+ * install, missing file, import error) — callers must treat null as
109
+ * "skip auto-capture", NOT as a reason to fall back to a legacy high-salience
110
+ * path.
111
+ * @returns {Promise<{ extractSessionMemories: Function, extractKeywordMemory: Function }|null>}
112
+ */
113
+ async function loadOpenClawExtract() {
114
+ if (_openClawExtract) return _openClawExtract;
115
+ if (_openClawExtractTried) return _openClawExtract; // null, don't retry every event
116
+ _openClawExtractTried = true;
117
+
118
+ try {
119
+ const root = await resolvePackageRoot();
120
+ if (!root) return null;
121
+
122
+ const wrapperPath = path.join(root, "scripts", "lib", "openclaw-extract.mjs");
123
+ await fs.access(wrapperPath);
124
+
125
+ // Absolute file URL — required so jiti / the copied hook resolves it
126
+ // regardless of the hook's own on-disk location.
127
+ const mod = await import(pathToFileURL(wrapperPath).href);
128
+ if (typeof mod?.extractSessionMemories !== "function" || typeof mod?.extractKeywordMemory !== "function") {
129
+ return null;
130
+ }
131
+
132
+ _openClawExtract = {
133
+ extractSessionMemories: mod.extractSessionMemories,
134
+ extractKeywordMemory: mod.extractKeywordMemory,
135
+ };
136
+ return _openClawExtract;
137
+ } catch {
138
+ return null;
139
+ }
140
+ }
141
+
80
142
  async function callCortex(tool, args = {}, options = { retries: 0, timeout: 15000 }) {
81
143
  const serverCmd = await resolveServerCmd();
82
144
 
@@ -139,5 +201,7 @@ export function createOpenClawRuntime({
139
201
  isOpenClawAutoMemoryEnabled,
140
202
  loadShieldConfig,
141
203
  resolveServerCmd,
204
+ resolvePackageRoot,
205
+ loadOpenClawExtract,
142
206
  };
143
207
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shieldcortex",
3
- "version": "4.28.1",
3
+ "version": "4.29.0",
4
4
  "description": "Trustworthy memory and security for AI agents. Recall debugging, review queue, OpenClaw session capture, and memory poisoning defence for Claude Code, Codex, OpenClaw, LangChain, and MCP agents.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Pure, synchronous near-duplicate detection for the hook write path.
3
+ *
4
+ * No DB, no native deps, no dist dependency — this lets the hook writer
5
+ * (save-memory.mjs) dedup even in a dev workspace where `dist/` hasn't been
6
+ * built. It is deliberately a .mjs sibling rather than an import of
7
+ * dist/memory/similarity.js so the write path never gains a build dependency.
8
+ *
9
+ * The Jaccard algorithm below MIRRORS src/memory/similarity.ts:tokenize/
10
+ * jaccardSimilarity so the write-path gate and the consolidate/contradiction
11
+ * paths agree on what "similar" means. Keep them in lockstep if either moves.
12
+ *
13
+ * NOTE (future convergence): hooks/openclaw/cortex-memory/handler.ts has its
14
+ * own `inspectNovelty` Jaccard gate. This module is the candidate convergence
15
+ * point for all three, but the handler is intentionally left untouched here.
16
+ */
17
+
18
+ /**
19
+ * Tokenize text into a set of normalized words.
20
+ * Lowercase, strip punctuation to whitespace, split on whitespace, drop words
21
+ * of length <= 2. Mirrors src/memory/similarity.ts:tokenize exactly.
22
+ *
23
+ * @param {string} text
24
+ * @returns {Set<string>}
25
+ */
26
+ export function tokenize(text) {
27
+ return new Set(
28
+ String(text ?? '')
29
+ .toLowerCase()
30
+ .replace(/[^\w\s]/g, ' ') // punctuation -> space
31
+ .split(/\s+/)
32
+ .filter((word) => word.length > 2), // drop very short words (and empties)
33
+ );
34
+ }
35
+
36
+ /**
37
+ * Jaccard similarity between two texts: |A ∩ B| / |A ∪ B|.
38
+ * Mirrors src/memory/similarity.ts:jaccardSimilarity (two empty token sets => 1.0).
39
+ *
40
+ * @param {string} textA
41
+ * @param {string} textB
42
+ * @returns {number} 0..1
43
+ */
44
+ export function jaccardSimilarity(textA, textB) {
45
+ const setA = tokenize(textA);
46
+ const setB = tokenize(textB);
47
+
48
+ if (setA.size === 0 && setB.size === 0) return 1.0;
49
+ if (setA.size === 0 || setB.size === 0) return 0.0;
50
+
51
+ let intersection = 0;
52
+ for (const word of setA) {
53
+ if (setB.has(word)) intersection++;
54
+ }
55
+
56
+ const union = setA.size + setB.size - intersection;
57
+ return intersection / union;
58
+ }
59
+
60
+ /**
61
+ * Decide whether `candidate` is a near-duplicate of `existing`.
62
+ *
63
+ * Two-stage to keep cost down and avoid false merges:
64
+ * 1. Title-Jaccard PRE-GATE — only consider pairs whose titles already
65
+ * overlap (>= titleJaccard). If titles are unrelated we never compute
66
+ * content similarity. This both bounds work and stops two genuinely
67
+ * different notes that happen to reuse a few content words from merging.
68
+ * 2. Combined score — content*0.6 + title*0.4, matching the weighting in
69
+ * src/memory/consolidate.ts (~L603-613). >= combinedThreshold => dup.
70
+ *
71
+ * @param {{title: string, content: string}} candidate — the incoming write
72
+ * @param {{title: string, content: string}} existing — a stored row
73
+ * @param {{titleJaccard: number, combinedThreshold: number}} thresholds
74
+ * @returns {{ duplicate: boolean, combined: number, titleSim: number }}
75
+ */
76
+ export function isNearDuplicate(candidate, existing, { titleJaccard, combinedThreshold }) {
77
+ // Empty-title guard: jaccardSimilarity() (faithful to similarity.ts) returns
78
+ // 1.0 for two empty token sets, but for a dedup PRE-GATE that's a false
79
+ // match — two titles that tokenize to nothing (e.g. "A", "PC", a bare digit)
80
+ // carry no overlap signal and must NOT auto-pass the gate. Require real title
81
+ // tokens on both sides before trusting titleSim.
82
+ const candTitleTokens = tokenize(candidate.title);
83
+ const existTitleTokens = tokenize(existing.title);
84
+ if (candTitleTokens.size === 0 || existTitleTokens.size === 0) {
85
+ return { duplicate: false, combined: 0, titleSim: 0 };
86
+ }
87
+
88
+ const titleSim = jaccardSimilarity(candidate.title, existing.title);
89
+
90
+ // Pre-gate: unrelated titles short-circuit before any content work.
91
+ if (titleSim < titleJaccard) {
92
+ return { duplicate: false, combined: 0, titleSim };
93
+ }
94
+
95
+ const contentSim = jaccardSimilarity(candidate.content, existing.content);
96
+ const combined = contentSim * 0.6 + titleSim * 0.4;
97
+
98
+ return { duplicate: combined >= combinedThreshold, combined, titleSim };
99
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Pure extraction wrapper for the OpenClaw cortex-memory hook.
3
+ *
4
+ * The OpenClaw hook runs inside a LONG-LIVED gateway process. It must NOT
5
+ * touch the native DB path (saveAutoExtractedMemory / initDatabase install
6
+ * global shutdown handlers and open better-sqlite3 — a confirmed crash-loop
7
+ * mechanism). Persistence happens via the existing `callCortex("remember")`
8
+ * shell-out, which runs in a throwaway mcporter subprocess.
9
+ *
10
+ * This module exists so the hook gets the SAME extraction QUALITY as the
11
+ * Claude-Code side (sentence-bounded capture, rejection corpus, deterministic
12
+ * taxonomy, 0.6 salience cap) without importing anything native. It depends
13
+ * ONLY on the pure chunker — string in, plain objects out, no DB, no runtime.
14
+ */
15
+
16
+ import {
17
+ extractMemorableSegments,
18
+ processSegments,
19
+ shouldRejectCandidate,
20
+ EXTRACTOR_TO_CATEGORY,
21
+ EXTRACTOR_TO_PURPOSE,
22
+ } from './extract-memorable-segments.mjs';
23
+
24
+ // Session extraction uses the lighter session-end threshold band. 0.30 is the
25
+ // same dynamic threshold the Claude-Code session-end hook passes; the chunker
26
+ // then applies its category-aware floors and the 0.6 cap.
27
+ const SESSION_DYNAMIC_THRESHOLD = 0.30;
28
+
29
+ // Explicit keyword captures ("remember this: ...") are plain user notes whose
30
+ // content rarely contains a chunker trigger word. When no extractor matches
31
+ // the content, treat it as an explicit important-note (category note,
32
+ // purpose project) — the chunker's own taxonomy for that type.
33
+ const KEYWORD_FALLBACK_EXTRACTOR = 'important-note';
34
+
35
+ /**
36
+ * Extract memories from a full session transcript.
37
+ *
38
+ * Runs the chunker over the conversation text and returns the processed
39
+ * memories — already rejection-filtered, deduped, taxonomy-pinned,
40
+ * threshold-filtered, and salience-capped at 0.6.
41
+ *
42
+ * @param {string} conversationText
43
+ * @returns {Array<{ title: string, content: string, category: string, memoryPurpose: string, tags: string[] }>}
44
+ */
45
+ export function extractSessionMemories(conversationText) {
46
+ if (!conversationText || typeof conversationText !== 'string') return [];
47
+
48
+ const segments = extractMemorableSegments(conversationText);
49
+ if (segments.length === 0) return [];
50
+
51
+ const processed = processSegments(segments, SESSION_DYNAMIC_THRESHOLD, {
52
+ conversationText,
53
+ });
54
+
55
+ return Array.isArray(processed) ? processed : [];
56
+ }
57
+
58
+ /**
59
+ * Derive the chunker extractor type for an explicit keyword capture.
60
+ *
61
+ * Reuses the chunker: if the content itself contains a recognised trigger
62
+ * shape (a decision, a fix, a learning, etc.), adopt that extractor's type so
63
+ * the taxonomy is accurate. Otherwise fall back to an explicit important-note.
64
+ * The longest captured segment wins (most context retained).
65
+ */
66
+ function deriveKeywordExtractorType(content) {
67
+ const segments = extractMemorableSegments(content);
68
+ if (segments.length === 0) return KEYWORD_FALLBACK_EXTRACTOR;
69
+ let best = segments[0];
70
+ for (const seg of segments) {
71
+ if (seg.content.length > best.content.length) best = seg;
72
+ }
73
+ return best.extractorType || KEYWORD_FALLBACK_EXTRACTOR;
74
+ }
75
+
76
+ /**
77
+ * Build a single memory from an EXPLICIT keyword trigger.
78
+ *
79
+ * Explicit user intent ("remember this", "for the record", ...) must never be
80
+ * silently dropped by the salience threshold (design B8). So this path applies
81
+ * ONLY the rejection corpus (to drop true malformations) and bypasses the
82
+ * threshold. It still derives category + memory_purpose from the chunker's
83
+ * deterministic taxonomy — no new classification logic.
84
+ *
85
+ * Classification is driven by the AUTHORITATIVE extractorType passed in from
86
+ * the matched trigger (the trigger phrase carries the intent signal, e.g.
87
+ * "the fix was" → error-fix). When extractorType is absent or unknown we fall
88
+ * back to re-scanning the content with the chunker (the truly-generic case).
89
+ * We never silently collapse a typed trigger to a generic `note`.
90
+ *
91
+ * @param {string} content — the captured content after the trigger
92
+ * @param {string} [extractorType] — authoritative chunker extractor type from
93
+ * the matched trigger (one of EXTRACTOR_TO_CATEGORY's keys). Optional.
94
+ * @returns {Array<{ title: string, content: string, category: string, memoryPurpose: string }>}
95
+ * exactly ONE memory, or [] if the rejection corpus flags it as malformed.
96
+ */
97
+ export function extractKeywordMemory(content, extractorType) {
98
+ if (!content || typeof content !== 'string') return [];
99
+ const trimmed = content.trim();
100
+ if (trimmed.length < 5) return [];
101
+
102
+ // The trigger's extractorType is authoritative when supplied & recognised;
103
+ // otherwise re-scan the content (generic triggers like "remember this" whose
104
+ // text may itself carry a decision/fix/learning shape).
105
+ const resolvedType =
106
+ extractorType && extractorType in EXTRACTOR_TO_CATEGORY
107
+ ? extractorType
108
+ : deriveKeywordExtractorType(trimmed);
109
+
110
+ const candidate = { title: '', content: trimmed.slice(0, 500), extractorType: resolvedType };
111
+
112
+ // Rejection corpus only — drop true malformations (bare imperatives,
113
+ // negation-scope drops, email-body bleed, etc.). NOT the salience threshold.
114
+ if (shouldRejectCandidate(candidate, trimmed).rejected) return [];
115
+
116
+ const category = EXTRACTOR_TO_CATEGORY[resolvedType] ?? 'note';
117
+ const memoryPurpose = EXTRACTOR_TO_PURPOSE[resolvedType] ?? 'project';
118
+
119
+ const title = trimmed.slice(0, 80).replace(/["\n]/g, ' ').trim();
120
+
121
+ return [
122
+ {
123
+ title,
124
+ content: candidate.content,
125
+ category,
126
+ memoryPurpose,
127
+ },
128
+ ];
129
+ }
@@ -98,7 +98,22 @@ function rotate() {
98
98
  * source?: 'fts' | 'category-boost',
99
99
  * effectiveSalience?: number,
100
100
  * injected?: boolean,
101
- * dropReason?: string | null,
101
+ * // Why a candidate was not injected (null = injected). Recognised values:
102
+ * // 'dedupe' — content hash seen in a recent turn
103
+ * // 'outside_top_n' — ranked below the MAX_RESULTS cut
104
+ * // 'not_injected' — early-exit before the dedupe filter ran
105
+ * // 'below_term_coverage' — P4 gate: matched too few distinct query terms
106
+ * // 'below_relevance_floor' — P4 gate: BM25 rank below the relative floor
107
+ * // The two P4 reasons appear in SHADOW mode too (the row is still
108
+ * // injected then) so `shieldcortex inspect last-recall` can show
109
+ * // "considered but below floor" for threshold tuning before enforcement.
110
+ * dropReason?:
111
+ * | 'dedupe'
112
+ * | 'outside_top_n'
113
+ * | 'not_injected'
114
+ * | 'below_term_coverage'
115
+ * | 'below_relevance_floor'
116
+ * | null,
102
117
  * }>,
103
118
  * injectedCount?: number,
104
119
  * finalContextChars?: number,
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Recall relevance gate (P4, B9 design).
3
+ *
4
+ * The per-turn UserPromptSubmit recall hook runs an FTS5 OR-of-terms query.
5
+ * FTS5 happily returns a row that matches just ONE common term out of six —
6
+ * which is how off-topic, high-salience memories ended up injected every turn
7
+ * ("same 5 every turn, none relevant" — EDITH, 2026-05). A relative BM25 floor
8
+ * alone does NOT fix this: a "what is the weather today" query still returns
9
+ * five hits within 35% of the best rank.
10
+ *
11
+ * The load-bearing discriminator is TERM-COVERAGE: how many DISTINCT query
12
+ * terms a row actually matches. A 1-of-6-terms OR-match is noise; a real match
13
+ * covers multiple terms.
14
+ *
15
+ * Two-stage gate:
16
+ * 1. TERM-COVERAGE (primary). Keep iff
17
+ * matchedTerms >= minTermMatches
18
+ * OR (totalQueryTerms <= minTermMatches AND matchedTerms === totalQueryTerms)
19
+ * — i.e. a multi-term match, OR a terse prompt that was matched in full.
20
+ * So a 1-of-6 match drops; a 2-of-2 terse match keeps.
21
+ * 2. RELATIVE BM25 FLOOR (secondary). Among coverage survivors that carry an
22
+ * FTS `rank`, drop rows weaker than `relFactor` of this query's best rank.
23
+ * SQLite bm25 ranks are negative; more negative = more relevant, so the
24
+ * floor is `best * relFactor` (less negative) and we drop `rank > floor`.
25
+ * Rows WITHOUT a `rank` (category-boost path) have no FTS rank and are
26
+ * exempt from the BM25 floor — but are STILL subject to term-coverage.
27
+ *
28
+ * Pure: no DB, no env reads, no mutation of inputs. The caller resolves all
29
+ * options (mirror the pickNumber/env pattern in salience.mjs at the call site).
30
+ */
31
+
32
+ /**
33
+ * Count how many DISTINCT query terms appear in the given text, matched
34
+ * case-insensitively on word-ish boundaries (NOT substring — "schema" must
35
+ * not match inside "schematics"). A term made entirely of non-word characters
36
+ * after lowercasing is skipped.
37
+ *
38
+ * @param {string} text
39
+ * @param {string[]} queryTerms
40
+ * @returns {number}
41
+ */
42
+ function countMatchedTerms(text, queryTerms) {
43
+ const haystack = String(text || '').toLowerCase();
44
+ let matched = 0;
45
+ const seen = new Set();
46
+ for (const raw of queryTerms) {
47
+ const term = String(raw || '').toLowerCase();
48
+ if (!term || seen.has(term)) continue;
49
+ seen.add(term);
50
+ // Word-ish boundary: the term must be bounded by a non-word-char (or
51
+ // string edge) on both sides. Escape regex metacharacters in the term.
52
+ const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
53
+ const re = new RegExp(`(^|[^\\p{L}\\p{N}_])${escaped}([^\\p{L}\\p{N}_]|$)`, 'u');
54
+ if (re.test(haystack)) matched += 1;
55
+ }
56
+ return matched;
57
+ }
58
+
59
+ /**
60
+ * Apply the term-coverage + relative-BM25 relevance gate to a set of recall
61
+ * candidate rows.
62
+ *
63
+ * @param {Array<{ id?: any, title?: string, content?: string, rank?: number }>} rows
64
+ * @param {{
65
+ * queryTerms: string[],
66
+ * minTermMatches?: number,
67
+ * relFactor?: number,
68
+ * maxBm25?: number | null,
69
+ * }} opts
70
+ * @returns {{ kept: any[], dropped: Array<{ row: any, reason: 'below_term_coverage' | 'below_relevance_floor' }> }}
71
+ */
72
+ export function filterByRelevance(rows, opts = {}) {
73
+ const list = Array.isArray(rows) ? rows : [];
74
+ const queryTerms = Array.isArray(opts.queryTerms) ? opts.queryTerms : [];
75
+ const minTermMatches =
76
+ typeof opts.minTermMatches === 'number' && Number.isFinite(opts.minTermMatches)
77
+ ? opts.minTermMatches
78
+ : 2;
79
+ const relFactor =
80
+ typeof opts.relFactor === 'number' && Number.isFinite(opts.relFactor) ? opts.relFactor : 0.35;
81
+ // Absolute BM25 floor is OPT-IN: null/undefined (the default) means "no
82
+ // absolute dreg cut". It has no safe cross-corpus default — on a small/new
83
+ // FTS index real bm25 ranks are tiny (~-1e-6), so an absolute floor like
84
+ // -0.5 would drop even a perfect full-coverage match the moment an operator
85
+ // turns on enforce mode. Only honour it when an explicit numeric value is
86
+ // passed; the relative floor + term-coverage do the gating otherwise.
87
+ const maxBm25 =
88
+ typeof opts.maxBm25 === 'number' && Number.isFinite(opts.maxBm25) ? opts.maxBm25 : null;
89
+
90
+ // Distinct, non-empty query terms (lowercased) — defines totalQueryTerms.
91
+ const distinctTerms = [];
92
+ const seenTerm = new Set();
93
+ for (const raw of queryTerms) {
94
+ const t = String(raw || '').toLowerCase();
95
+ if (t && !seenTerm.has(t)) {
96
+ seenTerm.add(t);
97
+ distinctTerms.push(t);
98
+ }
99
+ }
100
+ const totalQueryTerms = distinctTerms.length;
101
+
102
+ // With no usable query terms there is nothing to gate on — keep everything
103
+ // (defensive; the hook short-circuits before calling us in that case).
104
+ if (totalQueryTerms === 0) {
105
+ return { kept: [...list], dropped: [] };
106
+ }
107
+
108
+ const dropped = [];
109
+
110
+ // ── Stage 1: term coverage ────────────────────────────────────────────
111
+ const coverageSurvivors = [];
112
+ for (const row of list) {
113
+ const text = `${row && row.title ? row.title : ''} ${row && row.content ? row.content : ''}`;
114
+ const matchedTerms = countMatchedTerms(text, distinctTerms);
115
+ const multiTerm = matchedTerms >= minTermMatches;
116
+ const terseFullMatch = totalQueryTerms <= minTermMatches && matchedTerms === totalQueryTerms;
117
+ if (multiTerm || terseFullMatch) {
118
+ coverageSurvivors.push(row);
119
+ } else {
120
+ dropped.push({ row, reason: 'below_term_coverage' });
121
+ }
122
+ }
123
+
124
+ // ── Stage 2: relative BM25 floor (only over rows carrying an FTS rank) ──
125
+ const ranked = coverageSurvivors.filter(
126
+ (r) => typeof r.rank === 'number' && Number.isFinite(r.rank),
127
+ );
128
+ // best = most-negative (most relevant) rank among survivors.
129
+ let best = null;
130
+ for (const r of ranked) {
131
+ if (best === null || r.rank < best) best = r.rank;
132
+ }
133
+ // Relative floor: drop ranked rows weaker (greater, i.e. less negative) than
134
+ // best * relFactor. Only meaningful when best is negative (normal BM25).
135
+ const relativeFloor = best !== null && best < 0 ? best * relFactor : null;
136
+
137
+ const kept = [];
138
+ for (const row of coverageSurvivors) {
139
+ const hasRank = typeof row.rank === 'number' && Number.isFinite(row.rank);
140
+ if (hasRank) {
141
+ // Absolute dreg cut: a rank weaker than maxBm25 is noise regardless of
142
+ // the relative floor (guards a query where even the best hit is weak).
143
+ if (maxBm25 !== null && row.rank > maxBm25) {
144
+ dropped.push({ row, reason: 'below_relevance_floor' });
145
+ continue;
146
+ }
147
+ if (relativeFloor !== null && row.rank > relativeFloor) {
148
+ dropped.push({ row, reason: 'below_relevance_floor' });
149
+ continue;
150
+ }
151
+ }
152
+ // No rank (category-boost) → exempt from the BM25 floor, already passed
153
+ // term coverage. Or a ranked row at/under the floor.
154
+ kept.push(row);
155
+ }
156
+
157
+ return { kept, dropped };
158
+ }
159
+
160
+ /**
161
+ * Extract the distinct FTS query terms from a raw prompt, using the SAME
162
+ * normalisation the hook's escapeFts5 applies before the OR-join (strip FTS5
163
+ * operators, drop boolean keywords, split on whitespace, keep words longer
164
+ * than two chars, cap at six). Exposed here so the relevance gate and the FTS
165
+ * query agree on exactly which terms count.
166
+ *
167
+ * @param {string} query
168
+ * @returns {string[]} up to 6 distinct lowercased terms
169
+ */
170
+ export function extractQueryTerms(query) {
171
+ const words = String(query || '')
172
+ .replace(/[*(){}[\]<>~^"]/g, ' ')
173
+ .replace(/\b(AND|OR|NOT|NEAR)\b/gi, '')
174
+ .split(/\s+/)
175
+ .filter((w) => w.length > 2);
176
+ // Dedup BEFORE the cap so repeated early terms don't crowd out distinct
177
+ // later ones (e.g. "drizzle drizzle drizzle migration migration rollback
178
+ // schema postgres journal" → up to 6 DISTINCT terms, not 3). First
179
+ // occurrence wins (order preserved); the slice then keeps the first 6.
180
+ const out = [];
181
+ const seen = new Set();
182
+ for (const w of words) {
183
+ const lw = w.toLowerCase();
184
+ if (!seen.has(lw)) {
185
+ seen.add(lw);
186
+ out.push(lw);
187
+ }
188
+ if (out.length >= 6) break;
189
+ }
190
+ return out;
191
+ }
@@ -11,7 +11,7 @@
11
11
  * effective = base × recency × access × pin × downvote_penalty
12
12
  *
13
13
  * recency = exp(-Δt_days / halfLifeDays) // decay
14
- * access = log(1 + access_count) / log(1 + accessNorm)
14
+ * access = accessFloor + (1 - accessFloor) × log(1 + access_count) / log(1 + accessNorm)
15
15
  * pin = pinned ? pinBoost : 1
16
16
  * downvote_penalty = max(0.1, 1 - downvoteDecay × downvote_count)
17
17
  *
@@ -30,6 +30,7 @@
30
30
  * @param {{
31
31
  * halfLifeDays?: number,
32
32
  * accessNorm?: number,
33
+ * accessFloor?: number,
33
34
  * pinBoost?: number,
34
35
  * downvoteDecay?: number,
35
36
  * now?: number,
@@ -52,6 +53,7 @@ export function computeEffectiveSalience(memory, opts = {}) {
52
53
  const accessNorm = pickNumber(opts.accessNorm, 'SHIELDCORTEX_SALIENCE_ACCESS_NORM', 10);
53
54
  const pinBoost = pickNumber(opts.pinBoost, 'SHIELDCORTEX_SALIENCE_PIN_BOOST', 1.5);
54
55
  const downvoteDecay = pickNumber(opts.downvoteDecay, 'SHIELDCORTEX_SALIENCE_DOWNVOTE_DECAY', 0.3);
56
+ const accessFloor = pickNumber(opts.accessFloor, 'SHIELDCORTEX_ACCESS_FLOOR', 0.4);
55
57
  const now = opts.now ?? Date.now();
56
58
 
57
59
  const base = typeof memory.salience === 'number' ? memory.salience : 0;
@@ -67,9 +69,12 @@ export function computeEffectiveSalience(memory, opts = {}) {
67
69
  }
68
70
  }
69
71
 
70
- // Access: log-scaled, normalised so access_count=accessNorm produces ~1.0.
72
+ // Access: log-scaled boost, NOT a gate. Floored at accessFloor so a
73
+ // never-accessed memory (access_count=0, ~44% of the live DB) keeps a
74
+ // non-zero multiplier instead of collapsing the whole product to 0.
75
+ // access_count=0 → accessFloor; access_count=accessNorm → ~1.0.
71
76
  const accessCount = Math.max(0, Number(memory.access_count) || 0);
72
- const access = Math.log1p(accessCount) / Math.log1p(accessNorm);
77
+ const access = accessFloor + (1 - accessFloor) * (Math.log1p(accessCount) / Math.log1p(accessNorm));
73
78
 
74
79
  // Pin: SQLite stores boolean as 0/1.
75
80
  const pin = memory.pinned ? pinBoost : 1;
@@ -1,6 +1,32 @@
1
1
  import { randomUUID } from 'crypto';
2
2
  import { dirname, resolve } from 'path';
3
3
  import { fileURLToPath, pathToFileURL } from 'url';
4
+ import { isNearDuplicate } from './dedup.mjs';
5
+
6
+ // Env-tunable dedup thresholds (pickNumber/env pattern — mirrors
7
+ // scripts/prompt-recall-hook.mjs). A typo'd or empty env var falls back to the
8
+ // documented default rather than silently zeroing the gate.
9
+ function pickNumber(envName, fallback) {
10
+ const fromEnv = Number(process.env[envName]);
11
+ if (Number.isFinite(fromEnv) && process.env[envName] !== undefined && process.env[envName] !== '') {
12
+ return fromEnv;
13
+ }
14
+ return fallback;
15
+ }
16
+
17
+ // Title-Jaccard PRE-GATE: only pairs whose titles already overlap this much get
18
+ // a content comparison (bounds cost + avoids merging unrelated notes).
19
+ const DEDUP_TITLE_JACCARD = pickNumber('SHIELDCORTEX_DEDUP_TITLE_JACCARD', 0.6);
20
+ // Combined (content*0.6 + title*0.4) score at/above which the incoming write is
21
+ // dropped as a near-duplicate.
22
+ //
23
+ // DELIBERATE: this write-skip threshold (0.5) is STRICTER than
24
+ // consolidate.ts's merge threshold (0.25). Skipping silently DISCARDS the new
25
+ // write, so a false positive here is data loss — we demand high confidence. A
26
+ // consolidate false-merge only concatenates two existing rows (recoverable),
27
+ // so it can afford to be more aggressive. Do not lower this to match consolidate.
28
+ const DEDUP_COMBINED = pickNumber('SHIELDCORTEX_DEDUP_COMBINED', 0.5);
29
+ const DEDUP_CANDIDATE_LIMIT = 200; // bound the candidate scan per write
4
30
 
5
31
  /**
6
32
  * Insert an auto-extracted memory into the SC database, routed through the
@@ -76,16 +102,18 @@ export async function saveAutoExtractedMemory(db, memory, project, opts = {}) {
76
102
  function insertMemoryRow(db, memory, project, sourceIdentifier) {
77
103
  const timestamp = new Date().toISOString();
78
104
 
79
- // Cross-call dedup: the hook fires repeatedly (per turn, or per salience
80
- // bypass) over overlapping transcript windows, so the same regex match
81
- // tends to surface multiple times across calls. The within-batch dedup in
82
- // processSegments doesn't cover that. A single SELECT by (title, project,
83
- // source_kind) keeps re-extractions from cluttering the store.
105
+ // Cross-call, CROSS-PATH exact-title dedup: the hook fires repeatedly (per
106
+ // turn, or per salience bypass) over overlapping transcript windows, so the
107
+ // same regex match tends to surface multiple times across calls. The
108
+ // within-batch dedup in processSegments doesn't cover that. We match on
109
+ // (title, project) WITHOUT a source_kind filter incoming writes here are
110
+ // always hook, so dropping the filter just means a hook re-extraction of
111
+ // something the user ALREADY saved manually is caught too (the old
112
+ // source_kind='hook' filter let those through).
84
113
  const existing = db.prepare(
85
114
  `SELECT 1 FROM memories
86
115
  WHERE title = ?
87
116
  AND (project IS ? OR (project IS NULL AND ? IS NULL))
88
- AND source_kind = 'hook'
89
117
  LIMIT 1`,
90
118
  ).get(memory.title, project || null, project || null);
91
119
  if (existing) {
@@ -93,6 +121,34 @@ function insertMemoryRow(db, memory, project, sourceIdentifier) {
93
121
  return;
94
122
  }
95
123
 
124
+ // Near-duplicate dedup: exact-title only catches verbatim re-saves. Reworded
125
+ // captures of the same fact ("Fix: X" vs "X fix") have different titles but
126
+ // near-identical content. Scan same-project, same-category, ACTIVE rows
127
+ // (most-recent first, bounded) and skip the write if any is a near-dup. This
128
+ // is also cross-path — a prior manual row can block a hook re-extraction.
129
+ const candidates = db.prepare(
130
+ `SELECT title, content FROM memories
131
+ WHERE (project IS ? OR (project IS NULL AND ? IS NULL))
132
+ AND category IS ?
133
+ AND COALESCE(status, 'active') = 'active'
134
+ ORDER BY created_at DESC
135
+ LIMIT ?`,
136
+ ).all(project || null, project || null, memory.category ?? null, DEDUP_CANDIDATE_LIMIT);
137
+
138
+ for (const candidate of candidates) {
139
+ const { duplicate, combined } = isNearDuplicate(
140
+ { title: memory.title, content: memory.content },
141
+ { title: candidate.title, content: candidate.content },
142
+ { titleJaccard: DEDUP_TITLE_JACCARD, combinedThreshold: DEDUP_COMBINED },
143
+ );
144
+ if (duplicate) {
145
+ process.stderr.write(
146
+ `[shieldcortex save-memory] skipped near-duplicate (combined=${combined.toFixed(2)}): ${memory.title}\n`,
147
+ );
148
+ return;
149
+ }
150
+ }
151
+
96
152
  db.prepare(`
97
153
  INSERT INTO memories (
98
154
  uuid, title, content, type, category, salience, tags, project,