@theihtisham/agent-shadow-brain 7.1.3 → 7.1.4

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 (254) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +125 -90
  3. package/dist/adapters/index.d.ts +1 -1
  4. package/dist/adapters/index.d.ts.map +1 -1
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/brain/activity-journal.d.ts.map +1 -1
  7. package/dist/brain/activity-journal.js +2 -1
  8. package/dist/brain/activity-journal.js.map +1 -1
  9. package/dist/brain/auto-update.js +1 -1
  10. package/dist/brain/brain-portability.js +1 -1
  11. package/dist/brain/client-config.d.ts +19 -0
  12. package/dist/brain/client-config.d.ts.map +1 -0
  13. package/dist/brain/client-config.js +40 -0
  14. package/dist/brain/client-config.js.map +1 -0
  15. package/dist/brain/client-integrations.d.ts +2 -0
  16. package/dist/brain/client-integrations.d.ts.map +1 -1
  17. package/dist/brain/client-integrations.js +8 -27
  18. package/dist/brain/client-integrations.js.map +1 -1
  19. package/dist/brain/context-assembly.d.ts +10 -2
  20. package/dist/brain/context-assembly.d.ts.map +1 -1
  21. package/dist/brain/context-assembly.js +238 -14
  22. package/dist/brain/context-assembly.js.map +1 -1
  23. package/dist/brain/context-request.d.ts +4 -0
  24. package/dist/brain/context-request.d.ts.map +1 -0
  25. package/dist/brain/context-request.js +22 -0
  26. package/dist/brain/context-request.js.map +1 -0
  27. package/dist/brain/context-retrieval.d.ts +88 -0
  28. package/dist/brain/context-retrieval.d.ts.map +1 -0
  29. package/dist/brain/context-retrieval.js +300 -0
  30. package/dist/brain/context-retrieval.js.map +1 -0
  31. package/dist/brain/feature-settings.d.ts.map +1 -1
  32. package/dist/brain/feature-settings.js +2 -1
  33. package/dist/brain/feature-settings.js.map +1 -1
  34. package/dist/brain/file-identity.d.ts +4 -0
  35. package/dist/brain/file-identity.d.ts.map +1 -0
  36. package/dist/brain/file-identity.js +27 -0
  37. package/dist/brain/file-identity.js.map +1 -0
  38. package/dist/brain/global-brain.d.ts +11 -1
  39. package/dist/brain/global-brain.d.ts.map +1 -1
  40. package/dist/brain/global-brain.js +58 -6
  41. package/dist/brain/global-brain.js.map +1 -1
  42. package/dist/brain/mcp-server.d.ts +9 -0
  43. package/dist/brain/mcp-server.d.ts.map +1 -1
  44. package/dist/brain/mcp-server.js +237 -53
  45. package/dist/brain/mcp-server.js.map +1 -1
  46. package/dist/brain/mcp-stdio.d.ts.map +1 -1
  47. package/dist/brain/mcp-stdio.js +10 -4
  48. package/dist/brain/mcp-stdio.js.map +1 -1
  49. package/dist/brain/memory-annotations.d.ts.map +1 -1
  50. package/dist/brain/memory-annotations.js +2 -1
  51. package/dist/brain/memory-annotations.js.map +1 -1
  52. package/dist/brain/memory-inspection.d.ts +72 -0
  53. package/dist/brain/memory-inspection.d.ts.map +1 -0
  54. package/dist/brain/memory-inspection.js +131 -0
  55. package/dist/brain/memory-inspection.js.map +1 -0
  56. package/dist/brain/memory-mcp-content.d.ts +44 -0
  57. package/dist/brain/memory-mcp-content.d.ts.map +1 -0
  58. package/dist/brain/memory-mcp-content.js +91 -0
  59. package/dist/brain/memory-mcp-content.js.map +1 -0
  60. package/dist/brain/orchestrator.js +1 -1
  61. package/dist/brain/project-feedback.d.ts +63 -0
  62. package/dist/brain/project-feedback.d.ts.map +1 -0
  63. package/dist/brain/project-feedback.js +289 -0
  64. package/dist/brain/project-feedback.js.map +1 -0
  65. package/dist/brain/project-hub-contract.d.ts +326 -0
  66. package/dist/brain/project-hub-contract.d.ts.map +1 -0
  67. package/dist/brain/project-hub-contract.js +56 -0
  68. package/dist/brain/project-hub-contract.js.map +1 -0
  69. package/dist/brain/project-hub-mcp.d.ts +168 -0
  70. package/dist/brain/project-hub-mcp.d.ts.map +1 -0
  71. package/dist/brain/project-hub-mcp.js +41 -0
  72. package/dist/brain/project-hub-mcp.js.map +1 -0
  73. package/dist/brain/project-hub-pdf-worker.d.ts +13 -0
  74. package/dist/brain/project-hub-pdf-worker.d.ts.map +1 -0
  75. package/dist/brain/project-hub-pdf-worker.js +84 -0
  76. package/dist/brain/project-hub-pdf-worker.js.map +1 -0
  77. package/dist/brain/project-hub-store.d.ts +38 -0
  78. package/dist/brain/project-hub-store.d.ts.map +1 -0
  79. package/dist/brain/project-hub-store.js +189 -0
  80. package/dist/brain/project-hub-store.js.map +1 -0
  81. package/dist/brain/project-hub.d.ts +127 -0
  82. package/dist/brain/project-hub.d.ts.map +1 -0
  83. package/dist/brain/project-hub.js +381 -0
  84. package/dist/brain/project-hub.js.map +1 -0
  85. package/dist/brain/project-kit-inspection.d.ts +28 -0
  86. package/dist/brain/project-kit-inspection.d.ts.map +1 -0
  87. package/dist/brain/project-kit-inspection.js +200 -0
  88. package/dist/brain/project-kit-inspection.js.map +1 -0
  89. package/dist/brain/project-kit-paths.d.ts +13 -0
  90. package/dist/brain/project-kit-paths.d.ts.map +1 -0
  91. package/dist/brain/project-kit-paths.js +141 -0
  92. package/dist/brain/project-kit-paths.js.map +1 -0
  93. package/dist/brain/project-kit.d.ts +41 -0
  94. package/dist/brain/project-kit.d.ts.map +1 -0
  95. package/dist/brain/project-kit.js +255 -0
  96. package/dist/brain/project-kit.js.map +1 -0
  97. package/dist/brain/project-memory.d.ts +38 -0
  98. package/dist/brain/project-memory.d.ts.map +1 -0
  99. package/dist/brain/project-memory.js +76 -0
  100. package/dist/brain/project-memory.js.map +1 -0
  101. package/dist/brain/subconscious.js +2 -2
  102. package/dist/brain/subconscious.js.map +1 -1
  103. package/dist/brain/workflow-skills.d.ts.map +1 -1
  104. package/dist/brain/workflow-skills.js +3 -2
  105. package/dist/brain/workflow-skills.js.map +1 -1
  106. package/dist/cli.js +22 -3
  107. package/dist/cli.js.map +1 -1
  108. package/dist/context-cli.d.ts.map +1 -1
  109. package/dist/context-cli.js +39 -5
  110. package/dist/context-cli.js.map +1 -1
  111. package/dist/dashboard/brain-hub-actions.d.ts +2 -0
  112. package/dist/dashboard/brain-hub-actions.d.ts.map +1 -0
  113. package/dist/dashboard/brain-hub-actions.js +137 -0
  114. package/dist/dashboard/brain-hub-actions.js.map +1 -0
  115. package/dist/dashboard/brain-hub-client.d.ts +2 -0
  116. package/dist/dashboard/brain-hub-client.d.ts.map +1 -0
  117. package/dist/dashboard/brain-hub-client.js +335 -0
  118. package/dist/dashboard/brain-hub-client.js.map +1 -0
  119. package/dist/dashboard/brain-hub-graph.d.ts +2 -0
  120. package/dist/dashboard/brain-hub-graph.d.ts.map +1 -0
  121. package/dist/dashboard/brain-hub-graph.js +176 -0
  122. package/dist/dashboard/brain-hub-graph.js.map +1 -0
  123. package/dist/dashboard/brain-hub-html.d.ts +2 -0
  124. package/dist/dashboard/brain-hub-html.d.ts.map +1 -0
  125. package/dist/dashboard/brain-hub-html.js +30 -0
  126. package/dist/dashboard/brain-hub-html.js.map +1 -0
  127. package/dist/dashboard/brain-hub-project-views.d.ts +2 -0
  128. package/dist/dashboard/brain-hub-project-views.d.ts.map +1 -0
  129. package/dist/dashboard/brain-hub-project-views.js +79 -0
  130. package/dist/dashboard/brain-hub-project-views.js.map +1 -0
  131. package/dist/dashboard/brain-hub-views.d.ts +2 -0
  132. package/dist/dashboard/brain-hub-views.d.ts.map +1 -0
  133. package/dist/dashboard/brain-hub-views.js +150 -0
  134. package/dist/dashboard/brain-hub-views.js.map +1 -0
  135. package/dist/dashboard/demo-workspace.d.ts +11 -0
  136. package/dist/dashboard/demo-workspace.d.ts.map +1 -0
  137. package/dist/dashboard/demo-workspace.js +178 -0
  138. package/dist/dashboard/demo-workspace.js.map +1 -0
  139. package/dist/dashboard/live-workspace-html.d.ts.map +1 -1
  140. package/dist/dashboard/live-workspace-html.js +177 -28
  141. package/dist/dashboard/live-workspace-html.js.map +1 -1
  142. package/dist/dashboard/project-feedback-html.d.ts +5 -0
  143. package/dist/dashboard/project-feedback-html.d.ts.map +1 -0
  144. package/dist/dashboard/project-feedback-html.js +216 -0
  145. package/dist/dashboard/project-feedback-html.js.map +1 -0
  146. package/dist/dashboard/project-hub-api.d.ts +5 -0
  147. package/dist/dashboard/project-hub-api.d.ts.map +1 -0
  148. package/dist/dashboard/project-hub-api.js +121 -0
  149. package/dist/dashboard/project-hub-api.js.map +1 -0
  150. package/dist/dashboard/project-kit-html.d.ts +4 -0
  151. package/dist/dashboard/project-kit-html.d.ts.map +1 -0
  152. package/dist/dashboard/project-kit-html.js +359 -0
  153. package/dist/dashboard/project-kit-html.js.map +1 -0
  154. package/dist/dashboard/project-workspace.d.ts +16 -0
  155. package/dist/dashboard/project-workspace.d.ts.map +1 -0
  156. package/dist/dashboard/project-workspace.js +106 -0
  157. package/dist/dashboard/project-workspace.js.map +1 -0
  158. package/dist/dashboard/server.d.ts +1 -0
  159. package/dist/dashboard/server.d.ts.map +1 -1
  160. package/dist/dashboard/server.js +15 -4
  161. package/dist/dashboard/server.js.map +1 -1
  162. package/dist/dashboard/v6-dashboard-html.d.ts +1 -1
  163. package/dist/dashboard/v6-dashboard-html.d.ts.map +1 -1
  164. package/dist/dashboard/v6-dashboard-html.js +27 -27
  165. package/dist/dashboard/workspace-api.d.ts +6 -0
  166. package/dist/dashboard/workspace-api.d.ts.map +1 -1
  167. package/dist/dashboard/workspace-api.js +154 -34
  168. package/dist/dashboard/workspace-api.js.map +1 -1
  169. package/dist/dashboard/workspace-html.d.ts.map +1 -1
  170. package/dist/dashboard/workspace-html.js +487 -81
  171. package/dist/dashboard/workspace-html.js.map +1 -1
  172. package/dist/dashboard/workspace-status.d.ts +50 -0
  173. package/dist/dashboard/workspace-status.d.ts.map +1 -0
  174. package/dist/dashboard/workspace-status.js +22 -0
  175. package/dist/dashboard/workspace-status.js.map +1 -0
  176. package/dist/index.d.ts +10 -0
  177. package/dist/index.d.ts.map +1 -1
  178. package/dist/index.js +7 -0
  179. package/dist/index.js.map +1 -1
  180. package/dist/integration-cli.d.ts.map +1 -1
  181. package/dist/integration-cli.js +6 -4
  182. package/dist/integration-cli.js.map +1 -1
  183. package/dist/project-cli.d.ts +3 -0
  184. package/dist/project-cli.d.ts.map +1 -0
  185. package/dist/project-cli.js +100 -0
  186. package/dist/project-cli.js.map +1 -0
  187. package/dist/types.d.ts +1 -1
  188. package/dist/types.d.ts.map +1 -1
  189. package/docs/COMPARISON.md +22 -0
  190. package/docs/COMPONENTS.md +66 -0
  191. package/docs/CONTEXT_LAB.md +79 -0
  192. package/docs/FEATURES_AND_LIMITS.md +24 -0
  193. package/docs/INTEGRATIONS.md +68 -0
  194. package/docs/LEARNING.md +46 -0
  195. package/docs/PROJECT_KIT.md +93 -0
  196. package/docs/QUICKSTART.md +44 -0
  197. package/docs/README.md +33 -0
  198. package/docs/ROADMAP-v7.md +147 -0
  199. package/docs/SHARING.md +13 -0
  200. package/docs/SHOWCASE.md +87 -0
  201. package/docs/STATS.md +21 -0
  202. package/docs/TROUBLESHOOTING.md +42 -0
  203. package/docs/UPGRADE_PLAN_v7.0.0.md +203 -0
  204. package/docs/VIRAL-PLAYBOOK.md +259 -0
  205. package/docs/VISUAL_GUIDE_v7.1.4.md +70 -0
  206. package/docs/WORKFLOW_INTEGRATION.md +19 -1
  207. package/docs/WORKSPACE.md +17 -91
  208. package/docs/assets/7.1.4-collaboration-design-preview.png +0 -0
  209. package/docs/assets/7.1.4-comparison.png +0 -0
  210. package/docs/assets/7.1.4-context-lab.png +0 -0
  211. package/docs/assets/7.1.4-control-room-dense-preview.png +0 -0
  212. package/docs/assets/7.1.4-control-room-focus-preview.png +0 -0
  213. package/docs/assets/7.1.4-features.png +0 -0
  214. package/docs/assets/7.1.4-graph.png +0 -0
  215. package/docs/assets/7.1.4-hub-control-room.png +0 -0
  216. package/docs/assets/7.1.4-hub-empty.png +0 -0
  217. package/docs/assets/7.1.4-hub-knowledge.png +0 -0
  218. package/docs/assets/7.1.4-hub-learning.png +0 -0
  219. package/docs/assets/7.1.4-hub-mobile.png +0 -0
  220. package/docs/assets/7.1.4-hub-public-docs-source.png +0 -0
  221. package/docs/assets/7.1.4-hub-public-docs.gif +0 -0
  222. package/docs/assets/7.1.4-hub-public-docs.png +0 -0
  223. package/docs/assets/7.1.4-hub-setup.png +0 -0
  224. package/docs/assets/7.1.4-hub-walkthrough.gif +0 -0
  225. package/docs/assets/7.1.4-integrations.png +0 -0
  226. package/docs/assets/7.1.4-knowledge-design-preview.png +0 -0
  227. package/docs/assets/7.1.4-learning-design-preview.png +0 -0
  228. package/docs/assets/7.1.4-learning.png +0 -0
  229. package/docs/assets/7.1.4-live.png +0 -0
  230. package/docs/assets/7.1.4-memory-review.png +0 -0
  231. package/docs/assets/7.1.4-mobile.png +0 -0
  232. package/docs/assets/7.1.4-models-design-preview.png +0 -0
  233. package/docs/assets/7.1.4-outcome-ranking.png +0 -0
  234. package/docs/assets/7.1.4-overview.png +0 -0
  235. package/docs/assets/7.1.4-project-kit.png +0 -0
  236. package/docs/assets/7.1.4-project-skill-preview.png +0 -0
  237. package/docs/assets/7.1.4-replay.png +0 -0
  238. package/docs/assets/7.1.4-setup-design-preview.png +0 -0
  239. package/docs/assets/7.1.4-usage-design-preview.png +0 -0
  240. package/docs/assets/context-v7.1.3.png +0 -0
  241. package/docs/assets/features-v7.1.3.png +0 -0
  242. package/docs/assets/graph-v7.1.3.png +0 -0
  243. package/docs/assets/integrations-v7.1.3.png +0 -0
  244. package/docs/assets/live-v7.1.4.gif +0 -0
  245. package/docs/evaluation/CONTEXT_EVALUATION_v7.1.4.json +1818 -0
  246. package/docs/evaluation/CONTEXT_EVALUATION_v7.1.4.md +36 -0
  247. package/docs/launch/COMPONENT_MAP_v7.1.4.md +307 -0
  248. package/docs/launch/PROJECT_KIT_PLAN_v7.1.4.md +110 -0
  249. package/docs/launch/PROJECT_LEARNING_PLAN_v7.1.4.md +63 -0
  250. package/docs/launch/RELEASE_NOTES_v7.1.4.md +30 -0
  251. package/docs/launch/RESEARCH_AND_PLAN_v7.1.4.md +255 -0
  252. package/package.json +25 -7
  253. package/skills/shadow-research/SKILL.md +2 -0
  254. package/skills/shadow-workflow/SKILL.md +6 -0
