pi-mega-compact 0.13.4 → 0.13.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/README.md +11 -8
  2. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +50 -0
  3. package/dist/extensions/dashboard-server/api-contracts/rag-metrics.js +1 -0
  4. package/dist/extensions/dashboard-server/api-contracts/rag-settings.js +3 -3
  5. package/dist/extensions/dashboard-server/api-contracts/wiki.js +1 -0
  6. package/dist/extensions/dashboard-server/routes-rag-metrics.js +60 -0
  7. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +167 -0
  8. package/dist/extensions/dashboard-server/routes-rag-settings.js +136 -77
  9. package/dist/extensions/dashboard-server/routes-wiki-helpers.js +97 -0
  10. package/dist/extensions/dashboard-server/routes-wiki.js +330 -0
  11. package/dist/extensions/dashboard-server/routes.js +2 -0
  12. package/dist/extensions/dashboard-server/server.js +17 -3
  13. package/dist/extensions/mega-events/context-handler/afterCompact.js +35 -2
  14. package/dist/extensions/mega-pipeline/recall.js +31 -0
  15. package/dist/extensions/mega-turn-store.js +2 -1
  16. package/dist/src/config/turns.js +10 -0
  17. package/dist/src/config.js +2 -0
  18. package/dist/src/recall/async.js +10 -1
  19. package/dist/src/recall/format.js +10 -1
  20. package/dist/src/recall/hydeTelemetry.js +22 -0
  21. package/dist/src/recall/sync.js +28 -2
  22. package/dist/src/store/turns/hydeStore.js +32 -0
  23. package/dist/src/store/turns/index.js +1 -0
  24. package/dist/src/store/turns/schema.js +102 -1
  25. package/dist/src/store/turns/sqlite-store/rows.js +23 -0
  26. package/dist/src/store/turns/sqlite-store/write.js +7 -3
  27. package/dist/src/topics/durability.js +187 -0
  28. package/dist/src/topics/index.js +2 -1
  29. package/dist/src/topics/store.js +28 -9
  30. package/dist/src/wiki/curation-helpers.js +71 -0
  31. package/dist/src/wiki/curation.js +147 -0
  32. package/dist/src/wiki/incremental.js +126 -0
  33. package/dist/src/wiki/index.js +5 -0
  34. package/extensions/dashboard-client/dist/assets/AreaChart-KAmp_6bL.js +70 -0
  35. package/extensions/dashboard-client/dist/assets/{SessionsTab-C30AwyoF.js.map → AreaChart-KAmp_6bL.js.map} +1 -1
  36. package/extensions/dashboard-client/dist/assets/CacheTab-Bk-gm-R0.js +2 -0
  37. package/extensions/dashboard-client/dist/assets/CacheTab-Bk-gm-R0.js.map +1 -0
  38. package/extensions/dashboard-client/dist/assets/EventsTab-Cyylo0of.js +2 -0
  39. package/extensions/dashboard-client/dist/assets/EventsTab-Cyylo0of.js.map +1 -0
  40. package/extensions/dashboard-client/dist/assets/HealthTab-D-IzmV29.js +2 -0
  41. package/extensions/dashboard-client/dist/assets/HealthTab-D-IzmV29.js.map +1 -0
  42. package/extensions/dashboard-client/dist/assets/MaintenanceTab-CrlkCFVj.js +2 -0
  43. package/extensions/dashboard-client/dist/assets/MaintenanceTab-CrlkCFVj.js.map +1 -0
  44. package/extensions/dashboard-client/dist/assets/MemoryMapTab-DHUdZ7RR.js +2 -0
  45. package/extensions/dashboard-client/dist/assets/MemoryMapTab-DHUdZ7RR.js.map +1 -0
  46. package/extensions/dashboard-client/dist/assets/MetricsTab-B7I4HWVg.js +2 -0
  47. package/extensions/dashboard-client/dist/assets/MetricsTab-B7I4HWVg.js.map +1 -0
  48. package/extensions/dashboard-client/dist/assets/OverviewTab-K6j4ZI7u.js +2 -0
  49. package/extensions/dashboard-client/dist/assets/OverviewTab-K6j4ZI7u.js.map +1 -0
  50. package/extensions/dashboard-client/dist/assets/ReposTab-BSRWhn56.js +2 -0
  51. package/extensions/dashboard-client/dist/assets/ReposTab-BSRWhn56.js.map +1 -0
  52. package/extensions/dashboard-client/dist/assets/SessionsTab-BXdizHN1.js +2 -0
  53. package/extensions/dashboard-client/dist/assets/SessionsTab-BXdizHN1.js.map +1 -0
  54. package/extensions/dashboard-client/dist/assets/SetupTab-CAgyfqY3.js +2 -0
  55. package/extensions/dashboard-client/dist/assets/SetupTab-CAgyfqY3.js.map +1 -0
  56. package/extensions/dashboard-client/dist/assets/TimeSavedCard-DwgCzPD9.js +2 -0
  57. package/extensions/dashboard-client/dist/assets/TimeSavedCard-DwgCzPD9.js.map +1 -0
  58. package/extensions/dashboard-client/dist/assets/TurnsTab-CVKcStBp.js +5 -0
  59. package/extensions/dashboard-client/dist/assets/TurnsTab-CVKcStBp.js.map +1 -0
  60. package/extensions/dashboard-client/dist/assets/WikiTab-BNohxTbs.js +2 -0
  61. package/extensions/dashboard-client/dist/assets/WikiTab-BNohxTbs.js.map +1 -0
  62. package/extensions/dashboard-client/dist/assets/button-B4A-_dCA.js +2 -0
  63. package/extensions/dashboard-client/dist/assets/button-B4A-_dCA.js.map +1 -0
  64. package/extensions/dashboard-client/dist/assets/card-Cjn7Ifoe.js +2 -0
  65. package/extensions/dashboard-client/dist/assets/card-Cjn7Ifoe.js.map +1 -0
  66. package/extensions/dashboard-client/dist/assets/index-CQjCLvv7.js +137 -0
  67. package/extensions/dashboard-client/dist/assets/index-CQjCLvv7.js.map +1 -0
  68. package/extensions/dashboard-client/dist/assets/index-CUA6EDRF.css +1 -0
  69. package/extensions/dashboard-client/dist/assets/switch-CcdXX0Zm.js +2 -0
  70. package/extensions/dashboard-client/dist/assets/switch-CcdXX0Zm.js.map +1 -0
  71. package/extensions/dashboard-client/dist/assets/toggle-KdBnFLu3.js +2 -0
  72. package/extensions/dashboard-client/dist/assets/toggle-KdBnFLu3.js.map +1 -0
  73. package/extensions/dashboard-client/dist/assets/{useSSE-p1pOI_-P.js → useSSE-B6t44vzc.js} +2 -2
  74. package/extensions/dashboard-client/dist/assets/{useSSE-p1pOI_-P.js.map → useSSE-B6t44vzc.js.map} +1 -1
  75. package/extensions/dashboard-client/dist/index.html +2 -2
  76. package/extensions/dashboard-client/package-lock.json +1995 -191
  77. package/extensions/dashboard-client/package.json +12 -1
  78. package/extensions/dashboard-client/postcss.config.js +6 -0
  79. package/extensions/dashboard-client/src/App.tsx +127 -88
  80. package/extensions/dashboard-client/src/api/client.ts +109 -10
  81. package/extensions/dashboard-client/src/components/ActiveReposTable.tsx +15 -7
  82. package/extensions/dashboard-client/src/components/ActiveSessionsTable.tsx +67 -55
  83. package/extensions/dashboard-client/src/components/CacheHitRateTrendCard.tsx +17 -13
  84. package/extensions/dashboard-client/src/components/CacheHitsCard.tsx +35 -30
  85. package/extensions/dashboard-client/src/components/CacheStatusPerModel.tsx +33 -28
  86. package/extensions/dashboard-client/src/components/EventCategoryFilter.tsx +5 -6
  87. package/extensions/dashboard-client/src/components/EventStream.tsx +17 -13
  88. package/extensions/dashboard-client/src/components/HydeDetailPanel.tsx +173 -0
  89. package/extensions/dashboard-client/src/components/LegendCard.tsx +16 -9
  90. package/extensions/dashboard-client/src/components/PerfCards.tsx +28 -20
  91. package/extensions/dashboard-client/src/components/PerfChart.tsx +9 -6
  92. package/extensions/dashboard-client/src/components/ProviderCacheCard.tsx +15 -7
  93. package/extensions/dashboard-client/src/components/RagDashboard.tsx +253 -0
  94. package/extensions/dashboard-client/src/components/RepoAllSessionsCard.tsx +15 -10
  95. package/extensions/dashboard-client/src/components/RepoContextStack.tsx +24 -16
  96. package/extensions/dashboard-client/src/components/RepoDetailModal.tsx +52 -49
  97. package/extensions/dashboard-client/src/components/RepoTable.tsx +26 -17
  98. package/extensions/dashboard-client/src/components/SessionContextGauges.tsx +11 -14
  99. package/extensions/dashboard-client/src/components/SessionInfo.tsx +20 -15
  100. package/extensions/dashboard-client/src/components/SessionsMemoryChart.tsx +8 -5
  101. package/extensions/dashboard-client/src/components/SummaryTiles.tsx +26 -17
  102. package/extensions/dashboard-client/src/components/TabBar.tsx +1 -3
  103. package/extensions/dashboard-client/src/components/layout/AppShell.tsx +42 -0
  104. package/extensions/dashboard-client/src/components/layout/BottomBar.tsx +88 -0
  105. package/extensions/dashboard-client/src/components/layout/Sidebar.tsx +83 -0
  106. package/extensions/dashboard-client/src/components/ui/badge.tsx +30 -0
  107. package/extensions/dashboard-client/src/components/ui/button.tsx +44 -0
  108. package/extensions/dashboard-client/src/components/ui/card.tsx +32 -0
  109. package/extensions/dashboard-client/src/components/ui/select.tsx +64 -0
  110. package/extensions/dashboard-client/src/components/ui/switch.tsx +26 -0
  111. package/extensions/dashboard-client/src/components/ui/tabs.tsx +36 -0
  112. package/extensions/dashboard-client/src/components/ui/toggle.tsx +28 -0
  113. package/extensions/dashboard-client/src/components/ui/tooltip.tsx +27 -0
  114. package/extensions/dashboard-client/src/config.ts +10 -0
  115. package/extensions/dashboard-client/src/main.tsx +2 -2
  116. package/extensions/dashboard-client/src/memory-map-layout.ts +1 -2
  117. package/extensions/dashboard-client/src/styles/index.css +79 -0
  118. package/extensions/dashboard-client/src/tabs/CacheTab.tsx +33 -26
  119. package/extensions/dashboard-client/src/tabs/EventsTab.tsx +22 -7
  120. package/extensions/dashboard-client/src/tabs/HealthTab.tsx +77 -46
  121. package/extensions/dashboard-client/src/tabs/MaintenanceTab/ActionsCard.tsx +60 -50
  122. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DbStatsCard.tsx +61 -35
  123. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DebugBundleCard.tsx +34 -23
  124. package/extensions/dashboard-client/src/tabs/MaintenanceTab/HealthMitigationCard.tsx +62 -43
  125. package/extensions/dashboard-client/src/tabs/MaintenanceTab/SchemaHealthCard.tsx +53 -32
  126. package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +2 -2
  127. package/extensions/dashboard-client/src/tabs/MemoryMapTab/MemoryMapView.tsx +29 -46
  128. package/extensions/dashboard-client/src/tabs/MemoryMapTab/RaptorTreeView.tsx +10 -34
  129. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +14 -34
  130. package/extensions/dashboard-client/src/tabs/MetricsTab.tsx +5 -2
  131. package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +11 -5
  132. package/extensions/dashboard-client/src/tabs/ReposTab.tsx +5 -5
  133. package/extensions/dashboard-client/src/tabs/SessionsTab.tsx +47 -33
  134. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +6 -2
  135. package/extensions/dashboard-client/src/tabs/SetupTab/RagHealthCard.tsx +78 -0
  136. package/extensions/dashboard-client/src/tabs/SetupTab/SettingsPanel.tsx +198 -0
  137. package/extensions/dashboard-client/src/tabs/SetupTab/SettingsSection.tsx +50 -0
  138. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +21 -59
  139. package/extensions/dashboard-client/src/tabs/TopicsTab.tsx +75 -40
  140. package/extensions/dashboard-client/src/tabs/TurnsTab.tsx +150 -112
  141. package/extensions/dashboard-client/src/tabs/WikiTab/TopicEvolutionGraph.tsx +274 -0
  142. package/extensions/dashboard-client/src/tabs/WikiTab/TopicEvolutionView.tsx +71 -0
  143. package/extensions/dashboard-client/src/tabs/WikiTab/TopicTimeline.tsx +103 -0
  144. package/extensions/dashboard-client/src/tabs/WikiTab/WikiPage.tsx +213 -0
  145. package/extensions/dashboard-client/src/tabs/WikiTab/WikiPageControls.tsx +188 -0
  146. package/extensions/dashboard-client/src/tabs/WikiTab.tsx +222 -0
  147. package/extensions/dashboard-client/src/tabs/registry.ts +57 -0
  148. package/extensions/dashboard-client/src/utils/cn.ts +7 -0
  149. package/extensions/dashboard-client/tailwind.config.js +46 -0
  150. package/extensions/dashboard-server/api-contracts/core.ts +59 -0
  151. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +79 -0
  152. package/extensions/dashboard-server/api-contracts/index.ts +48 -2
  153. package/extensions/dashboard-server/api-contracts/rag-metrics.ts +22 -0
  154. package/extensions/dashboard-server/api-contracts/rag-settings.ts +30 -20
  155. package/extensions/dashboard-server/api-contracts/wiki.ts +138 -0
  156. package/extensions/dashboard-server/routes-rag-metrics.ts +82 -0
  157. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +314 -0
  158. package/extensions/dashboard-server/routes-rag-settings.ts +147 -97
  159. package/extensions/dashboard-server/routes-wiki-helpers.ts +126 -0
  160. package/extensions/dashboard-server/routes-wiki.ts +376 -0
  161. package/extensions/dashboard-server/routes.ts +2 -0
  162. package/extensions/dashboard-server/server.ts +18 -2
  163. package/extensions/mega-events/context-handler/afterCompact.ts +44 -1
  164. package/extensions/mega-pipeline/recall.ts +31 -0
  165. package/extensions/mega-turn-store.ts +13 -0
  166. package/package.json +1 -1
  167. package/src/config/turns.ts +17 -0
  168. package/src/config.ts +3 -0
  169. package/src/recall/async.ts +10 -1
  170. package/src/recall/format.ts +15 -2
  171. package/src/recall/hydeTelemetry.ts +44 -0
  172. package/src/recall/sync.ts +34 -2
  173. package/src/recall/types.ts +36 -0
  174. package/src/store/turns/hydeStore.ts +82 -0
  175. package/src/store/turns/index.ts +4 -0
  176. package/src/store/turns/schema.ts +126 -1
  177. package/src/store/turns/sqlite-store/rows.ts +25 -0
  178. package/src/store/turns/sqlite-store/write.ts +20 -2
  179. package/src/store/turns/types.ts +33 -0
  180. package/src/topics/durability.ts +231 -0
  181. package/src/topics/index.ts +7 -1
  182. package/src/topics/store.ts +35 -9
  183. package/src/wiki/curation-helpers.ts +140 -0
  184. package/src/wiki/curation.ts +217 -0
  185. package/src/wiki/incremental.ts +164 -0
  186. package/src/wiki/index.ts +13 -0
  187. package/extensions/dashboard-client/dist/assets/CacheTab-Cqnc7lMg.js +0 -2
  188. package/extensions/dashboard-client/dist/assets/CacheTab-Cqnc7lMg.js.map +0 -1
  189. package/extensions/dashboard-client/dist/assets/EventsTab-tYk3VFAp.js +0 -2
  190. package/extensions/dashboard-client/dist/assets/EventsTab-tYk3VFAp.js.map +0 -1
  191. package/extensions/dashboard-client/dist/assets/HealthTab-D2_kjgxw.js +0 -2
  192. package/extensions/dashboard-client/dist/assets/HealthTab-D2_kjgxw.js.map +0 -1
  193. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Bc-uPYrz.js +0 -2
  194. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Bc-uPYrz.js.map +0 -1
  195. package/extensions/dashboard-client/dist/assets/MemoryMapTab-D1V9g_NC.js +0 -2
  196. package/extensions/dashboard-client/dist/assets/MemoryMapTab-D1V9g_NC.js.map +0 -1
  197. package/extensions/dashboard-client/dist/assets/MetricsTab-DT_XfL96.js +0 -2
  198. package/extensions/dashboard-client/dist/assets/MetricsTab-DT_XfL96.js.map +0 -1
  199. package/extensions/dashboard-client/dist/assets/OverviewTab-BY6KZCme.js +0 -2
  200. package/extensions/dashboard-client/dist/assets/OverviewTab-BY6KZCme.js.map +0 -1
  201. package/extensions/dashboard-client/dist/assets/ReposTab-CeS-tpDM.js +0 -2
  202. package/extensions/dashboard-client/dist/assets/ReposTab-CeS-tpDM.js.map +0 -1
  203. package/extensions/dashboard-client/dist/assets/SessionsTab-C30AwyoF.js +0 -70
  204. package/extensions/dashboard-client/dist/assets/SetupTab-BJUHOVK2.js +0 -2
  205. package/extensions/dashboard-client/dist/assets/SetupTab-BJUHOVK2.js.map +0 -1
  206. package/extensions/dashboard-client/dist/assets/TimeSavedCard-p55K1tFP.js +0 -2
  207. package/extensions/dashboard-client/dist/assets/TimeSavedCard-p55K1tFP.js.map +0 -1
  208. package/extensions/dashboard-client/dist/assets/TopicsTab-D8FqyAbK.js +0 -2
  209. package/extensions/dashboard-client/dist/assets/TopicsTab-D8FqyAbK.js.map +0 -1
  210. package/extensions/dashboard-client/dist/assets/TurnsTab-ju-nXDys.js +0 -2
  211. package/extensions/dashboard-client/dist/assets/TurnsTab-ju-nXDys.js.map +0 -1
  212. package/extensions/dashboard-client/dist/assets/index-BEj1Zahy.js +0 -42
  213. package/extensions/dashboard-client/dist/assets/index-BEj1Zahy.js.map +0 -1
  214. package/extensions/dashboard-client/dist/assets/index-CUNn22iV.css +0 -1
  215. package/extensions/dashboard-client/src/styles/metrics-extra.css +0 -69
  216. package/extensions/dashboard-client/src/styles/turns.css +0 -142
  217. package/extensions/dashboard-client/src/tabs/SetupTab/RagSettingsCard.tsx +0 -160
