mulmoclaude 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (486) hide show
  1. package/Dockerfile.sandbox +100 -0
  2. package/README.md +17 -4
  3. package/bin/mulmoclaude.js +46 -15
  4. package/bin/prepare-dist.js +18 -2
  5. package/client/assets/chunk-CernVdwh.js +1 -0
  6. package/client/assets/chunk-D8eiyYIV-C1eAZMzz.js +1 -0
  7. package/client/assets/html2canvas-CDGcmOD3-BbPeutDg.js +5 -0
  8. package/client/assets/index-BbgSjFQ8.js +4968 -0
  9. package/client/assets/index-ECD0lgIv.css +2 -0
  10. package/client/assets/{index.es-D4YyL_Dg-BgT6a3Nd.js → index.es-DqtpmBm8-DJdTPdnc.js} +5 -5
  11. package/client/assets/material-symbols-outlined-BLDfUw-_.woff2 +0 -0
  12. package/client/assets/runtime-protocol-vue-6WYa8hAs.js +1 -0
  13. package/client/assets/runtime-vue-BVUzgYGA.js +1 -0
  14. package/client/assets/typeof-DBp4T-Ny-C2xoZtcz.js +1 -0
  15. package/client/assets/vue-1e_vz2LW.js +1 -0
  16. package/client/assets/vue.runtime.esm-bundler-DQ8Kjjui.js +4 -0
  17. package/client/index.html +33 -2
  18. package/package.json +20 -18
  19. package/sandbox-entrypoint.sh +106 -0
  20. package/server/accounting/accountNormalize.ts +32 -0
  21. package/server/accounting/defaultAccounts.ts +87 -0
  22. package/server/accounting/eventPublisher.ts +51 -0
  23. package/server/accounting/journal.ts +252 -0
  24. package/server/accounting/openingBalances.ts +114 -0
  25. package/server/accounting/report.ts +237 -0
  26. package/server/accounting/service.ts +718 -0
  27. package/server/accounting/snapshotCache.ts +333 -0
  28. package/server/accounting/timeSeries.ts +265 -0
  29. package/server/accounting/types.ts +148 -0
  30. package/server/agent/activeTools.ts +128 -0
  31. package/server/agent/attachmentConverter.ts +10 -5
  32. package/server/agent/backend/claude-code.ts +8 -2
  33. package/server/agent/backend/types.ts +1 -1
  34. package/server/agent/config.ts +101 -31
  35. package/server/agent/index.ts +45 -33
  36. package/server/agent/mcp-server.ts +146 -69
  37. package/server/agent/mcp-tools/index.ts +1 -5
  38. package/server/agent/mcp-tools/notify.ts +2 -22
  39. package/server/agent/mcp-tools/x.ts +0 -4
  40. package/server/agent/mcpHealth.ts +168 -0
  41. package/server/agent/plugin-names.ts +20 -77
  42. package/server/agent/prompt.ts +259 -51
  43. package/server/agent/resumeFailover.ts +1 -1
  44. package/server/agent/stream.ts +0 -1
  45. package/server/api/auth/bearerAuth.ts +5 -5
  46. package/server/api/csrfGuard.ts +1 -1
  47. package/server/api/routes/accounting.ts +366 -0
  48. package/server/api/routes/agent.ts +509 -46
  49. package/server/api/routes/attachment.ts +104 -0
  50. package/server/api/routes/chart.ts +2 -1
  51. package/server/api/routes/config.ts +12 -12
  52. package/server/api/routes/files.ts +105 -48
  53. package/server/api/routes/image.ts +70 -25
  54. package/server/api/routes/journal.ts +35 -0
  55. package/server/api/routes/mulmo-script.ts +358 -118
  56. package/server/api/routes/mulmoScriptValidate.ts +1 -1
  57. package/server/api/routes/news.ts +1 -1
  58. package/server/api/routes/notifications.ts +92 -22
  59. package/server/api/routes/notifier.ts +98 -0
  60. package/server/api/routes/pdf.ts +188 -48
  61. package/server/api/routes/plugins.ts +34 -14
  62. package/server/api/routes/presentHtml.ts +58 -3
  63. package/server/api/routes/roles.ts +1 -8
  64. package/server/api/routes/runtime-plugin.ts +224 -0
  65. package/server/api/routes/scheduler.ts +7 -5
  66. package/server/api/routes/schedulerHandlers.ts +1 -1
  67. package/server/api/routes/schedulerTasks.ts +8 -7
  68. package/server/api/routes/sessions.ts +234 -121
  69. package/server/api/routes/skills.ts +56 -51
  70. package/server/api/routes/sources.ts +52 -45
  71. package/server/api/routes/translation.ts +44 -0
  72. package/server/api/routes/wiki/frontmatter.ts +13 -65
  73. package/server/api/routes/wiki/history.ts +261 -0
  74. package/server/api/routes/wiki/pageIndex.ts +1 -1
  75. package/server/api/routes/wiki.ts +50 -26
  76. package/server/events/file-change.ts +83 -0
  77. package/server/events/notifications.ts +247 -91
  78. package/server/events/pub-sub/index.ts +1 -1
  79. package/server/events/relay-client.ts +5 -5
  80. package/server/events/scheduler-adapter.ts +2 -2
  81. package/server/events/session-store/index.ts +110 -22
  82. package/server/events/task-manager/index.ts +10 -9
  83. package/server/index.ts +509 -33
  84. package/server/notifier/engine.ts +419 -0
  85. package/server/notifier/legacy-adapters.ts +76 -0
  86. package/server/notifier/runtime-api.ts +74 -0
  87. package/server/notifier/store.ts +70 -0
  88. package/server/notifier/types.ts +121 -0
  89. package/server/plugins/dev-loader.ts +171 -0
  90. package/server/plugins/dev-watcher.ts +150 -0
  91. package/server/plugins/diagnostics.ts +188 -0
  92. package/server/plugins/preset-list.ts +52 -0
  93. package/server/plugins/preset-loader.ts +112 -0
  94. package/server/plugins/runtime-chat-api.ts +38 -0
  95. package/server/plugins/runtime-loader.ts +430 -0
  96. package/server/plugins/runtime-registry.ts +112 -0
  97. package/server/plugins/runtime-tasks-api.ts +50 -0
  98. package/server/plugins/runtime.ts +378 -0
  99. package/server/services/translation/cache.ts +72 -0
  100. package/server/services/translation/index.ts +106 -0
  101. package/server/services/translation/llm.ts +140 -0
  102. package/server/services/translation/types.ts +35 -0
  103. package/server/system/credentials.ts +13 -2
  104. package/server/system/env.ts +6 -1
  105. package/server/system/logger/formatters.ts +46 -4
  106. package/server/system/logger/index.ts +4 -4
  107. package/server/system/logger/sinks.ts +26 -5
  108. package/server/system/logger/types.ts +2 -2
  109. package/server/utils/dev-plugin-args.d.mts +11 -0
  110. package/server/utils/dev-plugin-args.mjs +43 -0
  111. package/server/utils/errors.ts +13 -4
  112. package/server/utils/files/accounting-io.ts +295 -0
  113. package/server/utils/files/atomic.ts +17 -49
  114. package/server/utils/files/attachment-store.ts +182 -0
  115. package/server/utils/files/html-io.ts +1 -7
  116. package/server/utils/files/html-store.ts +19 -0
  117. package/server/utils/files/image-store.ts +20 -22
  118. package/server/utils/files/index.ts +5 -15
  119. package/server/utils/files/journal-io.ts +7 -35
  120. package/server/utils/files/json.ts +2 -29
  121. package/server/utils/files/markdown-image-fill.ts +6 -37
  122. package/server/utils/files/markdown-store.ts +6 -21
  123. package/server/utils/files/naming.ts +3 -39
  124. package/server/utils/files/plugins-io.ts +100 -0
  125. package/server/utils/files/reference-dirs-io.ts +1 -9
  126. package/server/utils/files/roles-io.ts +2 -10
  127. package/server/utils/files/safe.ts +17 -19
  128. package/server/utils/files/scheduler-io.ts +1 -7
  129. package/server/utils/files/scheduler-overrides-io.ts +3 -12
  130. package/server/utils/files/session-io.ts +21 -30
  131. package/server/utils/files/spreadsheet-store.ts +9 -22
  132. package/server/utils/files/translation-io.ts +46 -0
  133. package/server/utils/files/user-tasks-io.ts +1 -7
  134. package/server/utils/files/workspace-io.ts +3 -79
  135. package/server/utils/gemini.ts +33 -11
  136. package/server/utils/html/htmlArtifactSplicer.ts +41 -0
  137. package/server/utils/markdown/frontmatter.ts +112 -0
  138. package/server/utils/regex.ts +56 -0
  139. package/server/utils/router.ts +41 -0
  140. package/server/utils/slug.ts +5 -3
  141. package/server/utils/time.ts +12 -0
  142. package/server/workspace/chat-index/indexer.ts +15 -2
  143. package/server/workspace/chat-index/summarizer.ts +1 -1
  144. package/server/workspace/custom-dirs.ts +1 -1
  145. package/server/workspace/helps/gemini.md +1 -1
  146. package/server/workspace/helps/guide.md +61 -0
  147. package/server/workspace/helps/index.md +4 -0
  148. package/server/workspace/helps/presenthtml.md +80 -0
  149. package/server/workspace/helps/sandbox.md +7 -0
  150. package/server/workspace/helps/storyteller.md +101 -0
  151. package/server/workspace/helps/telegram.md +1 -0
  152. package/server/workspace/helps/wiki.md +9 -7
  153. package/server/workspace/journal/archivist-cli.ts +7 -33
  154. package/server/workspace/journal/archivist-schemas.ts +5 -43
  155. package/server/workspace/journal/dailyPass.ts +34 -187
  156. package/server/workspace/journal/diff.ts +3 -28
  157. package/server/workspace/journal/index.ts +10 -81
  158. package/server/workspace/journal/indexFile.ts +3 -24
  159. package/server/workspace/journal/latestDaily.ts +51 -0
  160. package/server/workspace/journal/memoryExtractor.ts +4 -20
  161. package/server/workspace/journal/optimizationPass.ts +4 -21
  162. package/server/workspace/journal/paths.ts +4 -23
  163. package/server/workspace/journal/state.ts +6 -29
  164. package/server/workspace/memory/io.ts +213 -0
  165. package/server/workspace/memory/llm-classifier.ts +158 -0
  166. package/server/workspace/memory/migrate.ts +263 -0
  167. package/server/workspace/memory/run.ts +84 -0
  168. package/server/workspace/memory/topic-cluster.ts +218 -0
  169. package/server/workspace/memory/topic-detect.ts +67 -0
  170. package/server/workspace/memory/topic-index-hook.ts +128 -0
  171. package/server/workspace/memory/topic-io.ts +180 -0
  172. package/server/workspace/memory/topic-migrate.ts +248 -0
  173. package/server/workspace/memory/topic-run.ts +172 -0
  174. package/server/workspace/memory/topic-swap.ts +135 -0
  175. package/server/workspace/memory/topic-types.ts +142 -0
  176. package/server/workspace/memory/types.ts +83 -0
  177. package/server/workspace/news/reader.ts +4 -5
  178. package/server/workspace/paths.ts +124 -47
  179. package/server/workspace/roles.ts +2 -11
  180. package/server/workspace/skills/parser.ts +38 -55
  181. package/server/workspace/skills/user-tasks.ts +1 -2
  182. package/server/workspace/skills-preset/mc-library/SKILL.md +188 -0
  183. package/server/workspace/skills-preset.ts +196 -0
  184. package/server/workspace/sources/fetchers/githubIssues.ts +13 -11
  185. package/server/workspace/sources/fetchers/index.ts +1 -1
  186. package/server/workspace/sources/fetchers/rssParser.ts +1 -1
  187. package/server/workspace/sources/pipeline/index.ts +2 -2
  188. package/server/workspace/sources/pipeline/notify.ts +3 -3
  189. package/server/workspace/sources/pipeline/write.ts +2 -2
  190. package/server/workspace/sources/registry.ts +39 -61
  191. package/server/workspace/sources/robots.ts +1 -1
  192. package/server/workspace/tool-trace/classify.ts +2 -1
  193. package/server/workspace/tool-trace/index.ts +1 -1
  194. package/server/workspace/tool-trace/writeSearch.ts +6 -1
  195. package/server/workspace/wiki-backlinks/index.ts +19 -7
  196. package/server/workspace/wiki-backlinks/sessionBacklinks.ts +1 -0
  197. package/server/workspace/wiki-history/hook/snapshot.mjs +98 -0
  198. package/server/workspace/wiki-history/hook/snapshot.ts +135 -0
  199. package/server/workspace/wiki-history/provision.ts +181 -0
  200. package/server/workspace/wiki-pages/io.ts +217 -0
  201. package/server/workspace/wiki-pages/snapshot.ts +380 -0
  202. package/server/workspace/workspace.ts +75 -13
  203. package/src/App.vue +115 -40
  204. package/src/_runtime/protocol-vue.ts +21 -0
  205. package/src/_runtime/vue.ts +22 -0
  206. package/src/components/ChatInput.vue +14 -10
  207. package/src/components/CopyChatButton.vue +76 -0
  208. package/src/components/FileContentRenderer.vue +67 -14
  209. package/src/components/FileTree.vue +2 -2
  210. package/src/components/FilesView.vue +17 -1
  211. package/src/components/NewsView.vue +16 -2
  212. package/src/components/NotificationBell.vue +320 -93
  213. package/src/components/PageChatComposer.vue +5 -4
  214. package/src/components/PluginLauncher.vue +42 -6
  215. package/src/components/PluginScopedRoot.vue +87 -0
  216. package/src/components/RoleSelector.vue +12 -1
  217. package/src/components/RolesView.vue +562 -0
  218. package/src/components/SentAttachmentChip.vue +102 -0
  219. package/src/components/SessionHistoryPanel.vue +109 -20
  220. package/src/components/SessionRoleIcon.vue +7 -4
  221. package/src/components/SessionSidebar.vue +20 -7
  222. package/src/components/SessionTabBar.vue +1 -1
  223. package/src/components/SettingsMcpTab.vue +4 -4
  224. package/src/components/SettingsModal.vue +2 -0
  225. package/src/components/SidebarHeader.vue +16 -5
  226. package/src/components/SourcesManager.vue +23 -9
  227. package/src/components/SourcesView.vue +1 -1
  228. package/src/components/StackView.vue +102 -6
  229. package/src/components/SuggestionsPanel.vue +105 -16
  230. package/src/components/SystemFileBanner.vue +1 -1
  231. package/src/components/TodoExplorer.vue +4 -5
  232. package/src/components/todo/TodoAddDialog.vue +2 -3
  233. package/src/components/todo/TodoEditDialog.vue +1 -2
  234. package/src/components/todo/TodoEditPanel.vue +2 -3
  235. package/src/components/todo/TodoKanbanView.vue +8 -5
  236. package/src/components/todo/TodoListView.vue +3 -5
  237. package/src/components/todo/TodoTableView.vue +7 -5
  238. package/src/composables/useAccountingChannel.ts +58 -0
  239. package/src/composables/useActiveSession.ts +4 -25
  240. package/src/composables/useAppApi.ts +6 -44
  241. package/src/composables/useClipboardCopy.ts +3 -20
  242. package/src/composables/useContentDisplay.ts +33 -2
  243. package/src/composables/useDevPluginReload.ts +23 -0
  244. package/src/composables/useDynamicFavicon.ts +5 -31
  245. package/src/composables/useEventListeners.ts +0 -20
  246. package/src/composables/useExpandedDirs.ts +4 -15
  247. package/src/composables/useFaviconState.ts +12 -46
  248. package/src/composables/useFileChange.ts +53 -0
  249. package/src/composables/useFreshPluginData.ts +6 -43
  250. package/src/composables/useHealth.ts +14 -43
  251. package/src/composables/useImageErrorRepair.ts +104 -0
  252. package/src/composables/useLatestDaily.ts +40 -0
  253. package/src/composables/useMarkdownDoc.ts +39 -0
  254. package/src/composables/useMarkdownLinkHandler.ts +1 -1
  255. package/src/composables/useMcpTools.ts +3 -16
  256. package/src/composables/useNotifications.ts +138 -112
  257. package/src/composables/usePdfDownload.ts +17 -3
  258. package/src/composables/usePendingCalls.ts +8 -26
  259. package/src/composables/usePluginErrorBoundary.ts +68 -0
  260. package/src/composables/usePubSub.ts +9 -17
  261. package/src/composables/useRunElapsed.ts +5 -22
  262. package/src/composables/useSandboxStatus.ts +4 -20
  263. package/src/composables/useSessionDerived.ts +7 -15
  264. package/src/composables/useSessionHistory.ts +70 -29
  265. package/src/composables/useSessionSync.ts +25 -3
  266. package/src/composables/useSkillsList.ts +59 -0
  267. package/src/composables/useTranslatedQueries.ts +109 -0
  268. package/src/config/apiRoutes.ts +181 -80
  269. package/src/config/historyFilters.ts +5 -3
  270. package/src/config/hostEvents.ts +17 -0
  271. package/src/config/mcpCatalog.ts +277 -5
  272. package/src/config/pubsubChannels.ts +134 -12
  273. package/src/config/roles.ts +212 -147
  274. package/src/config/systemFileDescriptors.ts +5 -5
  275. package/src/config/toolNames.ts +52 -30
  276. package/src/config/workspacePaths.ts +26 -2
  277. package/src/lang/de.ts +483 -27
  278. package/src/lang/en.ts +448 -27
  279. package/src/lang/es.ts +474 -27
  280. package/src/lang/fr.ts +476 -27
  281. package/src/lang/ja.ts +465 -27
  282. package/src/lang/ko.ts +466 -27
  283. package/src/lang/pt-BR.ts +473 -27
  284. package/src/lang/zh.ts +463 -27
  285. package/src/lib/vue-i18n.ts +1 -1
  286. package/src/lib/wiki-page/slug.ts +66 -0
  287. package/src/main.ts +85 -0
  288. package/src/plugins/_extras.ts +58 -0
  289. package/src/plugins/_generated/metas.ts +42 -0
  290. package/src/plugins/_generated/registrations.ts +44 -0
  291. package/src/plugins/_generated/server-bindings.ts +47 -0
  292. package/src/plugins/accounting/Preview.vue +106 -0
  293. package/src/plugins/accounting/View.vue +632 -0
  294. package/src/plugins/accounting/actions.ts +34 -0
  295. package/src/plugins/accounting/api.ts +301 -0
  296. package/src/plugins/accounting/components/AccountEditor.vue +250 -0
  297. package/src/plugins/accounting/components/AccountRow.vue +50 -0
  298. package/src/plugins/accounting/components/AccountsList.vue +102 -0
  299. package/src/plugins/accounting/components/AccountsModal.vue +300 -0
  300. package/src/plugins/accounting/components/BalanceSheet.vue +186 -0
  301. package/src/plugins/accounting/components/BookSettings.vue +284 -0
  302. package/src/plugins/accounting/components/BookSwitcher.vue +78 -0
  303. package/src/plugins/accounting/components/DateRangePicker.vue +140 -0
  304. package/src/plugins/accounting/components/JournalEntryForm.vue +504 -0
  305. package/src/plugins/accounting/components/JournalList.vue +553 -0
  306. package/src/plugins/accounting/components/Ledger.vue +206 -0
  307. package/src/plugins/accounting/components/NewBookForm.vue +211 -0
  308. package/src/plugins/accounting/components/OpeningBalancesForm.vue +271 -0
  309. package/src/plugins/accounting/components/ProfitLoss.vue +160 -0
  310. package/src/plugins/accounting/components/accountDraft.ts +13 -0
  311. package/src/plugins/accounting/components/accountNumbering.ts +103 -0
  312. package/src/plugins/accounting/components/accountValidation.ts +75 -0
  313. package/src/plugins/accounting/components/useLatestRequest.ts +44 -0
  314. package/src/plugins/accounting/countries.ts +158 -0
  315. package/src/plugins/accounting/currencies.ts +64 -0
  316. package/src/plugins/accounting/dates.ts +51 -0
  317. package/src/plugins/accounting/definition.ts +199 -0
  318. package/src/plugins/accounting/fiscalYear.ts +136 -0
  319. package/src/plugins/accounting/index.ts +49 -0
  320. package/src/plugins/accounting/meta.ts +91 -0
  321. package/src/plugins/accounting/timeSeriesEnums.ts +16 -0
  322. package/src/plugins/api.ts +125 -0
  323. package/src/plugins/canvas/View.vue +38 -28
  324. package/src/plugins/canvas/definition.ts +10 -8
  325. package/src/plugins/canvas/index.ts +15 -8
  326. package/src/plugins/canvas/meta.ts +12 -0
  327. package/src/plugins/chart/Preview.vue +1 -1
  328. package/src/plugins/chart/View.vue +2 -2
  329. package/src/plugins/chart/definition.ts +12 -2
  330. package/src/plugins/chart/index.ts +15 -7
  331. package/src/plugins/chart/meta.ts +18 -0
  332. package/src/plugins/editImages/definition.ts +44 -0
  333. package/src/plugins/editImages/index.ts +43 -0
  334. package/src/plugins/editImages/meta.ts +5 -0
  335. package/src/plugins/generateImage/View.vue +3 -1
  336. package/src/plugins/generateImage/definition.ts +2 -0
  337. package/src/plugins/generateImage/index.ts +13 -5
  338. package/src/plugins/generateImage/meta.ts +5 -0
  339. package/src/plugins/index.ts +35 -0
  340. package/src/plugins/manageRoles/Preview.vue +7 -4
  341. package/src/plugins/manageRoles/View.vue +12 -8
  342. package/src/plugins/manageRoles/definition.ts +6 -0
  343. package/src/plugins/manageRoles/index.ts +7 -6
  344. package/src/plugins/manageSkills/View.vue +11 -7
  345. package/src/plugins/manageSkills/definition.ts +4 -1
  346. package/src/plugins/manageSkills/index.ts +14 -7
  347. package/src/plugins/manageSkills/meta.ts +21 -0
  348. package/src/plugins/manageSource/definition.ts +4 -1
  349. package/src/plugins/manageSource/index.ts +15 -7
  350. package/src/plugins/manageSource/meta.ts +21 -0
  351. package/src/plugins/markdown/Preview.vue +10 -8
  352. package/src/plugins/markdown/View.vue +84 -17
  353. package/src/plugins/markdown/definition.ts +7 -1
  354. package/src/plugins/markdown/index.ts +15 -8
  355. package/src/plugins/markdown/meta.ts +16 -0
  356. package/src/plugins/meta-types.ts +97 -0
  357. package/src/plugins/metas.ts +224 -0
  358. package/src/plugins/presentForm/Preview.vue +4 -15
  359. package/src/plugins/presentForm/View.vue +35 -78
  360. package/src/plugins/presentForm/definition.ts +7 -6
  361. package/src/plugins/presentForm/index.ts +12 -5
  362. package/src/plugins/presentForm/meta.ts +11 -0
  363. package/src/plugins/presentForm/plugin.ts +8 -9
  364. package/src/plugins/presentForm/types.ts +0 -24
  365. package/src/plugins/presentHtml/Preview.vue +1 -8
  366. package/src/plugins/presentHtml/View.vue +401 -30
  367. package/src/plugins/presentHtml/definition.ts +8 -5
  368. package/src/plugins/presentHtml/index.ts +15 -8
  369. package/src/plugins/presentHtml/meta.ts +14 -0
  370. package/src/plugins/presentMulmoScript/View.vue +327 -107
  371. package/src/plugins/presentMulmoScript/definition.ts +34 -7
  372. package/src/plugins/presentMulmoScript/helpers.ts +4 -5
  373. package/src/plugins/presentMulmoScript/index.ts +20 -7
  374. package/src/plugins/presentMulmoScript/meta.ts +52 -0
  375. package/src/plugins/scheduler/AutomationsPreview.vue +2 -8
  376. package/src/plugins/scheduler/Preview.vue +5 -2
  377. package/src/plugins/scheduler/TasksTab.vue +16 -36
  378. package/src/plugins/scheduler/View.vue +22 -54
  379. package/src/plugins/scheduler/automationsDefinition.ts +14 -9
  380. package/src/plugins/scheduler/automationsMeta.ts +5 -0
  381. package/src/plugins/scheduler/calendarDefinition.ts +4 -7
  382. package/src/plugins/scheduler/calendarMeta.ts +28 -0
  383. package/src/plugins/scheduler/formatSchedule.ts +6 -24
  384. package/src/plugins/scheduler/index.ts +26 -52
  385. package/src/plugins/scope.ts +57 -0
  386. package/src/plugins/server-bindings-types.ts +38 -0
  387. package/src/plugins/server.ts +32 -0
  388. package/src/plugins/skill/Preview.vue +25 -0
  389. package/src/plugins/skill/View.vue +125 -0
  390. package/src/plugins/skill/definition.ts +23 -0
  391. package/src/plugins/skill/index.ts +36 -0
  392. package/src/plugins/skill/plugin.ts +31 -0
  393. package/src/plugins/skill/types.ts +21 -0
  394. package/src/plugins/spreadsheet/Preview.vue +1 -3
  395. package/src/plugins/spreadsheet/View.vue +29 -49
  396. package/src/plugins/spreadsheet/cellHighlights.ts +2 -3
  397. package/src/plugins/spreadsheet/definition.ts +5 -2
  398. package/src/plugins/spreadsheet/index.ts +15 -8
  399. package/src/plugins/spreadsheet/keyboardNav.ts +38 -0
  400. package/src/plugins/spreadsheet/meta.ts +14 -0
  401. package/src/plugins/textResponse/Preview.vue +9 -1
  402. package/src/plugins/textResponse/View.vue +59 -8
  403. package/src/plugins/textResponse/index.ts +11 -3
  404. package/src/plugins/textResponse/plugin.ts +8 -10
  405. package/src/plugins/textResponse/types.ts +28 -0
  406. package/src/plugins/wiki/Preview.vue +6 -4
  407. package/src/plugins/wiki/View.vue +463 -254
  408. package/src/plugins/wiki/components/WikiPageBody.vue +159 -0
  409. package/src/plugins/wiki/helpers.ts +17 -0
  410. package/src/plugins/wiki/history/HistoryDetail.vue +325 -0
  411. package/src/plugins/wiki/history/HistoryTab.vue +167 -0
  412. package/src/plugins/wiki/history/RestoreConfirm.vue +63 -0
  413. package/src/plugins/wiki/history/api.ts +52 -0
  414. package/src/plugins/wiki/history/diff.ts +145 -0
  415. package/src/plugins/wiki/index.ts +42 -32
  416. package/src/plugins/wiki/meta.ts +10 -0
  417. package/src/plugins/wiki/pageEditLoader.ts +53 -0
  418. package/src/plugins/wiki/route.ts +8 -0
  419. package/src/router/guards.ts +2 -1
  420. package/src/router/index.ts +19 -0
  421. package/src/router/pageRoutes.ts +1 -0
  422. package/src/tools/index.ts +50 -51
  423. package/src/tools/runtimeLoader.ts +141 -0
  424. package/src/tools/types.ts +44 -1
  425. package/src/types/notification.ts +23 -0
  426. package/src/types/pastedFile.ts +10 -0
  427. package/src/types/session.ts +61 -3
  428. package/src/types/sse.ts +21 -6
  429. package/src/utils/agent/eventDispatch.ts +12 -9
  430. package/src/utils/agent/pastedAttachment.ts +35 -0
  431. package/src/utils/agent/request.ts +32 -3
  432. package/src/utils/agent/toolCalls.ts +7 -1
  433. package/src/utils/api.ts +1 -1
  434. package/src/utils/chat/exportMarkdown.ts +243 -0
  435. package/src/utils/errors.ts +10 -2
  436. package/src/utils/files/expandedDirs.ts +1 -1
  437. package/src/utils/filesPreview/todoPreview.ts +13 -2
  438. package/src/utils/format/date.ts +1 -3
  439. package/src/utils/format/jsonSyntax.ts +5 -0
  440. package/src/utils/html/iframeHeightReporterScript.ts +62 -0
  441. package/src/utils/html/previewCsp.ts +29 -2
  442. package/src/utils/image/htmlSrcAttrs.ts +122 -0
  443. package/src/utils/image/imageRepairInlineScript.ts +115 -0
  444. package/src/utils/image/resolve.ts +17 -3
  445. package/src/utils/image/rewriteMarkdownImageRefs.ts +62 -9
  446. package/src/utils/markdown/frontmatter.ts +125 -0
  447. package/src/utils/markdown/taskList.ts +7 -2
  448. package/src/utils/plugin/runtime.ts +132 -0
  449. package/src/utils/session/mergeSessions.ts +40 -37
  450. package/src/utils/session/sessionEntries.ts +74 -18
  451. package/src/utils/session/sessionHelpers.ts +54 -10
  452. package/src/utils/tools/result.ts +76 -14
  453. package/src/vite-env.d.ts +6 -0
  454. package/client/assets/html2canvas-Cx501zZr-Bug0qRNv.js +0 -5
  455. package/client/assets/index-CY-WpQUm.css +0 -2
  456. package/client/assets/index-DbTz2Mfs.js +0 -4911
  457. package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
  458. package/server/api/routes/html.ts +0 -114
  459. package/server/api/routes/todos.ts +0 -293
  460. package/server/api/routes/todosColumnsHandlers.ts +0 -333
  461. package/server/api/routes/todosHandlers.ts +0 -274
  462. package/server/api/routes/todosItemsHandlers.ts +0 -386
  463. package/server/utils/files/todos-io.ts +0 -29
  464. package/src/components/NotificationToast.vue +0 -75
  465. package/src/plugins/editImage/definition.ts +0 -27
  466. package/src/plugins/editImage/index.ts +0 -37
  467. package/src/plugins/presentHtml/helpers.ts +0 -72
  468. package/src/plugins/scheduler/LegacySchedulerView.vue +0 -32
  469. package/src/plugins/scheduler/legacyShape.ts +0 -34
  470. package/src/plugins/todo/Preview.vue +0 -68
  471. package/src/plugins/todo/View.vue +0 -378
  472. package/src/plugins/todo/composables/useTodos.ts +0 -179
  473. package/src/plugins/todo/definition.ts +0 -45
  474. package/src/plugins/todo/index.ts +0 -62
  475. package/src/plugins/todo/labels.ts +0 -163
  476. package/src/plugins/todo/priority.ts +0 -98
  477. package/src/plugins/todo/viewModes.ts +0 -19
  478. package/src/plugins/wiki/definition.ts +0 -25
  479. package/src/tools/legacyPluginNames.ts +0 -13
  480. package/src/utils/format/frontmatter.ts +0 -80
  481. package/src/utils/image/rewriteHtmlImageRefs.ts +0 -50
  482. package/src/utils/notification/dispatch.ts +0 -58
  483. /package/client/assets/{purify.es-Fx1Nqyry-BwJECkqS.js → purify.es-Fx1Nqyry-BSVNht6S.js} +0 -0
  484. /package/src/plugins/{editImage → editImages}/Preview.vue +0 -0
  485. /package/src/plugins/{editImage → editImages}/View.vue +0 -0
  486. /package/src/{config/schedulerActions.ts → plugins/scheduler/actions.ts} +0 -0