@@ -1,4 +1,5 @@
1
1
  // Dependency-free local workspace. The advanced dashboard is served separately.
2
+ import { PROJECT_KIT_MARKUP, PROJECT_KIT_SCRIPT, PROJECT_KIT_STYLE } from './project-kit-html.js';
2
3
  export const WORKSPACE_HTML = String.raw `<!doctype html>
3
4
  <html lang="en" data-theme="light" data-motion="system">
4
5
  <head>
@@ -300,8 +301,105 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
300
301
  .save-status { padding: 8px 19px; min-height: 35px; border-top: 1px solid var(--line); }
301
302
  .save-status[data-error="true"] { color: var(--bad); }
302
303
  .notice { margin: 0 0 16px; }
303
- .entered { animation: enter var(--ease) both; }
304
- @keyframes enter { from { opacity: .7; transform: translateY(3px); } to { opacity: 1; transform: translateY(0); } }
304
+ :root { --signal: #8fe5d3; --instrument: #142d3b; --instrument-deep: #102333; --instrument-ink: #f3f9fd; --instrument-muted: #b6cbd5; --instrument-line: #395663; }
305
+ .rail { gap: 35px; }
306
+ .nav-link[aria-current="page"] { box-shadow: inset 0 0 0 1px #426371; }
307
+ .rail-live { margin-top: 11px; }
308
+ .rail-live .icon { color: var(--rail-highlight); }
309
+ .page-head h1 { font-size: 2.27rem; }
310
+ .page-head .head-actions { align-items: end; }
311
+ .page-head .head-actions .button { align-self: end; }
312
+ .demo-notice { display: none; }
313
+ html[data-demo="true"] .demo-notice { display: flex; gap: 7px 10px; flex-wrap: wrap; padding: 8px 12px; margin-bottom: 16px; background: var(--amber-bg); color: var(--amber); border-radius: 7px; font-size: .83rem; }
314
+ html[data-demo="true"] .advanced-link { display: none; }
315
+ .overview-summary { display: grid; grid-template-columns: minmax(0,1.1fr) minmax(0,1fr); color: var(--instrument-ink); background: var(--instrument); border-radius: 12px; overflow: hidden; margin-bottom: 17px; }
316
+ .overview-summary .summary { grid-template-columns: repeat(3,minmax(0,1fr)); background: var(--instrument-deep); color: var(--instrument-ink); border: 0; border-radius: 0; margin: 0; }
317
+ .overview-summary .summary-item { flex-direction: column; align-items: start; justify-content: center; gap: 11px; padding: 20px 16px; }
318
+ .overview-summary .summary-item + .summary-item { border-color: var(--instrument-line); }
319
+ .overview-summary .summary dt { color: var(--instrument-muted); font-size: .83rem; }
320
+ .overview-summary .summary dd { font-size: 1.72rem; }
321
+ .overview-summary .tile-icon { color: var(--signal); background: #234550; }
322
+ .overview-lead { display: flex; align-items: center; gap: 19px; padding: 21px; min-width: 0; }
323
+ .overview-lead h2 { font-size: 1.28rem; }
324
+ .overview-lead p { color: var(--instrument-muted); margin-top: 5px; }
325
+ .overview-lead .button { color: var(--signal); border-color: var(--instrument-line); margin-top: 12px; }
326
+ .overview-lead .button:hover { background: #254350; }
327
+ .coverage-ring { flex: none; display: grid; place-items: center; width: 98px; aspect-ratio: 1; border-radius: 50%; background: conic-gradient(var(--signal) var(--coverage), #3b5665 0); }
328
+ .coverage-ring span { display: grid; place-items: center; width: 80px; aspect-ratio: 1; border-radius: 50%; background: var(--instrument); font-size: 1.46rem; font-weight: 720; font-variant-numeric: tabular-nums; }
329
+ .overview-columns { grid-template-columns: minmax(0,1.33fr) minmax(0,1fr); }
330
+ .overview-activity .panel-head { background: var(--inset); }
331
+ .attention-list { max-height: 355px; overflow: auto; }
332
+ .attention-row { display: grid; grid-template-columns: 30px minmax(0,1fr); gap: 11px; align-items: start; padding: 12px 19px; }
333
+ .attention-row .tile-icon { width: 30px; height: 30px; background: var(--amber-bg); color: var(--amber); }
334
+ .attention-row .tags { margin-top: 5px; }
335
+ .graph-stage {
336
+ --canvas: #142534; --surface: #203546; --ink: #f0f7fc; --muted: #bed0db; --line: #4b6679;
337
+ --project: #a3e8d5; --project-bg: #234651; --agent: #d3c5ff; --agent-bg: #3a3e5e; --memory: #c2daf2; --memory-bg: #344d64;
338
+ --graph-line: #8ba9bd; --graph-project: #82b8ab; --graph-agent: #aa9ed0; --graph-dot: #40566a; --teal: #9ce9d8; --bad: #ff9eab;
339
+ height: 520px; color: var(--ink); color-scheme: dark;
340
+ }
341
+ .graph-stage .button:hover { background: #30485a; }
342
+ .graph-stage .cluster-area { fill-opacity: .47; stroke-opacity: .34; }
343
+ .graph-node { transition: opacity var(--ease); }
344
+ .graph-node.selected-memory .node-surface { stroke: var(--teal); }
345
+ .graph-stage .graph-edge.supports { stroke: var(--teal); }
346
+ .graph-inspector, .graph-list { max-height: 520px; }
347
+ .graph-body[data-lens="true"] .graph-stage { height: 390px; }
348
+ .graph-body[data-lens="true"] :is(.graph-inspector,.graph-list) { max-height: 390px; }
349
+ .graph-lens { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; padding: 8px 14px; border-bottom: 1px solid var(--line); }
350
+ .legend-line.supports { border-color: var(--teal); }
351
+ .legend-line.contradicts { border-color: var(--bad); border-top-style: dashed; }
352
+ .legend-line.supersedes { border-color: var(--teal); border-top-style: dotted; }
353
+ .context-layout { grid-template-columns: minmax(255px,.68fr) minmax(0,1.75fr); }
354
+ .context-config { gap: 16px; }
355
+ .context-config .form-actions .primary { width: 100%; }
356
+ .context-settings-pair { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: 10px; }
357
+ .context-result-head { background: var(--inset); }
358
+ .context-output-head { display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: start; gap: 10px; }
359
+ .context-output-head .form-actions { justify-content: end; }
360
+ .context-metrics { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); border-block: 1px solid var(--line); }
361
+ .context-metrics > div { padding: 11px 10px; min-width: 0; }
362
+ .context-metrics > div + div { border-left: 1px solid var(--line); }
363
+ .context-metrics dt { font-size: .78rem; color: var(--muted); }
364
+ .context-metrics dd { font-size: 1.21rem; font-weight: 710; overflow-wrap: anywhere; }
365
+ .context-budget-track { height: 6px; border-radius: 4px; background: var(--line); overflow: hidden; margin-top: 5px; }
366
+ .context-budget-track span { display: block; height: 100%; background: var(--teal-fill); border-radius: inherit; }
367
+ .context-section { display: grid; gap: 11px; min-width: 0; }
368
+ .context-section > summary, .candidate > summary { cursor: pointer; font-weight: 680; }
369
+ .context-section > summary { color: var(--teal); }
370
+ .context-section[open] > summary { margin-bottom: 10px; }
371
+ .retrieval-flow { display: grid; grid-template-columns: repeat(auto-fit,minmax(97px,1fr)); gap: 1px; list-style: none; padding: 1px; border-radius: 8px; background: var(--instrument-line); overflow: hidden; }
372
+ .retrieval-flow li { display: grid; grid-template-rows: auto 1fr auto; gap: 5px; min-width: 0; background: var(--instrument-deep); color: var(--instrument-ink); padding: 10px; }
373
+ .retrieval-flow strong { font-size: 1.28rem; font-variant-numeric: tabular-nums; }
374
+ .retrieval-flow span { font-size: .78rem; color: var(--instrument-muted); overflow-wrap: anywhere; }
375
+ .retrieval-flow li:last-child { background: var(--instrument); }
376
+ .retrieval-flow li:last-child strong { color: var(--signal); }
377
+ .retrieval-stage-bar { height: 3px; background: var(--instrument-line); border-radius: 2px; overflow: hidden; }
378
+ .retrieval-stage-bar i { display: block; height: 100%; background: var(--signal); opacity: .74; }
379
+ .candidate-list { max-height: 415px; overflow: auto; border-block: 1px solid var(--line); }
380
+ .candidate { padding: 9px 9px 9px 12px; }
381
+ .candidate > summary { color: var(--ink); }
382
+ .candidate > summary:hover { color: var(--teal); }
383
+ .candidate-title { display: inline-grid; grid-template-columns: auto minmax(0,1fr) auto; align-items: baseline; gap: 7px; width: calc(100% - 20px); vertical-align: top; }
384
+ .candidate-title .rank { color: var(--muted); font: .8rem Consolas,monospace; }
385
+ .candidate-body { display: grid; gap: 7px; padding: 9px 0 4px; }
386
+ .candidate-signals { display: grid; grid-template-columns: repeat(auto-fit,minmax(110px,1fr)); gap: 5px; }
387
+ .candidate-signals div { background: var(--inset); border-radius: 5px; padding: 6px; }
388
+ .candidate-signals dt { color: var(--muted); font-size: .75rem; }
389
+ .candidate-signals dd { font-size: .89rem; font-variant-numeric: tabular-nums; }
390
+ .context-compare { display: grid; gap: 8px; }
391
+ .compare-grid { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); border: 1px solid var(--line); border-radius: 7px; overflow: hidden; }
392
+ .compare-grid > div { padding: 10px; min-width: 0; }
393
+ .compare-grid > div + div { border-left: 1px solid var(--line); }
394
+ .compare-grid ul { padding-left: 16px; }
395
+ .compare-grid h3 { margin-bottom: 5px; }
396
+ .integration-meta { display: flex; flex-wrap: wrap; gap: 7px; padding: 12px 19px; border-bottom: 1px solid var(--line); }
397
+ .integration-config { display: grid; gap: 8px; }
398
+ .integration-config > summary { cursor: pointer; color: var(--teal); font-weight: 650; }
399
+ .integration-config[open] > summary { margin-bottom: 8px; }
400
+ .integration-config .context-preview { margin-block: 7px; max-height: 280px; }
401
+ .setting .settings-description { display: flex; align-items: start; gap: 11px; }
402
+ .setting .tile-icon { width: 32px; height: 32px; }
305
403
  html[data-motion="reduce"] *, html[data-motion="reduce"] *::before, html[data-motion="reduce"] *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
306
404
  @media (prefers-reduced-motion: reduce) {
307
405
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
@@ -328,6 +426,7 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
328
426
  :root { --header: 48px; font-size: 14px; }
329
427
  .shell { display: block; }
330
428
  .rail { z-index: var(--sticky); height: auto; flex-direction: row; align-items: center; gap: 7px; padding: 5px 10px; }
429
+ .rail-live { display: none; }
331
430
  .rail-nav { display: flex; gap: 3px; }
332
431
  .nav-link { padding: 7px 9px; min-height: 35px; }
333
432
  .rail-foot { margin: 0 0 0 auto; padding: 0; border: 0; }
@@ -335,7 +434,7 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
335
434
  .top { padding: 7px 13px; }
336
435
  .top-actions { gap: 8px; }
337
436
  .content { padding: 22px 12px 32px; }
338
- h1 { font-size: 1.68rem; }
437
+ h1, .page-head h1 { font-size: 1.68rem; }
339
438
  .page-head { gap: 8px; margin-bottom: 17px; flex-wrap: wrap; }
340
439
  .summary { margin-bottom: 13px; }
341
440
  .summary-item { gap: 6px; padding: 11px 9px; }
@@ -349,20 +448,36 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
349
448
  .check-row .tag { grid-column: 2; grid-row: 1; }
350
449
  .graph-body { grid-template-columns: minmax(0,1fr); }
351
450
  .graph-stage, .graph-list { height: 365px; }
451
+ .graph-body[data-lens="true"] .graph-stage { height: 310px; }
352
452
  .graph-inspector { border-left: 0; border-top: 1px solid var(--line); max-height: 340px; }
353
453
  .panel-head, .panel-body, .memory-row, .setting { padding-left: 12px; padding-right: 12px; }
354
454
  .project-row, .event { padding-left: 12px; padding-right: 12px; }
355
455
  .list-footer { flex-wrap: wrap; }
356
456
  .graph-mode { margin-left: 0; }
457
+ .overview-summary { grid-template-columns: minmax(0,1fr); }
458
+ .overview-summary .summary-item { padding: 11px 10px; }
459
+ .overview-summary .summary dd { font-size: 1.42rem; }
460
+ .overview-lead { padding: 15px; gap: 12px; }
461
+ .coverage-ring { width: 77px; }
462
+ .coverage-ring span { width: 63px; font-size: 1.16rem; }
463
+ .context-output-head { grid-template-columns: minmax(0,1fr); }
464
+ .context-output-head .form-actions { justify-content: start; }
465
+ #context-result, #attention-heading { scroll-margin-top: 55px; }
466
+ .context-metrics > div { padding: 8px 6px; }
467
+ .context-metrics dd { font-size: 1rem; }
468
+ .retrieval-flow { grid-template-columns: repeat(3,minmax(0,1fr)); }
469
+ .candidate-title { grid-template-columns: auto minmax(0,1fr); }
470
+ .candidate-title .tag { grid-column: 2; justify-self: start; }
357
471
  }
358
472
  @media (max-width: 355px) {
359
473
  .rail { gap: 3px; }
360
- .nav-link { padding-left: 7px; padding-right: 7px; }
474
+ .nav-link { padding-left: 5px; padding-right: 5px; }
361
475
  .crumb > .muted, .crumb .icon { display: none; }
362
476
  .filters { grid-template-columns: repeat(2,minmax(0,1fr)); }
363
477
  .filters .search-field { grid-column: 1 / -1; }
364
478
  .filters .button { grid-row: auto; grid-column: auto; }
365
479
  }
480
+ ${PROJECT_KIT_STYLE}
366
481
  </style>
367
482
  </head>
368
483
  <body>
@@ -376,12 +491,14 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
376
491
  <a class="nav-link" href="#overview" aria-label="Overview" title="Overview" data-view="overview" aria-current="page"><span data-icon="overview"></span><span class="nav-label">Overview</span></a>
377
492
  <a class="nav-link" href="#memories" aria-label="Memories" title="Memories" data-view="memories"><span data-icon="memory"></span><span class="nav-label">Memories</span></a>
378
493
  <a class="nav-link" href="#graph" aria-label="Graph" title="Graph" data-view="graph"><span data-icon="graph"></span><span class="nav-label">Graph</span></a>
379
- <a class="nav-link" href="#context" aria-label="Context" title="Context" data-view="context"><span data-icon="context"></span><span class="nav-label">Context</span></a>
494
+ <a class="nav-link" href="#context" aria-label="Context Lab" title="Context Lab" data-view="context"><span data-icon="context"></span><span class="nav-label">Context Lab</span></a>
495
+ <a class="nav-link" href="#project-kit" aria-label="Project Kit" title="Project Kit" data-view="project-kit"><span data-icon="project"></span><span class="nav-label">Project Kit</span></a>
380
496
  <a class="nav-link" href="#integrations" aria-label="Integrations and health" title="Integrations and health" data-view="integrations"><span data-icon="integrations"></span><span class="nav-label">Integrations</span></a>
381
497
  <a class="nav-link" href="#features" aria-label="Features" title="Features" data-view="features"><span data-icon="settings"></span><span class="nav-label">Features</span></a>
498
+ <a class="nav-link rail-live" href="/live" aria-label="Live operations" title="Live operations"><span data-icon="activity"></span><span class="nav-label">Live operations</span></a>
382
499
  </nav>
383
500
  <div class="rail-foot">
384
- <a class="nav-link" href="/advanced" aria-label="Advanced dashboard" title="Advanced dashboard"><span data-icon="advanced"></span><span class="nav-label">Advanced tools</span><span class="arrow" data-icon="diagonal"></span></a>
501
+ <a class="nav-link advanced-link" href="/advanced" aria-label="Advanced dashboard" title="Advanced dashboard"><span data-icon="advanced"></span><span class="nav-label">Advanced tools</span><span class="arrow" data-icon="diagonal"></span></a>
385
502
  <p class="rail-build" id="rail-version">Local workspace</p>
386
503
  </div>
387
504
  </aside>
@@ -395,11 +512,14 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
395
512
  </div>
396
513
  </header>
397
514
  <main id="main-content" class="content" tabindex="-1">
515
+ <div class="demo-notice"><strong>Synthetic demo</strong><span>Scripted local MCP calls use sample data. Changes affect this temporary demo only.</span></div>
516
+ <div class="project-local-notice"><strong>Project workspace</strong><span>Memory and settings are stored with this project.</span><a href="#project-kit">Open Project Kit</a></div>
398
517
  <div class="notice" id="workspace-notice" role="status" hidden></div>
399
518
  <section id="page-overview" aria-labelledby="title-overview">
400
519
  <div class="page-head">
401
- <div><h1 id="title-overview" tabindex="-1">Overview</h1><p class="muted">See what your agents remember and what they’ve done recently.</p></div>
402
- <a href="#context" class="button primary"><span data-icon="context"></span>Assemble context</a>
520
+ <div><h1 id="title-overview" tabindex="-1">Memory workspace</h1><p class="muted">Review saved knowledge and prepare a brief for your next task.</p></div>
521
+ <div class="form-actions head-actions"><label class="label" for="overview-quality-project">Project<select id="overview-quality-project"><option value="">All projects</option></select></label>
522
+ <a href="#context" id="overview-context" class="button primary"><span data-icon="context"></span>Open Context Lab</a></div>
403
523
  </div>
404
524
  <div id="overview-body" aria-busy="true"></div>
405
525
  </section>
@@ -423,6 +543,7 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
423
543
  <div class="form-actions" id="detail-actions" hidden>
424
544
  <a id="detail-graph" class="button" href="#graph"><span data-icon="graph"></span>View on graph</a>
425
545
  <a id="detail-context" class="button" href="#context"><span data-icon="context"></span>Assemble context</a>
546
+ <a id="detail-outcome" class="button" href="#project-kit" hidden><span data-icon="activity"></span>Record outcome</a>
426
547
  </div>
427
548
  </div>
428
549
  <div id="detail-body" aria-busy="true"></div>
@@ -442,6 +563,7 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
442
563
  </div>
443
564
  <div class="panel" id="graph-panel" aria-busy="true">
444
565
  <div class="panel-head"><h2>Memory relationships</h2><span id="graph-count" class="muted small" role="status"></span></div>
566
+ <div id="graph-lens" class="graph-lens" hidden><p class="small muted" id="graph-lens-label"></p><button class="button" type="button" id="graph-lens-clear">Show full graph</button></div>
445
567
  <div id="graph-status"></div>
446
568
  <div class="graph-body" id="graph-body" hidden>
447
569
  <div class="graph-stage" id="graph-stage">
@@ -459,28 +581,35 @@ export const WORKSPACE_HTML = String.raw `<!doctype html>
459
581
  <div id="graph-legend" class="legend" hidden>