@@ -0,0 +1,97 @@
1
+ /**
2
+ * routes-wiki-helpers.ts — shared read/build helpers for routes-wiki.ts.
3
+ *
4
+ * Content access, index-entry resolution, provenance + time-bucket builders,
5
+ * lazy topic-model build, and SSE emission. Kept out of the dispatcher so
6
+ * routes-wiki.ts stays a thin pointer. Loopback-only (PREVENT-PI-004),
7
+ * parameterized (PREVENT-002).
8
+ */
9
+ import { appendFileSync } from "node:fs";
10
+ import { openStore } from "../../src/store/sqlite.js";
11
+ import { createTopicStore } from "../../src/topics/store.js";
12
+ import { buildTopicModel } from "../../src/topics/cluster.js";
13
+ /** Append a JSONL SSE line to events.log (best-effort, non-fatal). */
14
+ export function emitWikiEvent(ctx, event) {
15
+ try {
16
+ appendFileSync(ctx.eventsPath, JSON.stringify({ ts: new Date().toISOString(), ...event }) + "\n");
17
+ }
18
+ catch {
19
+ /* non-fatal: SSE push best-effort */
20
+ }
21
+ }
22
+ /** Lazy-build the topic model when the topics table is empty (best-effort). */
23
+ export function ensureWikiBuilt(ctx, tdb) {
24
+ const topicCount = tdb.prepare("SELECT COUNT(*) AS c FROM topics").get().c;
25
+ if (topicCount > 0)
26
+ return;
27
+ try {
28
+ const mainDb = openStore(ctx.stateDir);
29
+ const model = buildTopicModel(mainDb);
30
+ if (model.k > 0 && model.totalChunks > 0) {
31
+ createTopicStore(ctx.stateDir).replaceTopicModel(model);
32
+ }
33
+ }
34
+ catch {
35
+ /* non-fatal: lazy wiki build is best-effort */
36
+ }
37
+ }
38
+ /** Build a topic index entry with resolved label + override badges. */
39
+ export function toIndexEntry(topic, curation) {
40
+ const resolved = curation.resolveLabel(topic.id, topic.label);
41
+ return {
42
+ id: topic.id,
43
+ label: resolved.label,
44
+ memoryCount: topic.memoryCount,
45
+ lastUpdated: topic.lastUpdated,
46
+ edited: resolved.edited,
47
+ overrideKinds: curation.overrideKinds(topic.id),
48
+ };
49
+ }
50
+ /** Convert a member assignment into contract provenance. */
51
+ export function toProvenance(member) {
52
+ return {
53
+ memoryId: member.memoryId,
54
+ sessionId: member.sessionId ?? "",
55
+ assignedAt: member.assignedAt,
56
+ method: member.method,
57
+ };
58
+ }
59
+ /** Bucket epoch-ms values by day; returns ascending [{bucket, count}]. */
60
+ export function bucketize(values) {
61
+ const DAY = 86_400_000;
62
+ const counts = new Map();
63
+ for (const v of values) {
64
+ if (!Number.isFinite(v) || v <= 0)
65
+ continue;
66
+ const b = Math.floor(v / DAY) * DAY;
67
+ counts.set(b, (counts.get(b) ?? 0) + 1);
68
+ }
69
+ return [...counts.entries()]
70
+ .sort((a, b) => a[0] - b[0])
71
+ .map(([bucket, count]) => ({ bucket, count }));
72
+ }
73
+ /** Fetch member content (text + timestamp) from the main DB. */
74
+ export function memberContent(ctx, members) {
75
+ const out = new Map();
76
+ if (members.length === 0)
77
+ return out;
78
+ try {
79
+ const mainDb = openStore(ctx.stateDir);
80
+ for (const m of members) {
81
+ const row = mainDb
82
+ .prepare(`SELECT COALESCE(normalized_text, summary, topic_summary) AS text, timestamp
83
+ FROM context_chunks WHERE session_id = ? AND id = ?`)
84
+ .get(m.sessionId ?? "", m.memoryId);
85
+ if (row) {
86
+ out.set(m.memoryId, {
87
+ text: (row.text ?? "").trim(),
88
+ timestamp: row.timestamp ?? 0,
89
+ });
90
+ }
91
+ }
92
+ }
93
+ catch {
94
+ /* content fetch is best-effort */
95
+ }
96
+ return out;
97
+ }
@@ -0,0 +1,330 @@
1
+ /**
2
+ * dashboard-server/routes-wiki.ts — W2 wiki revival routes (curation + provenance).
3
+ *
4
+ * GET /api/wiki/index — topics with resolved labels
5
+ * GET /api/wiki/evolution — D3 nodes/edges/time buckets
6
+ * GET /api/wiki/topic/:topicId — single topic page + provenance
7
+ * GET /api/wiki/topic/:topicId/timeline — per-topic time buckets
8
+ * PUT /api/wiki/topic/:topicId/label — rename (emits wiki_topic_renamed)
9
+ * POST /api/wiki/merge — merge (emits wiki_topics_merged)
10
+ * POST /api/wiki/topic/:topicId/split — split (emits wiki_topic_split)
11
+ *
12
+ * Flag-gated by MEGACOMPACT_WIKI_ENHANCED (flag-OFF → 404 for all /api/wiki/*).
13
+ * Read/build helpers live in routes-wiki-helpers.ts (pointer-file split).
14
+ * Loopback-only (PREVENT-PI-004); parameterized (PREVENT-002); no `any`.
15
+ */
16
+ import { TurnsConfig } from "../../src/config/turns.js";
17
+ import { openTurnStore } from "../../src/store/turns/connection.js";
18
+ import { createTopicStore } from "../../src/topics/store.js";
19
+ import { extractiveSummary } from "../../src/wiki.js";
20
+ import { createWikiCuration } from "../../src/wiki/curation.js";
21
+ import { emitWikiEvent, ensureWikiBuilt, toIndexEntry, toProvenance, bucketize, memberContent, } from "./routes-wiki-helpers.js";
22
+ function sendJson(res, status, body) {
23
+ // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
24
+ res.writeHead(status, { "Content-Type": "application/json" });
25
+ res.end(JSON.stringify(body));
26
+ }
27
+ /** Decode a percent-encoded path segment; null on malformed encoding (%zz). */
28
+ function decodeSegment(raw) {
29
+ try {
30
+ return decodeURIComponent(raw);
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ /** Read a capped JSON body; returns { ok, value } or { error }. */
37
+ function readJsonBody(req, cb) {
38
+ let body = "";
39
+ let tooBig = false;
40
+ req.on("data", (chunk) => {
41
+ if (body.length > 65536) {
42
+ tooBig = true;
43
+ return;
44
+ }
45
+ body += chunk.toString();
46
+ });
47
+ req.on("end", () => {
48
+ if (tooBig)
49
+ return cb({ ok: false, error: "body_too_large" });
50
+ try {
51
+ const v = body ? JSON.parse(body) : {};
52
+ if (typeof v !== "object" || v === null || Array.isArray(v)) {
53
+ return cb({ ok: false, error: "invalid_object" });
54
+ }
55
+ cb({ ok: true, value: v });
56
+ }
57
+ catch {
58
+ cb({ ok: false, error: "invalid_json" });
59
+ }
60
+ });
61
+ }
62
+ export function handleWiki(req, res, ctx) {
63
+ const url = req.url ?? "";
64
+ if (!url.startsWith("/api/wiki"))
65
+ return false;
66
+ // Flag gate: feature-OFF → 404 for every wiki endpoint.
67
+ if (!TurnsConfig.WIKI_ENHANCED_ENABLED) {
68
+ sendJson(res, 404, { error: "wiki_enhanced_disabled" });
69
+ return true;
70
+ }
71
+ // ── GET /api/wiki/index ──────────────────────────────────────────
72
+ if (req.method === "GET" && url === "/api/wiki/index") {
73
+ try {
74
+ const tdb = openTurnStore(ctx.stateDir);
75
+ ensureWikiBuilt(ctx, tdb);
76
+ const store = createTopicStore(ctx.stateDir);
77
+ const curation = createWikiCuration(ctx.stateDir);
78
+ const entries = store.getTopics().map((t) => toIndexEntry(t, curation));
79
+ const totalMemories = entries.reduce((s, e) => s + e.memoryCount, 0);
80
+ const lastRebuildAt = entries.length > 0 ? Math.max(...entries.map((e) => e.lastUpdated)) : null;
81
+ const body = {
82
+ topics: entries,
83
+ totalTopics: entries.length,
84
+ totalMemories,
85
+ lastRebuildAt,
86
+ };
87
+ sendJson(res, 200, body);
88
+ }
89
+ catch (e) {
90
+ sendJson(res, 500, { error: String(e) });
91
+ }
92
+ return true;
93
+ }
94
+ // ── GET /api/wiki/evolution ──────────────────────────────────────
95
+ if (req.method === "GET" && url === "/api/wiki/evolution") {
96
+ try {
97
+ const tdb = openTurnStore(ctx.stateDir);
98
+ const curation = createWikiCuration(ctx.stateDir);
99
+ const nodes = createTopicStore(ctx.stateDir)
100
+ .getTopics()
101
+ .map((t) => ({
102
+ id: t.id,
103
+ label: curation.resolveLabel(t.id, t.label).label,
104
+ memoryCount: t.memoryCount,
105
+ }));
106
+ const overrides = tdb
107
+ .prepare(`SELECT kind, topic_id, merged_into, split_from, overridden_at
108
+ FROM topic_overrides WHERE kind IN ('merge','split')`)
109
+ .all();
110
+ const edges = overrides.flatMap((o) => {
111
+ if (o.kind === "merge" && o.merged_into) {
112
+ return [
113
+ {
114
+ source: o.topic_id,
115
+ target: o.merged_into,
116
+ kind: "merge",
117
+ at: o.overridden_at,
118
+ },
119
+ ];
120
+ }
121
+ if (o.kind === "split" && o.split_from) {
122
+ return [
123
+ {
124
+ source: o.split_from,
125
+ target: o.topic_id,
126
+ kind: "split",
127
+ at: o.overridden_at,
128
+ },
129
+ ];
130
+ }
131
+ return [];
132
+ });
133
+ const evoRows = tdb
134
+ .prepare("SELECT assigned_at FROM topic_evolution")
135
+ .all();
136
+ const body = {
137
+ nodes,
138
+ edges,
139
+ buckets: bucketize(evoRows.map((r) => r.assigned_at)),
140
+ };
141
+ sendJson(res, 200, body);
142
+ }
143
+ catch (e) {
144
+ sendJson(res, 500, { error: String(e) });
145
+ }
146
+ return true;
147
+ }
148
+ // ── GET /api/wiki/topic/:topicId/timeline ────────────────────────
149
+ const timelineMatch = req.method === "GET"
150
+ ? url.match(/^\/api\/wiki\/topic\/([^/?]+)\/timeline$/)
151
+ : null;
152
+ if (timelineMatch) {
153
+ const topicId = decodeSegment(timelineMatch[1]);
154
+ if (topicId === null) {
155
+ sendJson(res, 400, { error: "malformed_topic_id" });
156
+ return true;
157
+ }
158
+ try {
159
+ const tdb = openTurnStore(ctx.stateDir);
160
+ const rows = tdb
161
+ .prepare("SELECT assigned_at FROM memory_topics WHERE topic_id = ?")
162
+ .all(topicId);
163
+ const body = {
164
+ topicId,
165
+ buckets: bucketize(rows.map((r) => r.assigned_at)),
166
+ total: rows.length,
167
+ };
168
+ sendJson(res, 200, body);
169
+ }
170
+ catch (e) {
171
+ sendJson(res, 500, { error: String(e) });
172
+ }
173
+ return true;
174
+ }
175
+ // ── GET /api/wiki/topic/:topicId ─────────────────────────────────
176
+ const topicMatch = req.method === "GET" ? url.match(/^\/api\/wiki\/topic\/([^/?]+)$/) : null;
177
+ if (topicMatch) {
178
+ const topicId = decodeSegment(topicMatch[1]);
179
+ if (topicId === null) {
180
+ sendJson(res, 400, { error: "malformed_topic_id" });
181
+ return true;
182
+ }
183
+ try {
184
+ const store = createTopicStore(ctx.stateDir);
185
+ const topic = store.getTopics().find((t) => t.id === topicId);
186
+ if (!topic) {
187
+ sendJson(res, 404, { error: "topic_not_found" });
188
+ return true;
189
+ }
190
+ const members = store.getMemoriesForTopic(topicId, 200);
191
+ const content = memberContent(ctx, members);
192
+ const getText = (memId) => content.get(memId)?.text ?? "";
193
+ const summary = extractiveSummary(topic, members, getText);
194
+ const present = members.filter((m) => content.has(m.memoryId));
195
+ const keyMemories = [...present]
196
+ .sort((a, b) => (content.get(b.memoryId)?.text.length ?? 0) -
197
+ (content.get(a.memoryId)?.text.length ?? 0))
198
+ .slice(0, 10)
199
+ .map((m) => ({
200
+ memoryId: m.memoryId,
201
+ content: content.get(m.memoryId)?.text ?? "",
202
+ timestamp: content.get(m.memoryId)?.timestamp ?? 0,
203
+ importance: Math.round((content.get(m.memoryId)?.text.length ?? 0) / 40),
204
+ }));
205
+ const relatedTopicIds = store
206
+ .getTopics()
207
+ .filter((t) => t.id !== topicId && t.memoryCount > 0)
208
+ .slice(0, 3)
209
+ .map((t) => t.id);
210
+ const body = {
211
+ topic: toIndexEntry(topic, createWikiCuration(ctx.stateDir)),
212
+ summary,
213
+ keyMemories,
214
+ provenance: members.map(toProvenance),
215
+ relatedTopicIds,
216
+ generatedAt: Date.now(),
217
+ };
218
+ sendJson(res, 200, body);
219
+ }
220
+ catch (e) {
221
+ sendJson(res, 500, { error: String(e) });
222
+ }
223
+ return true;
224
+ }
225
+ // ── PUT /api/wiki/topic/:topicId/label ───────────────────────────
226
+ const renameMatch = req.method === "PUT"
227
+ ? url.match(/^\/api\/wiki\/topic\/([^/?]+)\/label$/)
228
+ : null;
229
+ if (renameMatch) {
230
+ readJsonBody(req, (result) => {
231
+ if (!result.ok)
232
+ return sendJson(res, 400, { error: result.error });
233
+ const topicId = decodeSegment(renameMatch[1]);
234
+ if (topicId === null)
235
+ return sendJson(res, 400, { error: "malformed_topic_id" });
236
+ const label = typeof result.value.label === "string" ? result.value.label : "";
237
+ try {
238
+ const curation = createWikiCuration(ctx.stateDir);
239
+ const out = curation.renameTopic(topicId, label);
240
+ if (!out.ok)
241
+ return sendJson(res, 404, { error: "topic_not_found" });
242
+ emitWikiEvent(ctx, {
243
+ type: "wiki_topic_renamed",
244
+ topicId,
245
+ label: out.label,
246
+ edited: out.edited,
247
+ });
248
+ sendJson(res, 200, out);
249
+ }
250
+ catch (e) {
251
+ sendJson(res, 500, { error: String(e) });
252
+ }
253
+ });
254
+ return true;
255
+ }
256
+ // ── POST /api/wiki/merge ─────────────────────────────────────────
257
+ if (req.method === "POST" && url === "/api/wiki/merge") {
258
+ readJsonBody(req, (result) => {
259
+ if (!result.ok)
260
+ return sendJson(res, 400, { error: result.error });
261
+ const sourceTopicId = typeof result.value.sourceTopicId === "string"
262
+ ? result.value.sourceTopicId
263
+ : "";
264
+ const targetTopicId = typeof result.value.targetTopicId === "string"
265
+ ? result.value.targetTopicId
266
+ : "";
267
+ if (!sourceTopicId || !targetTopicId) {
268
+ return sendJson(res, 400, { error: "missing_source_or_target" });
269
+ }
270
+ try {
271
+ const curation = createWikiCuration(ctx.stateDir);
272
+ const out = curation.mergeTopics(sourceTopicId, targetTopicId);
273
+ if (!out.ok) {
274
+ return sendJson(res, 400, {
275
+ error: sourceTopicId === targetTopicId
276
+ ? "cannot_merge_into_itself"
277
+ : "topic_not_found",
278
+ });
279
+ }
280
+ emitWikiEvent(ctx, {
281
+ type: "wiki_topics_merged",
282
+ sourceTopicId,
283
+ targetTopicId,
284
+ });
285
+ sendJson(res, 200, out);
286
+ }
287
+ catch (e) {
288
+ sendJson(res, 500, { error: String(e) });
289
+ }
290
+ });
291
+ return true;
292
+ }
293
+ // ── POST /api/wiki/topic/:topicId/split ──────────────────────────
294
+ const splitMatch = req.method === "POST"
295
+ ? url.match(/^\/api\/wiki\/topic\/([^/?]+)\/split$/)
296
+ : null;
297
+ if (splitMatch) {
298
+ readJsonBody(req, (result) => {
299
+ if (!result.ok)
300
+ return sendJson(res, 400, { error: result.error });
301
+ const topicId = decodeSegment(splitMatch[1]);
302
+ if (topicId === null)
303
+ return sendJson(res, 400, { error: "malformed_topic_id" });
304
+ const raw = result.value.memoryIds;
305
+ const memoryIds = Array.isArray(raw)
306
+ ? raw.filter((m) => typeof m === "string")
307
+ : [];
308
+ if (memoryIds.length === 0) {
309
+ return sendJson(res, 400, { error: "empty_memoryIds" });
310
+ }
311
+ try {
312
+ const curation = createWikiCuration(ctx.stateDir);
313
+ const out = curation.splitTopic(topicId, memoryIds);
314
+ if (!out.ok)
315
+ return sendJson(res, 404, { error: "topic_not_found" });
316
+ emitWikiEvent(ctx, {
317
+ type: "wiki_topic_split",
318
+ fromTopicId: topicId,
319
+ toTopicId: out.topicId,
320
+ });
321
+ sendJson(res, 200, out);
322
+ }
323
+ catch (e) {
324
+ sendJson(res, 500, { error: String(e) });
325
+ }
326
+ });
327
+ return true;
328
+ }
329
+ return false;
330
+ }
@@ -20,3 +20,5 @@ export { handleCacheStripes } from "./routes-cache.js";
20
20
  export { handleContextHealth, handleCachePoison, handleHealthSettings } from "./routes-health.js";
21
21
  export { handleEmbedderHealth } from "./routes-embedder-health.js";
22
22
  export { handleRagSettings } from "./routes-rag-settings.js";
23
+ export { handleRagMetrics } from "./routes-rag-metrics.js";
24
+ export { handleWiki } from "./routes-wiki.js";
@@ -11,8 +11,9 @@ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from
11
11
  import { join, dirname } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { createRequire } from "node:module";
14
+ import { NEW_UI } from "../../src/config.js";
14
15
  import { log, setLogPath, setDashboardServerVersion } from "./state.js";
15
- import { buildRouteContext, handleIndex, handleRepoIndex, handleEvents, handleGameState, handleGameScores, handlePerf, handleAchievements, handleSessions, handleTopics, handleTurns, handleMaintenance, handleProviderCache, handleCacheStripes, handleMemoryStatus, handleSetupStatus, handleSetupDetect, handleSetupConfigure, handleMemoryMap, handleRaptorTree, handleContextHealth, handleCachePoison, handleHealthSettings, handleEmbedderHealth, handleRagSettings, handleStatic, } from "./routes.js";
16
+ import { buildRouteContext, handleIndex, handleRepoIndex, handleEvents, handleGameState, handleGameScores, handlePerf, handleAchievements, handleSessions, handleTopics, handleTurns, handleMaintenance, handleProviderCache, handleCacheStripes, handleMemoryStatus, handleSetupStatus, handleSetupDetect, handleSetupConfigure, handleMemoryMap, handleRaptorTree, handleContextHealth, handleCachePoison, handleHealthSettings, handleEmbedderHealth, handleRagSettings, handleRagMetrics, handleWiki, handleStatic, } from "./routes.js";
16
17
  export async function launchDashboardServer(stateDir) {
17
18
  // Our own package version — exposed at /api/version so the launcher can
18
19
  // detect a stale server (started by an older build) and replace it on
@@ -71,6 +72,14 @@ export async function launchDashboardServer(stateDir) {
71
72
  const hasClientBuild = existsSync(clientIndexHtml);
72
73
  if (hasClientBuild)
73
74
  log("client build present", { clientDist });
75
+ function injectUiFlag(html) {
76
+ const flag = NEW_UI() ? "true" : "false";
77
+ const script = `<script>window.MEGACOMPACT_NEW_UI=${flag}</script>`;
78
+ if (html.includes("</head>")) {
79
+ return html.replace("</head>", `${script}</head>`);
80
+ }
81
+ return html + script;
82
+ }
74
83
  // guardrails-allow PREVENT-PI-004: read-only static file serving from the local dashboard-client/dist bundle (loopback-only UI).
75
84
  const serveClientAsset = (reqPath, res) => {
76
85
  if (!hasClientBuild)
@@ -84,7 +93,7 @@ export async function launchDashboardServer(stateDir) {
84
93
  if (!file.startsWith(clientDist) || !existsSync(file)) {
85
94
  // SPA fallback: unknown non-asset routes serve index.html (client-side routing).
86
95
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
87
- res.end(readFileSync(clientIndexHtml));
96
+ res.end(injectUiFlag(readFileSync(clientIndexHtml, "utf-8")));
88
97
  return true;
89
98
  }
90
99
  const ext = rel.slice(rel.lastIndexOf(".") + 1);
@@ -98,10 +107,11 @@ export async function launchDashboardServer(stateDir) {
98
107
  ico: "image/x-icon",
99
108
  map: "application/json",
100
109
  };
110
+ const payload = ext === "html" ? injectUiFlag(readFileSync(file, "utf-8")) : readFileSync(file);
101
111
  res.writeHead(200, {
102
112
  "Content-Type": types[ext] ?? "application/octet-stream",
103
113
  });
104
- res.end(readFileSync(file));
114
+ res.end(payload);
105
115
  return true;
106
116
  };
107
117
  log("launch invoked", { stateDir });
@@ -219,6 +229,10 @@ export async function launchDashboardServer(stateDir) {
219
229
  return;
220
230
  if (handleRagSettings(req, res, ctx))
221
231
  return;
232
+ if (handleRagMetrics(req, res, ctx))
233
+ return;
234
+ if (handleWiki(req, res, ctx))
235
+ return;
222
236
  handleStatic(req, res, ctx);
223
237
  });
224
238
  // Bind base + range are env-configurable so tests can use a private,
@@ -12,7 +12,8 @@ import { epochIdFor } from "../../../src/mirror/epoch.js";
12
12
  import { stampTurnsEpochFor } from "../../mega-turn-store.js";
13
13
  import { TurnsConfig } from "../../../src/config/turns.js";
14
14
  import { openTurnStore } from "../../../src/store/turns/connection.js";
15
- import { buildTopicModel, createTopicStore, bumpWikiCompactCounter, } from "../../../src/topics/index.js";
15
+ import { buildTopicModel, createTopicStore, bumpWikiCompactCounter, applyOverridesAfterRebuild, applyFullOverridesAfterRebuild, } from "../../../src/topics/index.js";
16
+ import { assignNewMemoriesIncremental } from "../../../src/wiki/index.js";
16
17
  import { TrigramEmbedder } from "../../../src/embedder.js";
17
18
  /**
18
19
  * Persist the checkpoint_epoch, stamp turns, rebuild the auto-wiki, seed the
@@ -58,7 +59,30 @@ export async function persistEpochAndMaintain(runtime, config, ran) {
58
59
  const every = Math.max(1, TurnsConfig.WIKI_REBUILD_EVERY_N_COMPACTS);
59
60
  const tdb = openTurnStore(runtime.currentStateDir);
60
61
  const n = bumpWikiCompactCounter(tdb);
61
- if (n % every === 0) {
62
+ // W5-B: on the Nth compact we full-rebuild; otherwise try the
63
+ // cheap incremental path (assign new memories to nearest
64
+ // centroid) and only force a full rebuild when the mean
65
+ // silhouette has degraded below the configured floor.
66
+ let rebuildNow = n % every === 0;
67
+ if (!rebuildNow &&
68
+ TurnsConfig.WIKI_INCREMENTAL_ENABLED &&
69
+ TurnsConfig.WIKI_ENHANCED_ENABLED) {
70
+ const inc = assignNewMemoriesIncremental(db, runtime.currentStateDir);
71
+ if (inc.silhouette !== null &&
72
+ inc.silhouette < TurnsConfig.WIKI_INCREMENTAL_SILHOUETTE_MIN) {
73
+ // Silhouette degraded — fall through to a full rebuild.
74
+ rebuildNow = true;
75
+ }
76
+ else {
77
+ runtime.logger.info("wiki_incremental", {
78
+ assigned: inc.assigned,
79
+ pending: inc.pending,
80
+ silhouetteScore: inc.silhouette,
81
+ uncalibrated: false,
82
+ });
83
+ }
84
+ }
85
+ if (rebuildNow) {
62
86
  const model = buildTopicModel(db, {
63
87
  kRange: [TurnsConfig.WIKI_K_MIN, TurnsConfig.WIKI_K_MAX],
64
88
  labelTopTerms: TurnsConfig.WIKI_LABEL_TOP_TERMS,
@@ -66,6 +90,13 @@ export async function persistEpochAndMaintain(runtime, config, ran) {
66
90
  seed: 0x9e3779b9,
67
91
  });
68
92
  createTopicStore(runtime.currentStateDir).replaceTopicModel(model);
93
+ // W5-A: full hybrid replay — label + merge + split overrides
94
+ // survive the rebuild (feature A, not just the label fast path).
95
+ applyFullOverridesAfterRebuild(tdb);
96
+ // SSE push so the dashboard Wiki/Evolution views refresh (non-fatal).
97
+ runtime.dashboard.event("wiki_rebuilt", {
98
+ topicCount: model.k,
99
+ });
69
100
  runtime.logger.info("wiki_rebuild", {
70
101
  clusterCount: model.k,
71
102
  totalChunks: model.totalChunks,
@@ -130,6 +161,8 @@ export async function persistEpochAndMaintain(runtime, config, ran) {
130
161
  seed: 0x9e3779b9,
131
162
  }, seedChunks);
132
163
  createTopicStore(runtime.currentStateDir).replaceTopicModel(model);
164
+ // Re-stamp custom label overrides wiped by the seed rebuild.
165
+ applyOverridesAfterRebuild(openTurnStore(runtime.currentStateDir));
133
166
  runtime.logger.info("wiki_seed", {
134
167
  clusterCount: model.k,
135
168
  sourceChunks: seedChunks.length,
@@ -40,6 +40,32 @@ export function doRecall(runtime, config, ctx, query, source) {
40
40
  injected: result.toInject.length,
41
41
  empty: result.empty,
42
42
  });
43
+ if (config.ragRecallMetrics && result.hydeInfo) {
44
+ runtime.dashboard.event("hyde_executed", {
45
+ sessionId: sid,
46
+ ran: result.hydeInfo.ran,
47
+ skipped: result.hydeInfo.skipped,
48
+ reason: result.hydeInfo.reason,
49
+ hypotheticalDoc: result.hydeInfo.hypotheticalDoc.slice(0, 400),
50
+ generationMs: result.hydeInfo.generationMs,
51
+ rawHitCount: result.hydeInfo.rawHitCount,
52
+ hydeHitCount: result.hydeInfo.hydeHitCount,
53
+ fusedHitCount: result.hydeInfo.fusedHitCount,
54
+ lift: result.hydeInfo.lift,
55
+ });
56
+ }
57
+ if (config.ragRecallMetrics && result.recallMetrics) {
58
+ runtime.dashboard.event("recall_metrics", {
59
+ sessionId: sid,
60
+ hitCount: result.recallMetrics.hitCount,
61
+ score: result.recallMetrics.score,
62
+ pass: result.recallMetrics.pass,
63
+ relevance: result.recallMetrics.relevance,
64
+ coverage: result.recallMetrics.coverage,
65
+ diversity: result.recallMetrics.diversity,
66
+ specificity: result.recallMetrics.specificity,
67
+ });
68
+ }
43
69
  if (!result.empty && result.toInject.length > 0) {
44
70
  const top = result.toInject[0];
45
71
  const scorePct = Math.round((top.score ?? 0) * 100);
@@ -72,6 +98,8 @@ export function doRecall(runtime, config, ctx, query, source) {
72
98
  turnIndex: runtime.currentTurn,
73
99
  role: "assistant",
74
100
  startedAt: Date.now(),
101
+ hyde: result.hydeInfo ?? undefined,
102
+ recallMetrics: result.recallMetrics ?? undefined,
75
103
  }, runtime.currentStateDir);
76
104
  recordRecallWrite(config, turnId, result.toInject.map((h) => ({
77
105
  checkpointId: h.checkpoint.checkpointId,
@@ -173,6 +201,9 @@ export async function doRecallAsync(runtime, config, ctx, query, source, opts =
173
201
  report: merged.map((h) => ` • ${h.checkpoint.checkpointId}${h.repoId ? ` (from ${h.repoId.split("/").filter(Boolean).pop()})` : ""}`),
174
202
  block,
175
203
  empty: merged.length === 0,
204
+ // H1: merged cross-repo result reuses the same-repo pass's telemetry.
205
+ hydeInfo: sameRepo.hydeInfo,
206
+ recallMetrics: sameRepo.recallMetrics,
176
207
  };
177
208
  }
178
209
  catch {
@@ -43,7 +43,6 @@ export function ensureConversationIdFor(config, sessionId, stateDir) {
43
43
  ? storeFor(stateDir).ensureConversationId(sessionId)
44
44
  : legacyEnsureConversationId(sessionId, stateDir);
45
45
  }
46
- /** Record one turn row (turn_end). Returns the contract TurnId (string). */
47
46
  export function recordTurnWrite(config, input, stateDir) {
48
47
  if (!config.turnsDbEnabled) {
49
48
  legacyRecordTurn(input, stateDir);
@@ -60,6 +59,8 @@ export function recordTurnWrite(config, input, stateDir) {
60
59
  pressureBand: input.pressureBand,
61
60
  model: input.modelId,
62
61
  epochId: input.epochId,
62
+ hyde: input.hyde,
63
+ recallMetrics: input.recallMetrics,
63
64
  };
64
65
  return storeFor(stateDir).appendTurn(entry);
65
66
  }
@@ -22,6 +22,13 @@ function envNum(name, def) {
22
22
  const n = Number(v);
23
23
  return Number.isFinite(n) ? n : def;
24
24
  }
25
+ /** Default-ON toggle honoring the MEGACOMPACT_<NAME>_DISABLED opt-out convention. */
26
+ function envEnabled(name, def) {
27
+ if (process.env[`${name}_DISABLED`] === "true" || process.env[`${name}_DISABLED`] === "1") {
28
+ return false;
29
+ }
30
+ return envBool(name, def);
31
+ }
25
32
  function envKRange(defMin, defMax) {
26
33
  const v = process.env.MEGACOMPACT_WIKI_K_RANGE;
27
34
  if (v) {
@@ -44,6 +51,9 @@ export function loadTurnsConfig() {
44
51
  WIKI_LABEL_TOP_TERMS: envNum("MEGACOMPACT_WIKI_LABEL_TOP_TERMS", 5),
45
52
  WIKI_REBUILD_EVERY_N_COMPACTS: envNum("MEGACOMPACT_WIKI_REBUILD_EVERY", 3),
46
53
  WIKI_SEED_FROM_TURNS: envBool("MEGACOMPACT_WIKI_SEED_FROM_TURNS", true),
54
+ WIKI_ENHANCED_ENABLED: envEnabled("MEGACOMPACT_WIKI_ENHANCED", true),
55
+ WIKI_INCREMENTAL_ENABLED: envEnabled("MEGACOMPACT_WIKI_INCREMENTAL", true),
56
+ WIKI_INCREMENTAL_SILHOUETTE_MIN: envNum("MEGACOMPACT_WIKI_SILHOUETTE_MIN", 0.2),
47
57
  };
48
58
  }
49
59
  export const TurnsConfig = loadTurnsConfig();
@@ -107,3 +107,5 @@ export const RAG_MEMORY_GRAPH = () => ragEnabled("MEGACOMPACT_MEMORY_GRAPH");
107
107
  * HttpEmbedder is active (the LLM is configured for indexing); opt OUT with
108
108
  * MEGACOMPACT_HYDE_DISABLED=true. TrigramEmbedder path is unaffected. */
109
109
  export const RAG_HYDE_ENABLED = () => ragEnabled("MEGACOMPACT_HYDE");
110
+ /** Spec 1: vbrainstorm visual design migration for the dashboard. */
111
+ export const NEW_UI = () => ragEnabled("MEGACOMPACT_NEW_UI");
@@ -108,5 +108,14 @@ export async function recallAndInlineAsync(opts, store) {
108
108
  // has repoId + score for proper labeling.
109
109
  const block = toInject.length > 0 ? formatRecallBlock(toInject) : "";
110
110
  const report = toInject.map((h) => ` • ${h.checkpoint.checkpointId} (${h.checkpoint.summary.slice(0, 60).replace(/\n/g, " ")}…)`);
111
- return { toInject, report, block, empty: toInject.length === 0 };
111
+ return {
112
+ toInject,
113
+ report,
114
+ block,
115
+ empty: toInject.length === 0,
116
+ // H1: cross-repo async path is HyDE-free; telemetry stays null until a
117
+ // future sprint threads it here.
118
+ hydeInfo: null,
119
+ recallMetrics: null,
120
+ };
112
121
  }