@@ -0,0 +1,158 @@
1
+ // LLM-backed `MemoryClassifier` for the legacy-memory migration
2
+ // (#1029 PR-B). Asks Claude to look at a single legacy bullet plus
3
+ // its surrounding H2 section header and produce a JSON verdict
4
+ // `{type, description}`. The four types and their meaning live in
5
+ // the system prompt so the LLM can pick consistently across
6
+ // candidates.
7
+ //
8
+ // CLEANUP 2026-07-01: see `run.ts` — this file is part of the
9
+ // one-shot `memory.md` → atomic migration chain and goes when the
10
+ // chain goes.
11
+ //
12
+ // The function is intentionally one-call-per-candidate (not a batch
13
+ // classify): batching saves tokens but couples failures together, so
14
+ // a single malformed verdict could poison the whole batch. Migration
15
+ // is one-time and small — keep the request shape simple.
16
+
17
+ import type { MemoryClassification, MemoryCandidate, MemoryClassifier } from "./migrate.js";
18
+ import { isMemoryType, type MemoryType } from "./types.js";
19
+
20
+ import { ClaudeCliNotFoundError, type Summarize } from "../journal/archivist-cli.js";
21
+ import { errorMessage } from "../../utils/errors.js";
22
+ import { log } from "../../system/logger/index.js";
23
+
24
+ const SYSTEM_PROMPT = `You are classifying a single bullet from a personal-memory file into one of four types.
25
+
26
+ Types:
27
+ - preference: a durable habit, preference, or convention that does not change over time. Examples: "uses yarn (npm not allowed)", "prefers Emacs", "writes commit messages in English".
28
+ - interest: a topic, hobby, or domain the user follows over a long horizon. Examples: "AI research papers", "robotics", "Impressionist painting".
29
+ - fact: a concrete personal fact that COULD become stale. Examples: "planning a trip to Egypt", "owns a toaster oven", "currently working on BootCamp project".
30
+ - reference: a pointer to an internal/external resource (path, dashboard, recurring task id, repo URL). Examples: "main repo at ~/ss/llm/mulmoclaude4", "weekly art-exhibitions-watch task".
31
+
32
+ Rules:
33
+ - Output ONE compact JSON object. No prose, no markdown, no code fences. Output literal JSON only.
34
+ - Schema: {"type":"<one of preference|interest|fact|reference>","description":"<short single-line description, <=100 chars>"}
35
+ - The description is for an index file. Keep it short and informative — strip filler, don't repeat the bullet verbatim.
36
+ - If the bullet is too vague to classify, output: null
37
+ - Never invent fields. Output exactly the schema above or null.`;
38
+
39
+ interface DepsForLlmClassifier {
40
+ /** Same Summarize callback used by `journal/dailyPass`. The
41
+ * classifier feeds it the system prompt + a single-bullet user
42
+ * prompt and parses the JSON reply. */
43
+ summarize: Summarize;
44
+ }
45
+
46
+ export function makeLlmMemoryClassifier(deps: DepsForLlmClassifier): MemoryClassifier {
47
+ return async (candidate: MemoryCandidate) => {
48
+ const userPrompt = buildUserPrompt(candidate);
49
+ let raw: string;
50
+ try {
51
+ raw = await deps.summarize(SYSTEM_PROMPT, userPrompt);
52
+ } catch (err) {
53
+ // Let `ClaudeCliNotFoundError` escape so the caller can abort
54
+ // the whole migration. Without this, every candidate runs
55
+ // through the classifier, gets `null` back, and migrateLegacyMemory
56
+ // produces an empty result + (pre-#1091) renames the legacy file
57
+ // to `.backup`. Even with #1091 the user pays N classifier
58
+ // failures (each spawns claude) before realising the CLI is
59
+ // missing (#1061 review).
60
+ if (err instanceof ClaudeCliNotFoundError) throw err;
61
+ log.warn("memory", "llm classifier: summarize threw", {
62
+ preview: candidate.body.slice(0, 80),
63
+ error: errorMessage(err),
64
+ });
65
+ return null;
66
+ }
67
+ return parseClassifierVerdict(raw);
68
+ };
69
+ }
70
+
71
+ function buildUserPrompt(candidate: MemoryCandidate): string {
72
+ const sectionLine = candidate.section ? `Section header: ${candidate.section}` : "Section header: (none)";
73
+ return [sectionLine, `Bullet: ${candidate.body}`, "", "Output JSON only."].join("\n");
74
+ }
75
+
76
+ // Tolerant JSON extraction — Claude occasionally wraps the verdict in
77
+ // a code fence or adds a leading word despite the prompt ban. Strip
78
+ // surrounding fences and pick out the first `{...}` block (or a bare
79
+ // `null`) before parsing.
80
+ export function parseClassifierVerdict(raw: string): MemoryClassification | null {
81
+ const trimmed = stripFenceAndWhitespace(raw);
82
+ if (trimmed === "null") return null;
83
+ const objectText = extractFirstObject(trimmed);
84
+ if (!objectText) return null;
85
+ let parsed: unknown;
86
+ try {
87
+ parsed = JSON.parse(objectText);
88
+ } catch {
89
+ return null;
90
+ }
91
+ if (!isPlainObject(parsed)) return null;
92
+ const { type, description } = parsed as { type?: unknown; description?: unknown };
93
+ if (!isMemoryType(type)) return null;
94
+ const desc = typeof description === "string" ? description.trim() : "";
95
+ // The description is optional from the spec's perspective —
96
+ // migrate.ts falls back to a body-derived description when missing.
97
+ // But if present, normalise to a single line and cap length.
98
+ // Cap matches the prompt's `<=100 chars>` declaration so persisted
99
+ // entries never exceed the schema the classifier was told to follow
100
+ // (#1061 review).
101
+ return desc.length > 0 ? { type: type as MemoryType, description: oneLine(desc).slice(0, 100) } : { type: type as MemoryType };
102
+ }
103
+
104
+ function stripFenceAndWhitespace(raw: string): string {
105
+ let text = raw.trim();
106
+ if (text.startsWith("```")) {
107
+ const firstNl = text.indexOf("\n");
108
+ if (firstNl >= 0) text = text.slice(firstNl + 1);
109
+ if (text.endsWith("```")) text = text.slice(0, -3);
110
+ }
111
+ return text.trim();
112
+ }
113
+
114
+ function extractFirstObject(text: string): string | null {
115
+ const start = text.indexOf("{");
116
+ if (start < 0) return null;
117
+ let depth = 0;
118
+ let index = start;
119
+ while (index < text.length) {
120
+ const char = text[index];
121
+ if (char === '"') {
122
+ index = skipStringBody(text, index + 1);
123
+ continue;
124
+ }
125
+ if (char === "{") depth += 1;
126
+ else if (char === "}") {
127
+ depth -= 1;
128
+ if (depth === 0) return text.slice(start, index + 1);
129
+ }
130
+ index += 1;
131
+ }
132
+ return null;
133
+ }
134
+
135
+ // Returns the index immediately after the closing `"`, or `text.length`
136
+ // if the string is unterminated. Backslash escapes the next char so
137
+ // `\"` does not close the string.
138
+ function skipStringBody(text: string, fromIndex: number): number {
139
+ let index = fromIndex;
140
+ while (index < text.length) {
141
+ const char = text[index];
142
+ if (char === "\\") {
143
+ index += 2;
144
+ continue;
145
+ }
146
+ if (char === '"') return index + 1;
147
+ index += 1;
148
+ }
149
+ return text.length;
150
+ }
151
+
152
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
153
+ return typeof value === "object" && value !== null && !Array.isArray(value);
154
+ }
155
+
156
+ function oneLine(text: string): string {
157
+ return text.replace(/\s+/g, " ").trim();
158
+ }
@@ -0,0 +1,263 @@
1
+ // Migration of legacy `conversations/memory.md` into the typed
2
+ // directory layout (#1029 PR-A). This module is a library — it does
3
+ // NOT decide when to run. PR-B wires it into `workspace.ts`'s init
4
+ // path so the conversion happens once on first start after the new
5
+ // layout ships.
6
+ //
7
+ // CLEANUP 2026-07-01: see `run.ts` — this file is part of the
8
+ // one-shot `memory.md` → atomic migration chain and goes when the
9
+ // chain goes.
10
+ //
11
+ // Steps:
12
+ // 1. Read existing `conversations/memory.md`. Absent → no-op.
13
+ // 2. Split by `## ` (H2). Each H2 block becomes a candidate entry
14
+ // (one per top-level bullet).
15
+ // 3. Ask the supplied classifier "preference / interest / fact /
16
+ // reference?" per candidate. Null verdict → skip + count.
17
+ // 4. Write each kept candidate as `<type>_<slug>.md`.
18
+ // 5. Rebuild `MEMORY.md`.
19
+ // 6. Rename source to `memory.md.backup` (never deleted).
20
+
21
+ import { mkdir, rename } from "node:fs/promises";
22
+ import path from "node:path";
23
+
24
+ import { writeFileAtomic } from "../../utils/files/atomic.js";
25
+ import { readTextSafe } from "../../utils/files/safe.js";
26
+ import { log } from "../../system/logger/index.js";
27
+ import { errorMessage } from "../../utils/errors.js";
28
+ import { ClaudeCliNotFoundError } from "../journal/archivist-cli.js";
29
+ import { WORKSPACE_FILES } from "../paths.js";
30
+ import { regenerateIndex, writeMemoryEntry } from "./io.js";
31
+ import { isMemoryType, slugifyMemoryName, type MemoryEntry, type MemoryType } from "./types.js";
32
+
33
+ export interface MemoryClassification {
34
+ type: MemoryType;
35
+ /** Optional one-line description. Falls back to a truncated body. */
36
+ description?: string;
37
+ }
38
+
39
+ export type MemoryClassifier = (candidate: MemoryCandidate) => Promise<MemoryClassification | null>;
40
+
41
+ export interface MemoryCandidate {
42
+ /** H2 section header the bullet was found under. Empty string for
43
+ * bullets that appeared before any H2. */
44
+ section: string;
45
+ /** Single-line text of the bullet (lead-in `- ` already stripped). */
46
+ body: string;
47
+ }
48
+
49
+ export interface MigrationResult {
50
+ /** Set when there was nothing to migrate (no `memory.md`). */
51
+ noop: boolean;
52
+ /** Count of candidates accepted, broken down by type. */
53
+ written: Record<MemoryType, number>;
54
+ /** Candidates the classifier returned `null` for. */
55
+ skippedByClassifier: number;
56
+ /** Candidates that errored on write — caller may want to retry or
57
+ * surface to the user. The migration continues past write errors
58
+ * so a single bad entry doesn't strand the whole batch. */
59
+ writeErrors: number;
60
+ }
61
+
62
+ // Number of in-flight `classify` calls. Each call spawns the Claude
63
+ // CLI, so there's a real cost to going wide; 4 is empirically a
64
+ // sweet spot — well above the 1-at-a-time baseline (~10× faster on
65
+ // real legacy files) while staying polite to the user's machine and
66
+ // to Claude's quota. The classifier callback is the only awaited
67
+ // step, so this is the dominant knob.
68
+ const CLASSIFY_CONCURRENCY = 4;
69
+
70
+ // Progress logs emitted every N processed candidates. A long legacy
71
+ // file (50+ bullets) takes minutes; without periodic logging it
72
+ // looks frozen.
73
+ const PROGRESS_LOG_INTERVAL = 5;
74
+
75
+ export async function migrateLegacyMemory(workspaceRoot: string, classify: MemoryClassifier): Promise<MigrationResult> {
76
+ const sourcePath = path.join(workspaceRoot, WORKSPACE_FILES.memory);
77
+ const raw = await readTextSafe(sourcePath);
78
+ if (raw === null) {
79
+ return emptyResult(true);
80
+ }
81
+
82
+ const candidates = parseCandidates(raw);
83
+ log.info("memory", "migration: classifying candidates", {
84
+ candidateCount: candidates.length,
85
+ concurrency: CLASSIFY_CONCURRENCY,
86
+ });
87
+ const classifications = await classifyInParallel(classify, candidates);
88
+
89
+ const result = emptyResult(false);
90
+ const usedSlugs = new Set<string>();
91
+
92
+ for (let index = 0; index < candidates.length; index += 1) {
93
+ const classification = classifications[index];
94
+ const candidate = candidates[index];
95
+ if (!classification) {
96
+ result.skippedByClassifier += 1;
97
+ } else {
98
+ const entry = buildEntry(candidate, classification, usedSlugs);
99
+ try {
100
+ await writeMemoryEntry(workspaceRoot, entry);
101
+ usedSlugs.add(entry.slug);
102
+ result.written[entry.type] += 1;
103
+ } catch (err) {
104
+ log.warn("memory", "migration: write failed", {
105
+ slug: entry.slug,
106
+ error: errorMessage(err),
107
+ });
108
+ result.writeErrors += 1;
109
+ }
110
+ }
111
+ const processed = index + 1;
112
+ if (processed % PROGRESS_LOG_INTERVAL === 0 || processed === candidates.length) {
113
+ log.info("memory", "migration: progress", {
114
+ processed,
115
+ total: candidates.length,
116
+ written: result.written,
117
+ skippedByClassifier: result.skippedByClassifier,
118
+ writeErrors: result.writeErrors,
119
+ });
120
+ }
121
+ }
122
+
123
+ await regenerateIndex(workspaceRoot);
124
+ await renameToBackup(sourcePath);
125
+ return result;
126
+ }
127
+
128
+ // Classify candidates with bounded concurrency. Workers pull
129
+ // indices off a shared cursor and write into a pre-allocated array
130
+ // so `result[i]` matches `candidates[i]`. Each await falls inside
131
+ // `safeClassify`, which already swallows individual classifier
132
+ // throws (logging once); a concurrent failure can therefore not
133
+ // poison the rest of the batch.
134
+ async function classifyInParallel(classify: MemoryClassifier, candidates: readonly MemoryCandidate[]): Promise<(MemoryClassification | null)[]> {
135
+ const results: (MemoryClassification | null)[] = new Array(candidates.length).fill(null);
136
+ let nextIndex = 0;
137
+ const worker = async (): Promise<void> => {
138
+ while (true) {
139
+ const index = nextIndex;
140
+ nextIndex += 1;
141
+ if (index >= candidates.length) return;
142
+ results[index] = await safeClassify(classify, candidates[index]);
143
+ }
144
+ };
145
+ const workers = Array.from({ length: Math.min(CLASSIFY_CONCURRENCY, candidates.length) }, () => worker());
146
+ await Promise.all(workers);
147
+ return results;
148
+ }
149
+
150
+ function emptyResult(noop: boolean): MigrationResult {
151
+ return {
152
+ noop,
153
+ written: { preference: 0, interest: 0, fact: 0, reference: 0 },
154
+ skippedByClassifier: 0,
155
+ writeErrors: 0,
156
+ };
157
+ }
158
+
159
+ // Split the legacy file into candidate bullets. We accept either
160
+ // `- ` or `* ` as bullet markers and ignore continuation lines (a
161
+ // candidate is one bullet line). The H2 header — when present —
162
+ // rides along as `section` so the classifier can use it as context.
163
+ // Parsing is character-driven (no regex) to keep sonar happy and to
164
+ // make the bullet-vs-header classification obvious from the code.
165
+ function parseCandidates(raw: string): MemoryCandidate[] {
166
+ const lines = raw.split("\n");
167
+ let section = "";
168
+ const out: MemoryCandidate[] = [];
169
+ for (const lineRaw of lines) {
170
+ const line = stripCarriageReturn(lineRaw);
171
+ const header = extractHeader(line);
172
+ if (header !== null) {
173
+ section = header;
174
+ continue;
175
+ }
176
+ const bullet = extractBullet(line);
177
+ if (bullet) out.push({ section, body: bullet });
178
+ }
179
+ return out;
180
+ }
181
+
182
+ function stripCarriageReturn(line: string): string {
183
+ return line.endsWith("\r") ? line.slice(0, -1) : line;
184
+ }
185
+
186
+ function extractHeader(line: string): string | null {
187
+ if (!line.startsWith("## ")) return null;
188
+ return line.slice(3).trim();
189
+ }
190
+
191
+ function extractBullet(line: string): string | null {
192
+ const trimmedLeft = line.replace(/^[\t ]+/, "");
193
+ if (!trimmedLeft.startsWith("- ") && !trimmedLeft.startsWith("* ")) return null;
194
+ const body = trimmedLeft.slice(2).trim();
195
+ return body.length > 0 ? body : null;
196
+ }
197
+
198
+ async function safeClassify(classify: MemoryClassifier, candidate: MemoryCandidate): Promise<MemoryClassification | null> {
199
+ try {
200
+ const verdict = await classify(candidate);
201
+ if (verdict && isMemoryType(verdict.type)) return verdict;
202
+ return null;
203
+ } catch (err) {
204
+ // Startup-wide failures (CLI missing) MUST escape so
205
+ // `runMemoryMigrationOnce` can log once + leave the legacy file
206
+ // for retry. Without this, every candidate would silently degrade
207
+ // to `null` and look like normal classifier disagreement
208
+ // (#1061 review).
209
+ if (err instanceof ClaudeCliNotFoundError) throw err;
210
+ log.warn("memory", "migration: classifier threw", {
211
+ preview: candidate.body.slice(0, 80),
212
+ error: errorMessage(err),
213
+ });
214
+ return null;
215
+ }
216
+ }
217
+
218
+ function buildEntry(candidate: MemoryCandidate, classification: MemoryClassification, usedSlugs: Set<string>): MemoryEntry {
219
+ const name = candidate.body.length > 80 ? `${candidate.body.slice(0, 77)}…` : candidate.body;
220
+ const description = classification.description?.trim() || truncateForDescription(candidate.body);
221
+ const baseSlug = slugifyMemoryName(candidate.body, classification.type);
222
+ const slug = uniqueSlug(baseSlug, usedSlugs);
223
+ return {
224
+ name,
225
+ description,
226
+ type: classification.type,
227
+ body: candidate.body,
228
+ slug,
229
+ };
230
+ }
231
+
232
+ function truncateForDescription(text: string): string {
233
+ const oneLine = text.replace(/\s+/g, " ").trim();
234
+ return oneLine.length > 120 ? `${oneLine.slice(0, 117)}…` : oneLine;
235
+ }
236
+
237
+ function uniqueSlug(base: string, used: Set<string>): string {
238
+ if (!used.has(base)) return base;
239
+ let counter = 2;
240
+ while (used.has(`${base}-${counter}`)) counter += 1;
241
+ return `${base}-${counter}`;
242
+ }
243
+
244
+ async function renameToBackup(sourcePath: string): Promise<void> {
245
+ const backupPath = `${sourcePath}.backup`;
246
+ try {
247
+ await rename(sourcePath, backupPath);
248
+ } catch (err) {
249
+ log.warn("memory", "migration: backup rename failed", {
250
+ sourcePath,
251
+ error: errorMessage(err),
252
+ });
253
+ }
254
+ }
255
+
256
+ // Bare write of the source file used in tests for setup. Production
257
+ // callers don't need this — `memory.md` is created by user activity
258
+ // or by an older mulmoclaude version.
259
+ export async function writeLegacyMemoryForTest(workspaceRoot: string, content: string): Promise<void> {
260
+ const sourcePath = path.join(workspaceRoot, WORKSPACE_FILES.memory);
261
+ await mkdir(path.dirname(sourcePath), { recursive: true });
262
+ await writeFileAtomic(sourcePath, content);
263
+ }
@@ -0,0 +1,84 @@
1
+ // One-shot legacy-memory migration entry point used by server
2
+ // startup (#1029 PR-B). Idempotent: returns immediately when there
3
+ // is nothing to do (legacy file absent or already migrated).
4
+ //
5
+ // Concurrency: the agent may serve requests before this finishes.
6
+ // The brief race window is documented in
7
+ // `plans/done/feat-memory-storage-wire.md`.
8
+ //
9
+ // CLEANUP 2026-07-01: this is one-shot migration code for the
10
+ // `memory.md` → atomic transition (#1029). After every active
11
+ // workspace has run through both `runMemoryMigrationOnce` and the
12
+ // follow-on `runTopicMigrationOnce`, this file plus the rest of
13
+ // the migration chain (`migrate.ts`, `llm-classifier.ts`, the
14
+ // atomic-aware branches in `prompt.ts`, the chain in
15
+ // `server/index.ts`) can be deleted in one sweep. Workspaces that
16
+ // have not migrated by then will need a manual conversion path —
17
+ // document the recovery before removing the code.
18
+
19
+ import { existsSync, statSync } from "node:fs";
20
+ import path from "node:path";
21
+
22
+ import { runClaudeCli, ClaudeCliNotFoundError, type Summarize } from "../journal/archivist-cli.js";
23
+ import { makeLlmMemoryClassifier } from "./llm-classifier.js";
24
+ import { migrateLegacyMemory } from "./migrate.js";
25
+ import { errorMessage } from "../../utils/errors.js";
26
+ import { log } from "../../system/logger/index.js";
27
+
28
+ export interface RunMigrationDeps {
29
+ /** Override the summarize callback (useful for tests). Defaults to
30
+ * the production `runClaudeCli` which spawns the Claude CLI. */
31
+ summarize?: Summarize;
32
+ }
33
+
34
+ export async function runMemoryMigrationOnce(workspaceRoot: string, deps: RunMigrationDeps = {}): Promise<void> {
35
+ const legacyPath = path.join(workspaceRoot, "conversations", "memory.md");
36
+ if (!existsSync(legacyPath)) {
37
+ log.debug("memory", "migration: no legacy file, skipping");
38
+ return;
39
+ }
40
+ // If the file is suspiciously empty (just the placeholder we used
41
+ // to write at init), skip — the migration would yield zero entries
42
+ // and the rename to `.backup` is pointless.
43
+ const stat = statSync(legacyPath);
44
+ if (stat.size < 64) {
45
+ log.info("memory", "migration: legacy file is below the placeholder threshold, skipping");
46
+ return;
47
+ }
48
+ // The rename to `.backup` is the final step of `migrateLegacyMemory`,
49
+ // so its presence is the "migration completed" marker. A workspace
50
+ // where both `memory.md` AND `memory.md.backup` exist means the
51
+ // user re-introduced the legacy file after a previous successful
52
+ // migration (probably by mistake or by extracting the backup).
53
+ // Re-running here would re-classify the bullets and could clobber
54
+ // typed entries the user has been editing in place; skip with a
55
+ // clear log instead. The interrupted-migration retry case still
56
+ // works because no `.backup` exists in that state — the rename
57
+ // never ran.
58
+ const backupPath = `${legacyPath}.backup`;
59
+ if (existsSync(backupPath)) {
60
+ log.info("memory", "migration: legacy file present but .backup also exists — refusing to re-run", {
61
+ legacyPath,
62
+ backupPath,
63
+ });
64
+ return;
65
+ }
66
+ const summarize = deps.summarize ?? runClaudeCli;
67
+ const classifier = makeLlmMemoryClassifier({ summarize });
68
+ log.info("memory", "migration: starting", { legacyPath });
69
+ try {
70
+ const result = await migrateLegacyMemory(workspaceRoot, classifier);
71
+ log.info("memory", "migration: done", {
72
+ noop: result.noop,
73
+ written: result.written,
74
+ skippedByClassifier: result.skippedByClassifier,
75
+ writeErrors: result.writeErrors,
76
+ });
77
+ } catch (err) {
78
+ if (err instanceof ClaudeCliNotFoundError) {
79
+ log.warn("memory", "migration: claude CLI not on PATH; legacy memory left in place");
80
+ return;
81
+ }
82
+ log.error("memory", "migration: threw, legacy memory left in place", { error: errorMessage(err) });
83
+ }
84
+ }