460
582
  <span><i class="legend-glyph project"></i> Project</span><span><i class="legend-glyph agent"></i> Agent</span><span><i class="legend-glyph memory"></i> Memory</span>
461
583
  <span><i class="legend-line project-edge"></i> Belongs to project</span><span><i class="legend-line agent-edge"></i> Recorded by agent</span>
462
- <span><i class="legend-line recorded-edge"></i> Recorded memory link</span>
584
+ <span><i class="legend-line recorded-edge"></i> Related</span><span><i class="legend-line supports"></i> Supports</span>
585
+ <span><i class="legend-line contradicts"></i> Contradicts</span><span><i class="legend-line supersedes"></i> Supersedes</span>
463
586
  </div>
464
587
  </div>
465
588
  </div>
466
589
  </section>
467
590
  <section id="page-context" aria-labelledby="title-context" hidden>
468
- <div class="page-head"><div><h1 id="title-context" tabindex="-1">Context</h1><p class="muted">Build a project brief for a specific task and review what it includes.</p></div></div>
591
+ <div class="page-head"><div><h1 id="title-context" tabindex="-1">Context Lab</h1><p class="muted">Assemble a task brief, inspect its evidence, and compare retrieval profiles.</p></div></div>
469
592
  <div class="split context-layout">
470
593
  <div class="panel">
471
- <div class="panel-head"><h2>Task details</h2></div>
594
+ <div class="panel-head"><h2>Build a brief</h2></div>
472
595
  <form class="panel-body context-config" id="context-form">
473
596
  <label class="label" for="context-project">Project<select id="context-project" required><option value="">Select a project</option></select></label>
474
597
  <label class="label" for="context-task">Task<textarea id="context-task" rows="5" maxlength="4096" required placeholder="Describe what the agent will work on."></textarea></label>
475
- <label class="label" for="context-budget">Estimated token budget<input id="context-budget" type="number" inputmode="numeric" min="0" max="12000" step="1" value="2000" required></label>
598
+ <div class="context-settings-pair">
599
+ <label class="label" for="context-budget">Token budget<input id="context-budget" type="number" inputmode="numeric" min="0" max="12000" step="1" value="2000" required aria-describedby="context-estimate"></label>
600
+ <label class="label" for="context-profile">Retrieval<select id="context-profile"><option value="adaptive">Adaptive</option><option value="legacy">Legacy</option></select></label>
601
+ </div>
602
+ <p class="small muted" id="context-estimate">Token usage is estimated for the assembled brief.</p>
603
+ <label class="inline-check small" id="context-related-row"><input type="checkbox" id="context-related" checked>Expand through recorded supporting and related links</label>
476
604
  <label class="inline-check small" id="context-cross-row" hidden><input type="checkbox" id="context-cross">Include eligible memories from other projects</label>
477
605
  <div id="context-gate"></div>
478
606
  <div class="form-actions"><button class="button primary" id="context-submit" type="submit" disabled><span data-icon="context"></span>Assemble context</button></div>
479
607
  </form>
480
608
  </div>
481
- <div class="panel" id="context-result"><div class="panel-head"><h2>Context preview</h2></div><div id="context-output"></div></div>
609
+ <div class="panel" id="context-result"><div class="panel-head context-result-head"><h2>Brief and evidence</h2><span class="small muted" id="context-result-state" role="status">Ready for a task</span></div><div id="context-output"></div></div>
482
610
  </div>
483
611
  </section>
612
+ ${PROJECT_KIT_MARKUP}
484
613
  <section id="page-integrations" aria-labelledby="title-integrations" hidden>
485
614
  <div class="page-head">
486
615
  <div><h1 id="title-integrations" tabindex="-1">Integrations &amp; health</h1><p class="muted">Check local storage and see which capabilities each agent supports.</p></div>
@@ -534,7 +663,7 @@ const glyphs = {
534
663
  warning: '<path d="m12 3 10 17H2L12 3zm0 6v4m0 3h.01"/>',
535
664
  refresh: '<path d="M19.5 9a8 8 0 1 0 .3 5.5M20 4v5h-5"/>'
536
665
  };
537
- const viewNames = { overview: 'Overview', memories: 'Memories', memory: 'Memory detail', graph: 'Graph', context: 'Context', integrations: 'Integrations', features: 'Features' };
666
+ const viewNames = { overview: 'Overview', memories: 'Memories', memory: 'Memory detail', graph: 'Graph', context: 'Context Lab', 'project-kit': 'Project Kit', integrations: 'Integrations', features: 'Features' };
538
667
  const reviewStates = { active: 'Active', contested: 'Needs review', archived: 'Archived', superseded: 'Superseded' };
539
668
  const linkTypes = { related: 'Related', supports: 'Supports', contradicts: 'Contradicts', supersedes: 'Supersedes' };
540
669
  const definitions = [
@@ -560,10 +689,11 @@ const TIME = new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric'
560
689
  const state = {
561
690
  view: '', route: '', projects: [], overviewLoaded: false, overviewPending: null, overviewScope: '', overviewRequest: 0, overviewRequestScope: '',
562
691
  activity: [], activityEnabled: null, activityPending: false, features: null, featurePending: null, featureSaving: false,
692
+ health: { snapshot: null, project: '', filter: '', request: 0 }, projectKit: null,
563
693
  memory: { query: null, request: 0, controller: null, limit: 20, offset: 0 },
564
694
  detail: { snapshot: null, id: '', project: '', back: '#memories', request: 0, targetRequest: 0, saving: false },
565
- graph: { snapshot: null, project: null, mode: matchMedia('(max-width: 690px)').matches ? 'list' : 'map', selected: '', selectedEdge: '', focusMemory: '', nodes: [], edges: [], layout: null, zoom: 1, panX: 0, panY: 0, request: 0, controller: null, drag: null },
566
- context: { pack: null, pending: false, dirty: false, routeProject: '' },
695
+ graph: { snapshot: null, project: null, mode: matchMedia('(max-width: 690px)').matches ? 'list' : 'map', selected: '', selectedEdge: '', focusMemory: '', lens: '', nodes: [], edges: [], layout: null, zoom: 1, panX: 0, panY: 0, request: 0, controller: null, drag: null },
696
+ context: { pack: null, params: null, comparison: null, comparing: false, pending: false, dirty: false, routeProject: '' },
567
697
  integrations: { snapshot: null, pending: false }
568
698
  };
569
699
 
@@ -664,11 +794,24 @@ function reviewTag(value) {
664
794
 
665
795
  function renderSummary(totals) {
666
796
  return '<dl class="summary">' + [
667
- ['memory', 'Stored memories', totals.memories], ['project', 'Projects', totals.projects], ['agent', 'Contributing agents', totals.agents]
797
+ ['memory', 'Total stored memories', totals.memories], ['project', 'Projects', totals.projects], ['agent', 'Contributing agents', totals.agents]
668
798
  ].map(([image, label, value]) => '<div class="summary-item"><span class="tile-icon">' + icon(image) +
669
799
  '</span><div><dt>' + label + '</dt><dd>' + number(value) + '</dd></div></div>').join('') + '</dl>';
670
800
  }
671
801
 
802
+ function renderOverviewSummary(data) {
803
+ const counts = data.quality || {};
804
+ const total = Number(counts.total) || 0;
805
+ const sources = Number(counts.sourceBacked) || 0;
806
+ const percent = total ? Math.round(Math.min(1, sources / total) * 100) : 0;
807
+ const project = data.scope?.projectId;
808
+ const scope = project ? state.projects.find((item) => item.id === project)?.name || project : 'All projects';
809
+ return '<div class="overview-summary"><div class="overview-lead"><div class="coverage-ring" aria-hidden="true" style="--coverage:' + percent + '%"><span>' +
810
+ (total ? percent + '%' : '—') + '</span></div><div><h2>Recorded source coverage</h2><p class="small">' +
811
+ esc(scope) + ' · ' + number(sources) + ' of ' + number(total) + (data.truncated ? ' sampled memories' : ' memories') +
812
+ '</p><button class="button quiet" type="button" id="attention-jump">Review attention queue ' + icon('chevron', true) + '</button></div></div>' + renderSummary(data.totals || {}) + '</div>';
813
+ }
814
+
672
815
  function renderProjects(projects) {
673
816
  if (!projects.length) return blank('project', 'No projects yet', 'Projects appear after an agent stores a memory with a project scope.');
674
817
  const max = Math.max(1, ...projects.map((project) => Number(project.memories) || 0));
@@ -698,8 +841,6 @@ function renderQuality(data) {
698
841
  const project = data.scope?.projectId || '';
699
842
  const scope = data.truncated ? 'Based on the latest ' + number(data.inventoryLimit) + ' stored memories.' :
700
843
  project ? 'Stored memories in this project.' : 'Stored memories across projects.';
701
- const options = state.projects.map((item) => '<option value="' + esc(item.id) + '"' +
702
- (item.id === project ? ' selected' : '') + '>' + esc(item.name || item.id) + '</option>').join('');
703
844
  const statusCounts = Object.entries(reviewStates).map(([key, label]) => ({ key, label, count: Number(quality[key]) || 0 }));
704
845
  const coverage = quality.total ? Math.max(0, Math.min(100, Number(quality.sourceBacked || 0) / quality.total * 100)) : 0;
705
846
  const statusBars = statusCounts.map(({ key, count }) =>
@@ -707,8 +848,7 @@ function renderQuality(data) {
707
848
  const statusLinks = statusCounts.map(({ key, label, count }) => '<a href="' + esc(routeLink('memories', { project, state: key })) +
708
849
  '"><i aria-hidden="true" class="quality-' + key + '"></i>' + esc(label) + ' <strong>' + number(count) + '</strong></a>').join('');
709
850
  return '<div class="panel"><div class="panel-head"><div><h2>Review and sources</h2><p class="small muted">' + esc(scope) +
710
- '</p></div><label class="label" for="overview-quality-project">Project<select id="overview-quality-project"><option value="">All projects</option>' +
711
- options + '</select></label></div>' + (quality.total ? '<div class="panel-body stack"><div class="stack" style="gap:8px"><h3>Memory status</h3>' +
851
+ '</p></div></div>' + (quality.total ? '<div class="panel-body stack"><div class="stack" style="gap:8px"><h3>Memory status</h3>' +
712
852
  '<div class="quality-track" aria-hidden="true">' + statusBars + '</div><div class="quality-labels">' + statusLinks + '</div></div>' +
713
853
  '<div class="stack" style="gap:8px"><div class="quality-source"><h3>Recorded sources</h3><span class="small muted">' +
714
854
  number(quality.sourceBacked) + ' of ' + number(quality.total) + ' memories</span></div>' +
@@ -717,6 +857,61 @@ function renderQuality(data) {
717
857
  '</div>' : blank('memory', 'No memories in scope', 'Save a memory to see review and source counts here.')) + '</div>';
718
858
  }
719
859
 
860
+ const healthIssues = {
861
+ contested: 'Needs review', contradiction: 'Contradictory link', 'orphaned-link': 'Missing linked memory',
862
+ duplicate: 'Repeated content', 'no-source': 'No recorded source', 'older-memory': 'Older than 90 days'
863
+ };
864
+
865
+ function renderAttention() {
866
+ const target = $('attention-results');
867
+ const store = state.health;
868
+ if (!target || !store.snapshot || store.project !== state.overviewScope) return;
869
+ const data = store.snapshot;
870
+ const counts = data.counts || {};
871
+ const rows = list(data.queue).filter((item) => !store.filter || list(item.issues).some((issue) => issue.kind === store.filter));
872
+ const options = Object.entries(healthIssues).map(([value, label]) => '<option value="' + value + '">' + label + '</option>').join('');
873
+ const controls = $('attention-filter');
874
+ if (!controls.childElementCount) controls.innerHTML = '<option value="">All issues</option>' + options;
875
+ controls.value = store.filter;
876
+ controls.disabled = !list(data.queue).length;
877
+ const scopeNote = (data.coverage?.truncated ? 'Checked the latest ' + number(data.coverage.limit) + ' memories in scope.' : 'Checked ' + number(counts.total) + ' memories in scope.');
878
+ $('attention-scope').textContent = scopeNote;
879
+ const warnings = list(data.warnings).map((warning) => '<p class="small muted panel-body">' + esc(warning) + '</p>').join('');
880
+ const count = '<p class="small muted panel-body">Showing ' + number(rows.length) + ' of ' + number(data.queueTotal) + ' flagged memories' +
881
+ (store.filter ? ' · filter applied to loaded items' : '') + '.</p>';
882
+ const items = rows.map((item) => '<li class="attention-row"><span class="tile-icon">' + icon('warning', true) + '</span><div>' +
883
+ memoryLink(item.id, item.projectId, item.title || item.id) + '<div class="tags">' +
884
+ tags([['project', state.projects.find((project) => project.id === item.projectId)?.name || item.projectId], ['memory', nice(item.category)]]) +
885
+ list(item.issues).map((issue) => '<span class="tag check-' + (issue.severity === 'review' ? 'warn' : '') + '">' +
886
+ esc(issue.label || healthIssues[issue.kind] || 'Review') + '</span>').join('') + '</div></div></li>').join('');
887
+ target.innerHTML = rows.length ? count + '<ul class="list attention-list" tabindex="0" aria-label="Flagged memories">' + items + '</ul>' + warnings :
888
+ blank('memory', store.filter ? 'No loaded items match' : 'No attention items', store.filter ? 'Choose another issue or show all loaded items.' : 'Review flags will appear here as the memory store changes.') + warnings;
889
+ }
890
+
891
+ async function loadHealth(project) {
892
+ const target = $('attention-results');
893
+ if (!target) return;
894
+ const store = state.health;
895
+ const request = ++store.request;
896
+ if (store.project !== project) store.filter = '';
897
+ store.project = project;
898
+ store.snapshot = null;
899
+ $('attention-filter').disabled = true;
900
+ $('attention-scope').textContent = '';
901
+ target.innerHTML = skeleton('Checking memory health…');
902
+ setBusy(target, true);
903
+ try {
904
+ const snapshot = await api(queryPath('memory-health', { project, limit: 30 }));
905
+ if (request !== store.request || target !== $('attention-results')) return;
906
+ store.snapshot = snapshot;
907
+ renderAttention();
908
+ } catch (error) {
909
+ if (request === store.request && target === $('attention-results')) target.innerHTML = errorMarkup(error.message, 'health');
910
+ } finally {
911
+ if (request === store.request && target === $('attention-results')) setBusy(target, false);
912
+ }
913
+ }
914
+
720
915
  function renderActivity() {
721
916
  const target = $('activity-body');
722
917
  if (!target) return;
@@ -745,12 +940,18 @@ function renderOverview(data) {
745
940
  number(data.inventoryLimit) + ' stored memories.</p>' : '';
746
941
  const pending = data.storageHealth?.status === 'pending' ? '<p class="status small muted notice">The memory store has pending writes.</p>' : '';
747
942
  const projectNote = state.projects.length > 8 ? 'Top projects by saved memory count' : 'Saved memories by project';
748
- $('overview-body').innerHTML = '<div id="overview-refresh"></div>' + pending + sample + renderSummary(data.totals || {}) +
749
- '<div class="split overview-columns"><div class="stack">' + renderQuality(data) + '<div class="panel"><div class="panel-head"><div><h2>Project inventory</h2>' +
943
+ optionSet('overview-quality-project', 'All projects', state.projects, data.scope?.projectId || '');
944
+ $('overview-context').href = routeLink('context', { project: data.scope?.projectId });
945
+ $('overview-body').innerHTML = '<div id="overview-refresh"></div>' + pending + renderOverviewSummary(data) + sample +
946
+ '<div class="split overview-columns"><div class="stack"><section class="panel" aria-labelledby="attention-heading"><div class="panel-head"><div>' +
947
+ '<h2 id="attention-heading" tabindex="-1">Attention queue</h2><p class="small muted" id="attention-scope"></p></div>' +
948
+ '<label class="label" for="attention-filter">Issue<select id="attention-filter" disabled></select></label></div>' +
949
+ '<div id="attention-results"></div></section>' + renderQuality(data) + '<div class="panel"><div class="panel-head"><div><h2>Project inventory</h2>' +
750
950
  '<p class="muted small">' + projectNote + '</p></div><a class="button quiet" href="#memories">Browse ' + icon('chevron', true) +
751
951
  '</a></div>' + renderProjects(state.projects) + '</div>' + renderOverviewMemories(list(data.recentMemories)) +
752
- '</div><div class="panel"><div class="panel-head"><h2>Recent activity</h2><span id="activity-status" class="small muted"></span></div><div id="activity-body"></div></div></div>';
952
+ '</div><div class="panel overview-activity"><div class="panel-head"><div><h2>Recent activity</h2><span id="activity-status" class="small muted"></span></div><a class="button quiet" href="/live">Open live operations ' + icon('chevron', true) + '</a></div><div id="activity-body"></div></div></div>';
753
953
  renderActivity();
954
+ if (state.view === 'overview') void loadHealth(data.scope?.projectId || '');
754
955
  }
755
956
 
756
957
  function syncProjectOptions() {
@@ -768,6 +969,8 @@ async function loadOverview(force = false, project = '') {
768
969
  state.overviewRequestScope = project;
769
970
  const target = $('overview-body');
770
971
  if (!state.overviewLoaded) target.innerHTML = '<div class="panel">' + skeleton('Loading workspace…') + '</div>';
972
+ else if (state.overviewScope !== project && $('overview-refresh')) $('overview-refresh').innerHTML =
973
+ '<div class="notice status small muted" role="status">Loading the selected project. Showing the previous scope.</div>';
771
974
  setBusy(target, true);
772
975
  state.overviewPending = (async () => {
773
976
  try {
@@ -776,6 +979,7 @@ async function loadOverview(force = false, project = '') {
776
979
  state.overviewLoaded = true;
777
980
  state.overviewScope = project;
778
981
  state.projects = list(data.projects);
982
+ state.projectKit = data.projectKit ?? null;
779
983
  state.activity = list(data.activity);
780
984
  if (typeof data.features?.liveActivity === 'boolean') state.activityEnabled = data.features.liveActivity;
781
985
  if (data.version) $('rail-version').textContent = 'Version ' + data.version;
@@ -931,6 +1135,9 @@ function renderDetail(snapshot) {
931
1135
  ]) + (memory.createdAt || memory.updatedAt ? '<div class="small muted">' + dateMarkup(memory.createdAt || memory.updatedAt, true) + '</div>' : '');
932
1136
  $('detail-graph').href = routeLink('graph', { project, memory: memory.id });
933
1137
  $('detail-context').href = routeLink('context', { project });
1138
+ const projectFeedback = document.documentElement.dataset.projectLocal === 'true' || state.projectKit?.projectId === project;
1139
+ $('detail-outcome').hidden = !projectFeedback;
1140
+ $('detail-outcome').href = routeLink('project-kit', { memory: memory.id });
934
1141
  $('detail-actions').hidden = false;
935
1142
  const linkRows = list(links).map((link) => {
936
1143
  const outgoing = link.fromId === memory.id;
@@ -1048,9 +1255,9 @@ async function saveDetail(kind, payload, focus) {
1048
1255
  await api(kind, { body: { projectId: project, expectedRevision: store.snapshot.revision, ...payload } });
1049
1256
  state.graph.snapshot = null;
1050
1257
  state.overviewLoaded = false;
1051
- if (state.context.pack) {
1258
+ if (state.context.pack || state.context.pending) {
1052
1259
  state.context.dirty = true;
1053
- renderContext();
1260
+ if (!state.context.pending) renderContext();
1054
1261
  }
1055
1262
  if (request !== store.request || state.view !== 'memory') return;
1056
1263
  if (await loadDetail(id, project, 'Saved. Could not reload the memory. ')) detailStatus(status, 'Saved.');
@@ -1317,6 +1524,10 @@ function centerGraphNode(id) {
1317
1524
  const position = graph.layout?.positions.get(id);
1318
1525
  if (!position || graph.mode !== 'map') return;
1319
1526
  const { width, height } = graphViewport();
1527
+ const left = graph.panX + position.x * graph.zoom;
1528
+ const top = graph.panY + position.y * graph.zoom;
1529
+ if (graph.zoom >= .8 && left >= 14 && top >= 14 && left + position.width * graph.zoom <= width - 14 &&
1530
+ top + position.height * graph.zoom <= height - 52) return;
1320
1531
  const zoom = Math.max(.8, graph.zoom);
1321
1532
  graph.zoom = zoom;
1322
1533
  graph.panX = width / 2 - (position.x + position.width / 2) * zoom;
@@ -1352,9 +1563,10 @@ function renderGraphInspector() {
1352
1563
  const from = all.get(selectedEdge.source);
1353
1564
  const to = all.get(selectedEdge.target);
1354
1565
  const record = (node) => node ? memoryLink(graphMemoryId(node), node.project, node.label || node.id) : '';
1566
+ const endpoint = (label, node) => (selectedEdge.kind === 'related' ? '' : '<p class="small muted">' + label + '</p>') + '<p>' + record(node) + '</p>';
1355
1567
  inspector.innerHTML = '<div class="panel-head"><h3>Link details</h3></div><div class="panel-body">' +
1356
- '<p>' + record(from) + '</p><div class="tags"><span class="tag memory">' + esc(graphEdgeLabels[selectedEdge.kind]) + '</span></div>' +
1357
- '<p>' + record(to) + '</p>' + (selectedEdge.note ? '<p class="small muted detail-note">' + esc(selectedEdge.note) + '</p>' : '') + '</div>';
1568
+ '<div>' + endpoint('From memory', from) + '</div>' + tags([['memory', graphEdgeLabels[selectedEdge.kind]], ['agent', 'Recorded memory link']]) +
1569
+ '<div>' + endpoint('To memory', to) + '</div>' + (selectedEdge.note ? '<p class="small muted detail-note">' + esc(selectedEdge.note) + '</p>' : '') + '</div>';
1358
1570
  return;
1359
1571
  }
1360
1572
  if (!selected) {
@@ -1367,7 +1579,7 @@ function renderGraphInspector() {
1367
1579
  adjacent.map(({ id, edge }) => {
1368
1580
  const node = all.get(id);
1369
1581
  if (recordedEdge(edge)) return '<button type="button" class="button quiet" data-edge-record="' + esc(edge.id) +
1370
- '"><span>' + esc(graphEdgeLabels[edge.kind]) + ' · ' + esc(node.label || node.id) + '</span></button>';
1582
+ '"><span>' + esc(linkCaption({ fromId: edge.source, type: edge.kind }, selected.id)) + ' · ' + esc(node.label || node.id) + '</span></button>';
1371
1583
  return '<button type="button" class="button quiet" data-related="' + esc(id) + '" title="' +
1372
1584
  esc(graphEdgeLabels[edge.kind] || nice(edge.kind) || 'Connection') + '">' + icon(node.kind, true) + '<span>' +
1373
1585
  esc(node.label || node.id) + '</span></button>';
@@ -1375,11 +1587,14 @@ function renderGraphInspector() {
1375
1587
  const project = state.projects.find((item) => item.id === selected.project)?.name || selected.project;
1376
1588
  const snippet = compact(selected.snippet) && compact(selected.snippet) !== compact(selected.label)
1377
1589
  ? '<p class="small">' + esc(selected.snippet) + '</p>' : '';
1590
+ const canFocus = selected.kind === 'memory' && adjacent.some(({ edge }) => recordedEdge(edge)) && graph.lens !== selected.id;
1378
1591
  inspector.innerHTML = '<div class="panel-head"><h3>Node details</h3></div><div class="panel-body">' +
1379
1592
  tags([[selected.kind, graphKinds[selected.kind]], ['memory', nice(selected.category)], ['state-' + (selected.effectiveState || ''), reviewLabel(selected.effectiveState)]]) +
1380
1593
  '<h3>' + (selected.kind === 'memory' ? memoryLink(graphMemoryId(selected), selected.project, selected.label || selected.id) : esc(selected.label || selected.id)) + '</h3>' +
1381
1594
  (project && selected.kind !== 'project' ? '<p class="muted small">' + esc(project) + '</p>' : '') + snippet +
1382
- (selected.kind === 'memory' ? sourceMarkup([...list(selected.sources), selected.source]) : '') + connected + '</div>';
1595
+ (selected.kind === 'memory' ? sourceMarkup([...list(selected.sources), selected.source]) : '') +
1596
+ (canFocus ? '<button class="button" type="button" data-graph-lens="' + esc(selected.id) + '">' + icon('graph', true) + 'Focus recorded links</button>' : '') +
1597
+ connected + '</div>';
1383
1598
  }
1384
1599
 
1385
1600
  function updateGraphSelection() {
@@ -1396,6 +1611,7 @@ function updateGraphSelection() {
1396
1611
  element.setAttribute('aria-pressed', String(id === nodeId));
1397
1612
  if (element instanceof SVGElement) {
1398
1613
  element.classList.toggle('unrelated', hasSelection && !connected.has(nodeId));
1614
+ element.classList.toggle('selected-memory', id === nodeId && nodeId.startsWith('memory:'));
1399
1615
  element.setAttribute('tabindex', nodeId === tabStop ? '0' : '-1');
1400
1616
  }
1401
1617
  });
@@ -1446,14 +1662,22 @@ function renderGraph() {
1446
1662
  const search = $('graph-query').value.trim().toLowerCase();
1447
1663
  const kind = $('graph-kind').value;
1448
1664
  const relation = $('graph-relation').value;
1449
- const nodes = list(graph.snapshot.nodes).filter((node) => Object.hasOwn(graphKinds, node.kind));
1665
+ const allNodes = list(graph.snapshot.nodes).filter((node) => Object.hasOwn(graphKinds, node.kind));
1666
+ const allEdges = list(graph.snapshot.edges);
1667
+ const anchor = allNodes.find((node) => node.id === graph.lens);
1668
+ const edges = anchor ? allEdges.filter((edge) => recordedEdge(edge) && (edge.source === anchor.id || edge.target === anchor.id)) : allEdges;
1669
+ const lensIds = anchor ? new Set([anchor.id, ...edges.flatMap((edge) => [edge.source, edge.target])]) : null;
1670
+ const nodes = lensIds ? allNodes.filter((node) => lensIds.has(node.id)) : allNodes;
1671
+ $('graph-lens').hidden = !anchor;
1672
+ $('graph-body').dataset.lens = String(!!anchor);
1673
+ $('graph-lens-label').textContent = anchor ? 'Recorded links for ' + shorten(compact(anchor.label || anchor.id), 94) : '';
1450
1674
  const matches = nodes.filter((node) => (!kind || node.kind === kind) &&
1451
1675
  (!search || [node.label, node.snippet, node.project, node.category].filter(Boolean).join(' ').toLowerCase().includes(search)));
1452
1676
  const ids = new Set(matches.map((node) => node.id));
1453
1677
  if (search && !kind) {
1454
- matches.forEach((node) => graphNeighbors(list(graph.snapshot.edges), node.id).forEach((item) => ids.add(item.id)));
1678
+ matches.forEach((node) => graphNeighbors(edges, node.id).forEach((item) => ids.add(item.id)));
1455
1679
  }
1456
- const matchingEdges = list(graph.snapshot.edges).filter((edge) => ids.has(edge.source) && ids.has(edge.target) &&
1680
+ const matchingEdges = edges.filter((edge) => ids.has(edge.source) && ids.has(edge.target) &&
1457
1681
  (!relation || (relation === 'recorded' ? recordedEdge(edge) : edge.kind === relation)));
1458
1682
  if (relation) {
1459
1683
  const endpoints = new Set(matchingEdges.flatMap((edge) => [edge.source, edge.target]));
@@ -1467,8 +1691,8 @@ function renderGraph() {
1467
1691
  $('graph-count').textContent = matchingCount + countLabel(graph.nodes.length, 'node') + ' · ' + countLabel(graph.edges.length, 'connection');
1468
1692
  $('graph-body').hidden = !graph.nodes.length;
1469
1693
  $('graph-legend').hidden = !graph.nodes.length;
1470
- const note = graph.snapshot.truncated ? '<div class="panel-body small muted">Showing ' +
1471
- number(graph.snapshot.shownMemories) + ' memories in this snapshot. Filter by project to explore a narrower set.</div>' : '';
1694
+ const note = graph.snapshot.truncated ? '<div class="panel-body small muted">This snapshot includes ' +
1695
+ number(graph.snapshot.shownMemories) + ' memories. Filter by project to explore a narrower set.</div>' : '';
1472
1696
  const focused = graph.focusMemory && graph.nodes.find((node) => graphMemoryId(node) === graph.focusMemory);
1473
1697
  const missing = graph.focusMemory && !focused ? '<p class="panel-body small muted">This memory is outside the current graph view.</p>' : '';
1474
1698
  $('graph-status').innerHTML = graph.nodes.length ? note + missing : missing + blank('graph', search || kind || relation ? 'No matching nodes' : 'No relationships yet',
@@ -1498,6 +1722,7 @@ async function loadGraph(project, force = false, memory = '') {
1498
1722
  const controller = new AbortController();
1499
1723
  graph.controller = controller;
1500
1724
  graph.snapshot = null;
1725
+ if (graph.project !== project || memory) graph.lens = '';
1501
1726
  graph.project = project;
1502
1727
  graph.selected = '';
1503
1728
  graph.selectedEdge = '';
@@ -1505,6 +1730,7 @@ async function loadGraph(project, force = false, memory = '') {
1505
1730
  $('graph-status').innerHTML = skeleton('Loading relationships…');
1506
1731
  $('graph-count').textContent = '';
1507
1732
  $('graph-body').hidden = true;
1733
+ $('graph-lens').hidden = true;
1508
1734
  $('graph-legend').hidden = true;
1509
1735
  try {
1510
1736
  const snapshot = await api(queryPath('graph', { project, memory }), { signal: AbortSignal.any([controller.signal, AbortSignal.timeout(12000)]) });
@@ -1521,10 +1747,11 @@ async function loadGraph(project, force = false, memory = '') {
1521
1747
  function renderContextControls() {
1522
1748
  const input = $('context-cross');
1523
1749
  const toggle = state.features?.features;
1524
- const canCross = toggle?.contextAssembly === true && toggle?.crossProjectContext === true;
1750
+ const canCross = document.documentElement.dataset.projectLocal !== 'true' && toggle?.contextAssembly === true && toggle?.crossProjectContext === true;
1525
1751
  $('context-cross-row').hidden = !canCross;
1526
1752
  if (!canCross) input.checked = false;
1527
1753
  $('context-submit').disabled = !toggle?.contextAssembly || !state.projects.length || state.context.pending;
1754
+ $('context-related-row').hidden = $('context-profile').value !== 'adaptive';
1528
1755
  const gate = $('context-gate');
1529
1756
  if (!toggle && state.featurePending) {
1530
1757
  gate.innerHTML = '<p class="muted small" role="status">Checking feature settings…</p>';
@@ -1547,11 +1774,99 @@ function contextReasonList(items, format) {
1547
1774
  return '<ul class="list">' + items.map(format).join('') + '</ul>';
1548
1775
  }
1549
1776
 
1777
+ const contextReasons = {
1778
+ selected: 'Included in the brief.', duplicate: 'Matches a memory already considered.', budget: 'Would exceed the selected budget.',
1779
+ limit: 'Beyond the brief’s memory limit.', 'candidate-limit': 'Beyond the evaluated candidate limit.', empty: 'Contains no memory text.',
1780
+ lookback: 'Falls outside the selected time range.', archived: 'Archived in a recorded review.', contested: 'Marked as needing review.',
1781
+ superseded: 'Superseded by a recorded replacement.'
1782
+ };
1783
+ const rankScore = (value) => Number.isFinite(value) ? value.toLocaleString(undefined, { maximumFractionDigits: 3 }) : '—';
1784
+
1785
+ function renderRetrieval(pack) {
1786
+ const retrieval = pack.retrieval;
1787
+ if (!retrieval || !pack.enabled) return '';
1788
+ const counts = list(retrieval.stages);
1789
+ const max = Math.max(1, ...counts.map((stage) => Number(stage.count) || 0));
1790
+ const stages = counts.map((stage) => '<li><strong>' + number(stage.count) + '</strong><span>' + esc(stage.label) +
1791
+ '</span><div class="retrieval-stage-bar" aria-hidden="true"><i style="width:' + Math.max(0, (Number(stage.count) || 0) / max * 100) + '%"></i></div></li>').join('');
1792
+ const candidates = list(retrieval.candidates);
1793
+ const labels = new Map([...list(pack.entries), ...candidates].map((item) => [item.id, item.title || shorten(compact(item.excerpt), 95) || item.id]));
1794
+ const rows = candidates.map((candidate) => {
1795
+ const signals = list(candidate.signals).map((signal) => {
1796
+ const sign = signal.label === 'Project task feedback' && signal.value > 0 ? '+' : '';
1797
+ return '<div><dt>' + esc(signal.label) + '</dt><dd>' + sign + esc(rankScore(signal.value)) + '</dd></div>';
1798
+ }).join('');
1799
+ const terms = list(candidate.matchedTerms);
1800
+ const fields = list(candidate.matchedFields);
1801
+ const paths = list(candidate.paths).map((path) => '<li><span class="tag agent">' + esc(linkTypes[path.relationType] || nice(path.relationType)) +
1802
+ '</span> ' + memoryLink(path.fromId, candidate.projectId, labels.get(path.fromId) || path.fromId) + ' ' + icon('chevron', true) + ' ' +
1803
+ memoryLink(path.toId, candidate.projectId, labels.get(path.toId) || path.toId) + '</li>').join('');
1804
+ const feedback = candidate.feedback;
1805
+ const reporters = list(feedback?.reporterKinds).map((kind) => kind === 'agent' ? 'Agent' : 'User');
1806
+ const feedbackSource = feedback ? '<p class="small muted">' + countLabel(list(feedback.recordIds).length, 'matching task report') +
1807
+ (reporters.length ? ' · ' + esc(reporters.join(' and ')) : '') +
1808
+ (state.projectKit?.projectId === candidate.projectId ? ' · <a href="' + esc(routeLink('project-kit', { memory: candidate.id })) + '">Review project outcomes</a>' : '') + '</p>' : '';
1809
+ const included = candidate.decision === 'selected';
1810
+ return '<li><details class="candidate"><summary><span class="candidate-title"><span class="rank">#' + number(candidate.rank) + '</span>' +
1811
+ '<span>' + esc(candidate.title || candidate.id) + '</span><span class="tag check-' + (included ? 'ok' : 'warn') + '">' +
1812
+ (included ? 'Included' : 'Excluded') + '</span></span></summary><div class="candidate-body"><p class="small muted">' +
1813
+ esc(contextReasons[candidate.reason] || nice(candidate.reason)) + ' Rank score ' + esc(rankScore(candidate.score)) + '.</p>' +
1814
+ (terms.length ? '<p class="small"><strong>Matched terms</strong> ' + esc(terms.join(', ')) + '</p>' : '') +
1815
+ (fields.length ? '<p class="small"><strong>Matched fields</strong> ' + esc(fields.map(nice).join(', ')) + '</p>' : '') +
1816
+ (signals ? '<dl class="candidate-signals" aria-label="Ranking contributions">' + signals + '</dl>' : '') + feedbackSource + sourceMarkup(candidate.sources) +
1817
+ (paths ? '<div><h3>Recorded link paths</h3><ul class="list">' + paths + '</ul></div>' : '') +
1818
+ '<p>' + memoryLink(candidate.id, candidate.projectId, 'Open memory') + '</p></div></details></li>';
1819
+ }).join('');
1820
+ const reportCount = retrieval.truncated ? ' Candidate diagnostics are bounded to ' + number(retrieval.candidateLimit) + ' records.' : '';
1821
+ return '<section class="context-section" aria-labelledby="retrieval-heading"><h3 id="retrieval-heading">Retrieval pipeline</h3>' +
1822
+ '<ol class="retrieval-flow" aria-label="Retrieval stages">' + stages + '</ol><p class="small muted">Scanned ' + number(retrieval.scanned) +
1823
+ ' memories. Ranking contributions explain ordering.' + esc(reportCount) + '</p>' +
1824
+ (rows ? '<details class="context-section" open><summary>Candidate decisions · ' + number(candidates.length) + '</summary>' +
1825
+ '<ul class="list candidate-list" aria-label="Candidate decisions">' + rows + '</ul></details>' : '') + '</section>';
1826
+ }
1827
+
1828
+ function renderComparison() {
1829
+ const target = $('context-comparison');
1830
+ if (!target) return;
1831
+ const { comparison, comparing, dirty } = state.context;
1832
+ if (dirty || !comparison && !comparing) {
1833
+ target.replaceChildren();
1834
+ return;
1835
+ }
1836
+ if (comparing) {
1837
+ target.innerHTML = skeleton('Comparing retrieval profiles…');
1838
+ return;
1839
+ }
1840
+ if (comparison.error) {
1841
+ target.innerHTML = errorMarkup(comparison.error);
1842
+ return;
1843
+ }
1844
+ const { legacy, adaptive, delta } = comparison;
1845
+ const shared = list(delta?.sharedIds);
1846
+ const column = (label, pack, ids) => {
1847
+ const unique = new Set(ids);
1848
+ const entries = list(pack.entries);
1849
+ const differences = entries.filter((entry) => unique.has(entry.id));
1850
+ return '<div><h3>' + label + '</h3><p class="small muted">' + countLabel(entries.length, 'included memory', 'included memories') + ' · ' +
1851
+ number(pack.budget?.estimatedTokens) + ' estimated tokens</p><p class="small"><strong>' + number(differences.length) + ' unique to ' + label + '</strong></p>' +
1852
+ (differences.length ? '<ul class="small">' + differences.map((entry) => '<li>' +
1853
+ memoryLink(entry.id, entry.projectId, shorten(compact(entry.excerpt), 95) || entry.id) + '</li>').join('') + '</ul>' : '') + '</div>';
1854
+ };
1855
+ target.innerHTML = '<h3>Profile comparison</h3><p class="small muted">Same task, scope, and requested budget · ' +
1856
+ countLabel(shared.length, 'shared memory', 'shared memories') + '</p><div class="compare-grid">' +
1857
+ column('Adaptive', adaptive, list(delta?.addedIds)) + column('Legacy', legacy, list(delta?.removedIds)) + '</div>' +
1858
+ list(comparison.warnings).map((warning) => '<p class="small muted">' + esc(warning) + '</p>').join('');
1859
+ }
1860
+
1550
1861
  function renderContext() {
1551
1862
  const output = $('context-output');
1552
- const { pack, dirty } = state.context;
1863
+ const { pack, dirty, params } = state.context;
1864
+ const status = $('context-result-state');
1553
1865
  if (!pack) {
1554
- output.innerHTML = blank('context', 'Ready for a task', 'Choose a project and describe the work to see the context and its sources here.');
1866
+ const enabled = state.features?.features?.contextAssembly !== false;
1867
+ status.textContent = enabled ? '' : 'Disabled';
1868
+ output.innerHTML = enabled ? blank('context', 'Ready for a task', 'Choose a project and describe the work to see the context and its sources here.') :
1869
+ blank('context', 'Context assembly is turned off', 'Enable it in Features to build a brief.');
1555
1870
  return;
1556
1871
  }
1557
1872
  const entries = list(pack.entries);
@@ -1559,7 +1874,9 @@ function renderContext() {
1559
1874
  const budget = pack.budget || {};
1560
1875
  const warnings = list(pack.warnings);
1561
1876
  const allowed = state.features?.features?.contextAssembly === true;
1562
- const canCopy = allowed && !dirty && !!pack.markdown && pack.enabled && entries.length > 0;
1877
+ const canInspect = allowed && !dirty && pack.enabled;
1878
+ const canCopy = canInspect && !!pack.markdown && entries.length > 0;
1879
+ status.textContent = dirty ? 'Rebuild needed' : pack.enabled ? 'Assembled' : 'Disabled';
1563
1880
  const warningMarkup = warnings.map((warning) => '<p class="small muted">' + esc(warning) + '</p>').join('');
1564
1881
  const selection = contextReasonList(entries, (entry) => {
1565
1882
  const title = shorten(compact(entry.excerpt), 120) || nice(entry.category) || entry.id;
@@ -1573,34 +1890,29 @@ function renderContext() {
1573
1890
  esc([entry.projectName, entry.agentTool, nice(entry.category)].filter(Boolean).join(' · ')) +
1574
1891
  '</div>' + reason + sourceMarkup(entry.sources) + links + '</li>';
1575
1892
  });
1576
- const reasonLabels = {
1577
- duplicate: 'Matches a memory already considered.',
1578
- budget: 'Would exceed the selected budget.',
1579
- limit: 'Beyond the brief’s memory limit.',
1580
- empty: 'Contains no memory text.',
1581
- lookback: 'Falls outside the selected time range.',
1582
- archived: 'Archived in a recorded review.',
1583
- contested: 'Marked as needing review.',
1584
- superseded: 'Superseded by a recorded replacement.'
1585
- };
1586
1893
  const rejections = contextReasonList(excluded, (entry) => '<li><strong class="mono">' +
1587
1894
  memoryLink(entry.id, entry.projectId || (pack.scope === 'project' ? pack.projectId : ''), entry.title || entry.id) +
1588
- '</strong><p class="small muted">' + esc(reasonLabels[entry.reason] || nice(entry.reason)) + '</p></li>');
1895
+ '</strong><p class="small muted">' + esc(contextReasons[entry.reason] || nice(entry.reason)) + '</p></li>');
1589
1896
  const projectName = state.projects.find((item) => item.id === pack.projectId)?.name || pack.projectId;
1590
1897
  const scope = pack.scope === 'cross-project' ? 'Cross-project · ' + projectName : projectName;
1898
+ const usage = budget.tokenBudget > 0 ? Math.min(100, Math.max(0, budget.estimatedTokens / budget.tokenBudget * 100)) : 0;
1591
1899
  output.innerHTML = '<div class="panel-body stack">' + (dirty ? '<p class="status small" role="status">This preview is out of date. Assemble again to refresh it.</p>' : '') +
1592
- '<div class="form-actions">' + tags([['project', scope], ['memory', number(budget.estimatedTokens) + ' / ' + number(budget.tokenBudget) + ' estimated tokens']]) +
1593
- (canCopy ? '<button class="button toolbar-end" id="context-copy" type="button">' + icon('copy') + 'Copy brief</button>' : '') + '</div>' +
1594
- '<p id="copy-status" class="small muted" role="status" hidden></p><p class="small muted">' +
1595
- countLabel(entries.length, 'included memory', 'included memories') + ' · ' + number(excluded.length) + ' excluded · ' +
1596
- number(budget.usedChars) + ' / ' + number(budget.maxChars) + ' characters</p>' +
1597
- warningMarkup +
1598
- (pack.markdown && pack.enabled && entries.length ? '<pre class="context-preview mono" id="context-text" tabindex="0" aria-label="Assembled context markdown">' + esc(pack.markdown) + '</pre>' :
1599
- blank('context', pack.enabled ? 'No memories fit this task' : 'Context assembly is turned off', pack.enabled ? 'Review any exclusions or try a different task.' : 'Enable it in Features to build a brief.')) +
1900
+ '<div class="context-output-head">' + tags([['project', scope], ['memory', params?.profile === 'adaptive' ? 'Adaptive' : 'Legacy']]) +
1901
+ '<div class="form-actions">' + (canInspect && params ? '<button type="button" class="button" id="context-compare-button"' +
1902
+ (state.context.comparing ? ' disabled' : '') + '>Compare profiles</button>' : '') +
1903
+ (canCopy ? '<button class="button" id="context-download" type="button">Download .md</button>' +
1904
+ '<button class="button primary" id="context-copy" type="button">' + icon('copy') + 'Copy brief</button>' : '') + '</div></div>' +
1905
+ '<p id="copy-status" class="small muted" role="status" hidden></p><dl class="context-metrics">' +
1906
+ '<div><dt>Estimated tokens</dt><dd>' + number(budget.estimatedTokens) + ' / ' + number(budget.tokenBudget) + '</dd><div class="context-budget-track" aria-hidden="true"><span style="width:' + usage + '%"></span></div></div>' +
1907
+ '<div><dt>Characters</dt><dd>' + number(budget.usedChars) + ' / ' + number(budget.maxChars) + '</dd></div>' +
1908
+ '<div><dt>Included / excluded</dt><dd>' + number(entries.length) + ' / ' + number(excluded.length) + '</dd></div></dl>' +
1909
+ warningMarkup + '<section class="context-compare" id="context-comparison" aria-live="polite"></section>' + renderRetrieval(pack) +
1600
1910
  '<div class="context-reasons">' +
1601
- (entries.length ? '<details open><summary>Included · ' + number(entries.length) + '</summary>' + selection + '</details>' : '') +
1602
- (excluded.length ? '<details><summary>Excluded · ' + number(excluded.length) + '</summary>' + rejections + '</details>' : '') +
1603
- '</div></div>';
1911
+ (entries.length ? '<details class="context-section"><summary>Included memories · ' + number(entries.length) + '</summary>' + selection + '</details>' : '') +
1912
+ (excluded.length ? '<details class="context-section"><summary>Excluded memories · ' + number(excluded.length) + '</summary>' + rejections + '</details>' : '') +
1913
+ '</div>' + (pack.markdown && pack.enabled && entries.length ? '<details class="context-section" open><summary>Assembled brief</summary><pre class="context-preview mono" id="context-text" tabindex="0" aria-label="Assembled context markdown">' + esc(pack.markdown) + '</pre></details>' :
1914
+ blank('context', pack.enabled ? 'No memories fit this task' : 'Context assembly is turned off', pack.enabled ? 'Review any exclusions or try a different task.' : 'Enable it in Features to build a brief.')) + '</div>';
1915
+ renderComparison();
1604
1916
  }
1605
1917
 
1606
1918
  async function assembleContext() {
@@ -1615,18 +1927,29 @@ async function assembleContext() {
1615
1927
  $('context-task').reportValidity();
1616
1928
  return;
1617
1929
  }
1618
- const body = { query, projectId, tokenBudget, includeCrossProject: toggle.crossProjectContext && $('context-cross').checked };
1930
+ const profile = $('context-profile').value;
1931
+ const body = { query, projectId, tokenBudget, includeCrossProject: toggle.crossProjectContext && $('context-cross').checked,
1932
+ profile, ...(profile === 'adaptive' ? { expandRelated: $('context-related').checked } : {}) };
1619
1933
  store.pending = true;
1620
1934
  store.dirty = false;
1935
+ store.pack = null;
1936
+ store.params = body;
1937
+ store.comparison = null;
1938
+ store.comparing = false;
1621
1939
  renderContextControls();
1622
1940
  setBusy($('context-result'), true);
1941
+ $('context-result-state').textContent = 'Assembling';
1623
1942
  $('context-output').innerHTML = skeleton('Assembling context…');
1624
1943
  try {
1625
- store.pack = await api('context', { body });
1944
+ const pack = await api('context', { body });
1945
+ const current = state.features?.features;
1946
+ if (store.params === body && current?.contextAssembly && (pack.scope !== 'cross-project' || current.crossProjectContext)) store.pack = pack;
1626
1947
  renderContext();
1627
1948
  } catch (error) {
1628
- store.pack = null;
1629
- $('context-output').innerHTML = errorMarkup(error.message, 'context');
1949
+ if (store.params === body) {
1950
+ $('context-result-state').textContent = 'Request failed';
1951
+ $('context-output').innerHTML = errorMarkup(error.message, 'context');
1952
+ }
1630
1953
  } finally {
1631
1954
  store.pending = false;
1632
1955
  setBusy($('context-result'), false);
@@ -1634,10 +1957,44 @@ async function assembleContext() {
1634
1957
  }
1635
1958
  }
1636
1959
 
1960
+ async function compareContext() {
1961
+ const store = state.context;
1962
+ if (!store.params || store.dirty || store.pending || store.comparing || !store.pack?.enabled) return;
1963
+ const params = store.params;
1964
+ const { profile, ...body } = params;
1965
+ store.comparing = true;
1966
+ $('context-compare-button').disabled = true;
1967
+ renderComparison();
1968
+ try {
1969
+ const result = await api('context-compare', { body });
1970
+ if (!result.adaptive || !result.legacy || !result.delta) throw new Error('The server returned an unreadable comparison.');
1971
+ if (store.params === params && !store.dirty) store.comparison = result;
1972
+ } catch (error) {
1973
+ if (store.params === params && !store.dirty) store.comparison = { error: error.message };
1974
+ } finally {
1975
+ if (store.params === params) {
1976
+ store.comparing = false;
1977
+ if ($('context-compare-button')) $('context-compare-button').disabled = false;
1978
+ renderComparison();
1979
+ }
1980
+ }
1981
+ }
1982
+
1983
+ function downloadContext() {
1984
+ const { pack, dirty } = state.context;
1985
+ if (!pack?.markdown || !pack.enabled || dirty || !state.features?.features.contextAssembly) return;
1986
+ const url = URL.createObjectURL(new Blob([pack.markdown], { type: 'text/markdown;charset=utf-8' }));
1987
+ const link = document.createElement('a');
1988
+ link.href = url;
1989
+ link.download = 'shadow-brain-context.md';
1990
+ link.click();
1991
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
1992
+ }
1993
+
1637
1994
  async function copyContext() {
1638
1995
  const store = state.context;
1639
1996
  const status = $('copy-status');
1640
- if (!store.pack?.markdown || store.dirty || !status) return;
1997
+ if (!store.pack?.markdown || !store.pack.enabled || store.dirty || !status || !state.features?.features.contextAssembly) return;
1641
1998
  try {
1642
1999
  await navigator.clipboard.writeText(store.pack.markdown);
1643
2000
  status.textContent = 'Brief copied.';
@@ -1682,6 +2039,12 @@ function renderIntegrations(data) {
1682
2039
  const agentRows = agents.map((agent) => '<li class="check-row"><h3>' + esc(agent.label || agent.id) + '</h3><div class="check-result">' +
1683
2040
  tags(list(agent.capabilities).map((value) => ['memory', value])) + (agent.detail ? '<p class="small muted">' + esc(agent.detail) + '</p>' : '') +
1684
2041
  (agent.note ? '<p class="small muted">' + esc(agent.note) + '</p>' : '') + commandMarkup(agent.command) +
2042
+ (typeof agent.config?.content === 'string' ? '<details class="integration-config"><summary>View MCP setup example</summary>' +
2043
+ '<p class="small muted">' + esc([agent.config.format, agent.config.filename].filter(Boolean).join(' · ')) + '</p>' +
2044
+ '<pre class="context-preview mono" tabindex="0">' + esc(agent.config.content) + '</pre>' +
2045
+ list(agent.config.notes).map((note) => '<p class="small muted">' + esc(note) + '</p>').join('') +
2046
+ '<div class="form-actions"><button type="button" class="button quiet" data-copy-command="' + esc(agent.config.content) + '">' +
2047
+ icon('copy', true) + 'Copy example</button><span class="small muted" role="status"></span></div></details>' : '') +
1685
2048
  '<div class="form-actions">' + docsLink(agent.docs) + '</div></div>' + checkTag(agent.installation, installStates) + '</li>').join('');
1686
2049
  $('integrations-body').innerHTML = '<div class="stack"><section class="panel"><div class="panel-head"><div><h2>Local diagnostics</h2>' +
1687
2050
  (data.checkedAt ? '<p class="small muted">Last checked ' + dateMarkup(data.checkedAt) + '</p>' : '') + '</div>' +
@@ -1689,7 +2052,8 @@ function renderIntegrations(data) {
1689
2052
  'Copy report</button><span class="small muted" role="status"></span></div>' : '') + '</div>' +
1690
2053
  (checkRows ? '<ul class="list">' + checkRows + '</ul>' : blank('activity', 'No diagnostic results', 'Run checks to refresh the local status.')) +
1691
2054
  '</section><section class="panel"><div class="panel-head"><div><h2>Supported agents</h2>' +
1692
- '<p class="small muted">Installation status reflects managed files in the checked scope.</p></div></div>' +
2055
+ '<p class="small muted">Installation status reflects project skill files. Use Live operations to inspect recorded sessions.</p></div></div>' +
2056
+ '<div class="integration-meta"><span class="tag project">Project skill files</span><span class="tag memory">MCP setup examples</span><a class="small" href="/live">Recorded sessions ' + icon('chevron', true) + '</a></div>' +
1693
2057
  (agentRows ? '<ul class="list">' + agentRows + '</ul>' : blank('integrations', 'No agent data', 'Run checks to load the support catalog.')) +
1694
2058
  '</section></div>';
1695
2059
  }
@@ -1725,18 +2089,25 @@ async function copyIntegrationText(value, message, button) {
1725
2089
  }
1726
2090
  }
1727
2091
 
2092
+ function projectFeatureLimit(id) {
2093
+ if (document.documentElement.dataset.projectLocal !== 'true') return undefined;
2094
+ return { crossProjectContext: 'Project memory stays scoped to this project.', automaticBriefing: 'Automatic briefing requires a legacy analysis session.' }[id];
2095
+ }
2096
+
1728
2097
  function renderFeatures() {
1729
2098
  const target = $('feature-list');
1730
2099
  if (!state.features) return;
1731
2100
  const current = state.features.features;
1732
2101
  const overrides = new Map(list(state.features.definitions).map((item) => [item.id, item]));
2102
+ const icons = { contextAssembly: 'context', crossProjectContext: 'graph', automaticBriefing: 'agent', liveActivity: 'activity', activityRecording: 'memory' };
1733
2103
  target.innerHTML = '<ul class="list">' + definitions.map((definition) => {
1734
2104
  const display = overrides.get(definition.id) || definition;
1735
- const on = current[definition.id] === true;
1736
- return '<li class="setting"><div><h3 id="label-' + definition.id + '">' + esc(display.label) + '</h3><p class="muted small" id="desc-' +
1737
- definition.id + '">' + esc(display.description) + '</p></div><button type="button" class="button switch" role="switch" aria-checked="' +
2105
+ const reason = projectFeatureLimit(definition.id);
2106
+ const on = !reason && current[definition.id] === true;
2107
+ return '<li class="setting"><div class="settings-description"><span class="tile-icon">' + icon(icons[definition.id], true) + '</span><div><h3 id="label-' + definition.id + '">' + esc(display.label) + '</h3><p class="muted small" id="desc-' +
2108
+ definition.id + '">' + esc(reason || display.description) + '</p></div></div><button type="button" class="button switch" role="switch" aria-checked="' +
1738
2109
  on + '" aria-labelledby="label-' + definition.id + '" aria-describedby="desc-' + definition.id + '" data-feature="' + definition.id + '"' +
1739
- (state.featureSaving ? ' disabled' : '') + '><span aria-hidden="true">' + (on ? 'On' : 'Off') + '</span><span class="switch-track" aria-hidden="true"></span></button></li>';
2110
+ (state.featureSaving || reason ? ' disabled' : '') + '><span aria-hidden="true">' + (reason ? 'Unavailable' : on ? 'On' : 'Off') + '</span><span class="switch-track" aria-hidden="true"></span></button></li>';
1740
2111
  }).join('') + '</ul>';
1741
2112
  }
1742
2113
 
@@ -1749,11 +2120,17 @@ function applyFeatures(snapshot) {
1749
2120
  if (state.features && snapshot.revision < state.features.revision) return;
1750
2121
  state.features = snapshot;
1751
2122
  state.activityEnabled = current.liveActivity;
1752
- if (before?.crossProjectContext && !current.crossProjectContext && state.context.pack?.scope === 'cross-project') state.context.dirty = true;
2123
+ const context = state.context;
2124
+ if (!current.contextAssembly || !current.crossProjectContext && context.params?.includeCrossProject) {
2125
+ context.pack = null;
2126
+ context.params = null;
2127
+ context.comparison = null;
2128
+ context.comparing = false;
2129
+ }
1753
2130
  renderFeatures();
1754
2131
  renderActivity();
1755
2132
  renderContextControls();
1756
- if (state.context.pack && !state.context.pending) renderContext();
2133
+ if (!context.pending) renderContext();
1757
2134
  if (current.liveActivity && before?.liveActivity !== true) void refreshActivity();
1758
2135
  }
1759
2136
 
@@ -1788,7 +2165,7 @@ async function loadFeatures(force = false) {
1788
2165
  }
1789
2166
 
1790
2167
  async function toggleFeature(key) {
1791
- if (state.featureSaving || !state.features || !definitions.some((definition) => definition.id === key)) return;
2168
+ if (state.featureSaving || !state.features || projectFeatureLimit(key) || !definitions.some((definition) => definition.id === key)) return;
1792
2169
  state.featureSaving = true;
1793
2170
  $('feature-list').querySelectorAll('[data-feature]').forEach((button) => { button.disabled = true; });
1794
2171
  featureStatus('Saving…');
@@ -1859,12 +2236,10 @@ function changeView(moveFocus = false) {
1859
2236
  for (const name of Object.keys(viewNames)) {
1860
2237
  const section = $('page-' + name);
1861
2238
  section.hidden = name !== view;
1862
- section.classList.remove('entered');
1863
2239
  const link = document.querySelector('[data-view="' + name + '"]');
1864
2240
  if (name === view || name === 'memories' && view === 'memory') link?.setAttribute('aria-current', 'page');
1865
2241
  else link?.removeAttribute('aria-current');
1866
2242
  }
1867
- if (previous !== view) $('page-' + view).classList.add('entered');
1868
2243
  if (moveFocus && (previous !== view || view === 'memory')) {
1869
2244
  window.scrollTo(0, 0);
1870
2245
  $('title-' + view).focus({ preventScroll: true });
@@ -1906,9 +2281,9 @@ function changeView(moveFocus = false) {
1906
2281
  if (project) {
1907
2282
  state.context.routeProject = project;
1908
2283
  syncProjectOptions();
1909
- if (state.context.pack && state.context.pack.projectId !== project) {
2284
+ if (state.context.params && state.context.params.projectId !== project) {
1910
2285
  state.context.dirty = true;
1911
- renderContext();
2286
+ if (!state.context.pending) renderContext();
1912
2287
  }
1913
2288
  }
1914
2289
  void loadOverview();
@@ -1919,6 +2294,9 @@ function changeView(moveFocus = false) {
1919
2294
  featureStatus('');
1920
2295
  void loadFeatures(true);
1921
2296
  break;
2297
+ case 'project-kit':
2298
+ void loadProjectKit();
2299
+ break;
1922
2300
  case 'integrations':
1923
2301
  if (state.integrations.snapshot) renderIntegrations(state.integrations.snapshot);
1924
2302
  else void loadIntegrations();
@@ -1986,8 +2364,7 @@ $('theme-setting').addEventListener('change', (event) => setPreference('theme',
1986
2364
  $('motion-setting').addEventListener('change', (event) => setPreference('motion', event.target.value));
1987
2365
  $('memory-form').addEventListener('submit', (event) => { event.preventDefault(); changeMemorySearch(); });
1988
2366
  ['memory-project', 'memory-category', 'memory-agent', 'memory-state'].forEach((id) => $(id).addEventListener('change', changeMemorySearch));
1989
- $('overview-body').addEventListener('change', (event) => {
1990
- if (event.target.id !== 'overview-quality-project') return;
2367
+ $('overview-quality-project').addEventListener('change', (event) => {
1991
2368
  const project = event.target.value;
1992
2369
  const route = routeLink('overview', { project });
1993
2370
  window.history.replaceState(null, '', route);
@@ -1996,6 +2373,14 @@ $('overview-body').addEventListener('change', (event) => {
1996
2373
  if (state.view === 'overview' && state.route === route) $('overview-quality-project')?.focus();
1997
2374
  });
1998
2375
  });
2376
+ $('overview-body').addEventListener('change', (event) => {
2377
+ if (event.target.id !== 'attention-filter') return;
2378
+ state.health.filter = event.target.value;
2379
+ renderAttention();
2380
+ });
2381
+ $('overview-body').addEventListener('click', (event) => {
2382
+ if (event.target.closest('#attention-jump')) $('attention-heading')?.focus();
2383
+ });
1999
2384
  $('integrations-refresh').addEventListener('click', () => { void loadIntegrations(); });
2000
2385
  $('graph-query').addEventListener('input', renderGraph);
2001
2386
  $('graph-kind').addEventListener('change', renderGraph);
@@ -2012,6 +2397,13 @@ $('graph-project').addEventListener('change', (event) => {
2012
2397
  renderGraph();
2013
2398
  }));
2014
2399
  $('graph-fit').addEventListener('click', () => fitGraph());
2400
+ $('graph-lens-clear').addEventListener('click', () => {
2401
+ const anchor = state.graph.lens;
2402
+ state.graph.lens = '';
2403
+ renderGraph();
2404
+ if (state.graph.nodes.some((node) => node.id === anchor)) selectGraphNode(anchor, { focus: true });
2405
+ else $('graph-query').focus();
2406
+ });
2015
2407
  $('graph-in').addEventListener('click', () => zoomGraph(1.35));
2016
2408
  $('graph-out').addEventListener('click', () => zoomGraph(1 / 1.35));
2017
2409
  $('graph-svg').addEventListener('keydown', handleGraphKeyboard);
@@ -2048,6 +2440,7 @@ window.addEventListener('resize', () => {
2048
2440
  $('context-form').addEventListener('submit', (event) => { event.preventDefault(); void assembleContext(); });
2049
2441
  $('context-form').addEventListener('input', () => {
2050
2442
  $('context-task').setCustomValidity('');
2443
+ $('context-related-row').hidden = $('context-profile').value !== 'adaptive';
2051
2444
  const store = state.context;
2052
2445
  if (store.pack || store.pending) {
2053
2446
  if (!store.dirty) {
@@ -2076,12 +2469,14 @@ $('detail-body').addEventListener('submit', (event) => {
2076
2469
  });
2077
2470
 
2078
2471
  document.addEventListener('click', (event) => {
2079
- const element = event.target.closest('[data-retry],[data-memory-clear],[data-memory-page],[data-node],[data-related],[data-edge-record],[data-feature],[data-unlink],[data-copy-command],#diagnostics-copy,#context-copy,.skip');
2472
+ const element = event.target.closest('[data-retry],[data-memory-clear],[data-memory-page],[data-node],[data-related],[data-edge-record],[data-graph-lens],[data-feature],[data-unlink],[data-copy-command],#diagnostics-copy,#context-copy,#context-download,#context-compare-button,.skip');
2080
2473
  if (!element) return;
2081
2474
  const data = element.dataset;
2082
2475
  if (data.retry) {
2083
2476
  switch (data.retry) {
2084
2477
  case 'overview': void loadOverview(true, state.view === 'overview' ? routeParams().get('project') || '' : ''); break;
2478
+ case 'health': void loadHealth(state.overviewScope); break;
2479
+ case 'kit-profile': void loadProjectKit(true); break;
2085
2480
  case 'memories': void loadMemories(state.memory.query || memoryQuery(), state.memory.offset); break;
2086
2481
  case 'graph': void loadGraph(state.graph.project, true, state.graph.focusMemory); break;
2087
2482
  case 'detail': void loadDetail(state.detail.id, state.detail.project); break;
@@ -2104,6 +2499,11 @@ document.addEventListener('click', (event) => {
2104
2499
  graphSelectFromList(data.related, true);
2105
2500
  } else if (data.edgeRecord) {
2106
2501
  selectGraphEdge(data.edgeRecord, $('graph-inspector').contains(element));
2502
+ } else if (data.graphLens) {
2503
+ state.graph.lens = data.graphLens;
2504
+ ['graph-query', 'graph-kind', 'graph-relation'].forEach((id) => { $(id).value = ''; });
2505
+ renderGraph();
2506
+ $('graph-lens-clear').focus();
2107
2507
  } else if (data.unlink) {
2108
2508
  void saveDetail('memory-unlink', { memoryId: state.detail.id, linkId: data.unlink }, 'link-editor');
2109
2509
  } else if (data.copyCommand) {
@@ -2112,6 +2512,10 @@ document.addEventListener('click', (event) => {
2112
2512
  void toggleFeature(data.feature);
2113
2513
  } else if (element.id === 'context-copy') {
2114
2514
  void copyContext();
2515
+ } else if (element.id === 'context-download') {
2516
+ downloadContext();
2517
+ } else if (element.id === 'context-compare-button') {
2518
+ void compareContext();
2115
2519
  } else if (element.id === 'diagnostics-copy') {
2116
2520
  void copyIntegrationText(state.integrations.snapshot?.diagnostics, 'Diagnostic report copied.', element);
2117
2521
  } else if (element.classList.contains('skip')) {
@@ -2120,6 +2524,8 @@ document.addEventListener('click', (event) => {
2120
2524
  }
2121
2525
  });
2122
2526
 
2527
+ ${PROJECT_KIT_SCRIPT}
2528
+
2123
2529
  window.addEventListener('hashchange', () => changeView(true));
2124
2530
  document.addEventListener('visibilitychange', () => { if (!document.hidden) void refreshActivity(); });
2125
2531
  setInterval(() => { void refreshActivity(); }, 10000);