@runfusion/fusion 0.25.0 → 0.27.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 (226) hide show
  1. package/README.md +6 -0
  2. package/dist/bin.js +28004 -16888
  3. package/dist/client/assets/AgentDetailView-B7QRcHJH.css +1 -0
  4. package/dist/client/assets/AgentDetailView-DwLmRXTY.js +18 -0
  5. package/dist/client/assets/{AgentsView-B3jYk8Kt.js → AgentsView-D-N6aA0P.js} +12 -7
  6. package/dist/client/assets/ChatView-DnCdKu8Z.js +1 -0
  7. package/dist/client/assets/{DevServerView-DyGDEiBP.js → DevServerView-BiA1nYtt.js} +1 -1
  8. package/dist/client/assets/{DirectoryPicker-D5UIeIl6.js → DirectoryPicker-DvBviDG6.js} +1 -1
  9. package/dist/client/assets/{DocumentsView-DNHu1T8K.js → DocumentsView-BWXOxpuq.js} +1 -1
  10. package/dist/client/assets/{EvalsView-CpRobtDi.js → EvalsView-CJFbtL7i.js} +1 -1
  11. package/dist/client/assets/{ExperimentalAgentOnboardingModal-DOY_oZi7.js → ExperimentalAgentOnboardingModal-DuGIPd0B.js} +1 -1
  12. package/dist/client/assets/InsightsView-BBpRiolN.js +11 -0
  13. package/dist/client/assets/{MemoryView-PSc5lGJt.js → MemoryView-48LuNkKk.js} +2 -2
  14. package/dist/client/assets/NodesView-CGQWSNZM.js +14 -0
  15. package/dist/client/assets/{PiExtensionsManager-DL_QcN56.js → PiExtensionsManager-i-7UL2oh.js} +2 -2
  16. package/dist/client/assets/PluginManager-DoSAykD6.js +1 -0
  17. package/dist/client/assets/{ResearchView-BzCcDAS4.css → ResearchView-BEI4ZSGs.css} +1 -1
  18. package/dist/client/assets/ResearchView-XZuRtOxE.js +1 -0
  19. package/dist/client/assets/SettingsModal-Ci0_sqbU.css +1 -0
  20. package/dist/client/assets/{SettingsModal-CUCyaAyE.js → SettingsModal-CmeF8CN4.js} +1 -1
  21. package/dist/client/assets/SettingsModal-DBcjf9Bu.js +31 -0
  22. package/dist/client/assets/SettingsModal-DWKgRxBA.css +1 -0
  23. package/dist/client/assets/{SetupWizardModal-BKscasuh.js → SetupWizardModal-CgtvpMX9.js} +1 -1
  24. package/dist/client/assets/{SkillsView-BdELqTy7.js → SkillsView-DErYRumF.js} +1 -1
  25. package/dist/client/assets/StashRecoveryView-B_8WIQEo.css +1 -0
  26. package/dist/client/assets/StashRecoveryView-QJrNS4Vg.js +1 -0
  27. package/dist/client/assets/{TodoView-DFNGBDNV.js → TodoView-BD9NRwq0.js} +2 -2
  28. package/dist/client/assets/createLucideIcon-BazL2hk5.js +21 -0
  29. package/dist/client/assets/dashboard-view-BWGH_fAq.js +63 -0
  30. package/dist/client/assets/dashboard-view-BoTzlP8b.css +1 -0
  31. package/dist/client/assets/dashboard-view-DdGlfuu-.css +1 -0
  32. package/dist/client/assets/dashboard-view-Ws9_ZnKu.js +21 -0
  33. package/dist/client/assets/{folder-open-k1xmUMyr.js → folder-open-CHSlllzf.js} +1 -1
  34. package/dist/client/assets/index-DCovGm5b.css +1 -0
  35. package/dist/client/assets/index-bEwSVl7B.js +692 -0
  36. package/dist/client/assets/{star-ne32r3Y4.js → star-BgVwWAPz.js} +1 -1
  37. package/dist/client/assets/{upload-MS-2Gx53.js → upload-CAzycxr9.js} +1 -1
  38. package/dist/client/assets/{users-C519GSjH.js → users-CZnxCCCJ.js} +1 -1
  39. package/dist/client/index.html +2 -2
  40. package/dist/client/version.json +1 -1
  41. package/dist/droid-cli/package.json +1 -1
  42. package/dist/droid-cli/src/__tests__/index.test.ts +228 -0
  43. package/dist/extension.js +15810 -10205
  44. package/dist/pi-claude-cli/package.json +1 -1
  45. package/dist/pi-claude-cli/src/__tests__/provider.test.ts +36 -22
  46. package/dist/pi-claude-cli/src/provider.ts +7 -1
  47. package/dist/plugins/fusion-plugin-cli-printing-press/manifest.json +24 -0
  48. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +44 -0
  49. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/TestRunnerPanel.test.tsx +99 -0
  50. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/config-flow.test.ts +91 -0
  51. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/dashboard-view.test.tsx +40 -0
  52. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/dashboard-views.test.ts +46 -0
  53. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/draft-store.test.ts +50 -0
  54. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/exec-mock.ts +80 -0
  55. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/fixtures.test.ts +40 -0
  56. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/registry.ts +82 -0
  57. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/generator.test.ts +54 -0
  58. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manage-view.test.tsx +98 -0
  59. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manifest.test.ts +36 -0
  60. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/registration.test.ts +29 -0
  61. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/run-routes.test.ts +98 -0
  62. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/runner.test.ts +55 -0
  63. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/runtime-availability.test.ts +61 -0
  64. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/validation.test.ts +30 -0
  65. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/wizard-routes.test.ts +61 -0
  66. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/workflow-integration.test.ts +19 -0
  67. package/dist/plugins/fusion-plugin-cli-printing-press/src/dashboard-view.css +43 -0
  68. package/dist/plugins/fusion-plugin-cli-printing-press/src/dashboard-view.tsx +49 -0
  69. package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/generator.ts +95 -0
  70. package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/redact.ts +9 -0
  71. package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/runner.ts +79 -0
  72. package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/types.ts +31 -0
  73. package/dist/plugins/fusion-plugin-cli-printing-press/src/index.ts +58 -0
  74. package/dist/plugins/fusion-plugin-cli-printing-press/src/manage/EditDraftModal.tsx +75 -0
  75. package/dist/plugins/fusion-plugin-cli-printing-press/src/manage/useDrafts.ts +73 -0
  76. package/dist/plugins/fusion-plugin-cli-printing-press/src/manage-view.css +79 -0
  77. package/dist/plugins/fusion-plugin-cli-printing-press/src/manage-view.tsx +122 -0
  78. package/dist/plugins/fusion-plugin-cli-printing-press/src/routes/wizard-routes.ts +272 -0
  79. package/dist/plugins/fusion-plugin-cli-printing-press/src/run/TestRunnerPanel.css +70 -0
  80. package/dist/plugins/fusion-plugin-cli-printing-press/src/run/TestRunnerPanel.tsx +98 -0
  81. package/dist/plugins/fusion-plugin-cli-printing-press/src/run/useRunGeneratedCli.ts +37 -0
  82. package/dist/plugins/fusion-plugin-cli-printing-press/src/runtime/__tests__/executor-runtime-env.test.ts +191 -0
  83. package/dist/plugins/fusion-plugin-cli-printing-press/src/runtime/executor-runtime-env.ts +75 -0
  84. package/dist/plugins/fusion-plugin-cli-printing-press/src/storage/draft-store.ts +85 -0
  85. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/__tests__/cli-press-store.test.ts +128 -0
  86. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/__tests__/credentials.test.ts +62 -0
  87. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/cli-press-store.ts +427 -0
  88. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/cli-press-types.ts +110 -0
  89. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/credentials.ts +95 -0
  90. package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/steps.tsx +55 -0
  91. package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/types.ts +33 -0
  92. package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/validation.ts +63 -0
  93. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  94. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  95. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  96. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  97. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  98. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  99. package/dist/plugins/fusion-plugin-reports/manifest.json +10 -0
  100. package/dist/plugins/fusion-plugin-reports/package.json +18 -2
  101. package/dist/plugins/fusion-plugin-reports/src/__tests__/approval.test.ts +164 -0
  102. package/dist/plugins/fusion-plugin-reports/src/__tests__/manifest.test.ts +14 -0
  103. package/dist/plugins/fusion-plugin-reports/src/__tests__/routes-approval.test.ts +109 -0
  104. package/dist/plugins/fusion-plugin-reports/src/__tests__/scaffold.test.ts +60 -0
  105. package/dist/plugins/fusion-plugin-reports/src/__tests__/share-blocks.test.ts +83 -0
  106. package/dist/plugins/fusion-plugin-reports/src/aggregation.ts +23 -0
  107. package/dist/plugins/fusion-plugin-reports/src/approval.ts +97 -0
  108. package/dist/plugins/fusion-plugin-reports/src/cadence.ts +23 -0
  109. package/dist/plugins/fusion-plugin-reports/src/dashboard/ReportsView.css +82 -0
  110. package/dist/plugins/fusion-plugin-reports/src/dashboard/ReportsView.tsx +24 -0
  111. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportComparisonDrawer.test.tsx +12 -0
  112. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportDetailPanel.test.tsx +12 -0
  113. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportFiltersBar.test.tsx +14 -0
  114. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportsView.test.tsx +27 -0
  115. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/api.test.ts +19 -0
  116. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/useReportSectionDiff.test.ts +11 -0
  117. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/useReports.test.ts +13 -0
  118. package/dist/plugins/fusion-plugin-reports/src/dashboard/api.ts +85 -0
  119. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportApprovalPanel.css +59 -0
  120. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportApprovalPanel.tsx +58 -0
  121. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportComparisonDrawer.tsx +21 -0
  122. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportDetailPanel.tsx +29 -0
  123. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportEmptyState.tsx +3 -0
  124. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportFiltersBar.tsx +19 -0
  125. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportListItem.tsx +8 -0
  126. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ShareBlocksPanel.css +29 -0
  127. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ShareBlocksPanel.tsx +43 -0
  128. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/__tests__/ReportApprovalPanel.test.tsx +38 -0
  129. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/__tests__/ShareBlocksPanel.test.tsx +24 -0
  130. package/dist/plugins/fusion-plugin-reports/src/dashboard/test-setup.ts +18 -0
  131. package/dist/plugins/fusion-plugin-reports/src/dashboard/types.ts +22 -0
  132. package/dist/plugins/fusion-plugin-reports/src/dashboard/useReportPreview.ts +44 -0
  133. package/dist/plugins/fusion-plugin-reports/src/dashboard/useReportSectionDiff.ts +59 -0
  134. package/dist/plugins/fusion-plugin-reports/src/dashboard/useReports.ts +71 -0
  135. package/dist/plugins/fusion-plugin-reports/src/dashboard/useViewportMode.ts +13 -0
  136. package/dist/plugins/fusion-plugin-reports/src/dashboard-view.tsx +6 -0
  137. package/dist/plugins/fusion-plugin-reports/src/index.ts +48 -2
  138. package/dist/plugins/fusion-plugin-reports/src/pipeline.ts +58 -0
  139. package/dist/plugins/fusion-plugin-reports/src/render/__tests__/escape.test.ts +20 -0
  140. package/dist/plugins/fusion-plugin-reports/src/render/__tests__/html-template.test.ts +110 -0
  141. package/dist/plugins/fusion-plugin-reports/src/render/__tests__/standalone-html.test.ts +66 -0
  142. package/dist/plugins/fusion-plugin-reports/src/render/escape.ts +12 -0
  143. package/dist/plugins/fusion-plugin-reports/src/render/html-styles.ts +40 -0
  144. package/dist/plugins/fusion-plugin-reports/src/render/html-template.ts +137 -0
  145. package/dist/plugins/fusion-plugin-reports/src/render/index.ts +4 -0
  146. package/dist/plugins/fusion-plugin-reports/src/render/standalone-html.ts +75 -0
  147. package/dist/plugins/fusion-plugin-reports/src/report-schema.ts +31 -0
  148. package/dist/plugins/fusion-plugin-reports/src/routes/__tests__/report-export-routes.test.ts +104 -0
  149. package/dist/plugins/fusion-plugin-reports/src/routes/report-approval-routes.ts +98 -0
  150. package/dist/plugins/fusion-plugin-reports/src/routes/report-export-routes.ts +77 -0
  151. package/dist/plugins/fusion-plugin-reports/src/routes/report-list-routes.ts +72 -0
  152. package/dist/plugins/fusion-plugin-reports/src/runs-store.ts +69 -0
  153. package/dist/plugins/fusion-plugin-reports/src/share-blocks.ts +82 -0
  154. package/dist/plugins/fusion-plugin-reports/src/store/report-store.ts +51 -2
  155. package/dist/plugins/fusion-plugin-reports/src/store/report-types.ts +6 -1
  156. package/dist/plugins/fusion-plugin-roadmap/bundled.js +1672 -0
  157. package/dist/plugins/fusion-plugin-roadmap/manifest.json +1 -1
  158. package/dist/plugins/fusion-plugin-roadmap/package.json +4 -41
  159. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  160. package/package.json +2 -3
  161. package/skill/fusion/references/engine-tools.md +1 -1
  162. package/skill/fusion/references/extension-tools.md +3 -3
  163. package/skill/fusion/references/fusion-capabilities.md +1 -1
  164. package/dist/client/assets/AgentDetailView-BwJaLqZh.css +0 -1
  165. package/dist/client/assets/AgentDetailView-ZbHEbYRT.js +0 -18
  166. package/dist/client/assets/ChatView-DhPkiEGs.js +0 -1
  167. package/dist/client/assets/InsightsView-vp0RE8Mg.js +0 -11
  168. package/dist/client/assets/NodesView-DMj6HGeC.js +0 -14
  169. package/dist/client/assets/PluginManager-BtYKm8IT.js +0 -1
  170. package/dist/client/assets/ResearchView-BhWqfdV0.js +0 -1
  171. package/dist/client/assets/SettingsModal-BAgB4_AR.js +0 -31
  172. package/dist/client/assets/SettingsModal-BNSrO1M9.css +0 -1
  173. package/dist/client/assets/SettingsModal-DzsLquBu.css +0 -1
  174. package/dist/client/assets/index-Qq2JOOWx.css +0 -1
  175. package/dist/client/assets/index-TFYXEVpn.js +0 -692
  176. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/api-client.test.ts +0 -101
  177. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/index.test.ts +0 -92
  178. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/roadmap-routes.test.ts +0 -48
  179. package/dist/plugins/fusion-plugin-roadmap/src/__tests__/roadmap-suggestions.test.ts +0 -31
  180. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.css +0 -1299
  181. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.tsx +0 -2559
  182. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/__tests__/RoadmapsView.test.tsx +0 -1144
  183. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/__tests__/useRoadmaps.test.ts +0 -1756
  184. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/api.ts +0 -70
  185. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/test-setup.ts +0 -7
  186. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/types.ts +0 -1
  187. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useConfirm.ts +0 -8
  188. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useRoadmaps.ts +0 -1188
  189. package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useViewportMode.ts +0 -20
  190. package/dist/plugins/fusion-plugin-roadmap/src/dashboard-view.tsx +0 -6
  191. package/dist/plugins/fusion-plugin-roadmap/src/index.ts +0 -74
  192. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-routes.ts +0 -1
  193. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-schema.ts +0 -41
  194. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-suggestions.d.ts +0 -15
  195. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-suggestions.ts +0 -15
  196. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.d.ts +0 -283
  197. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.d.ts.map +0 -1
  198. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.js +0 -21
  199. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.js.map +0 -1
  200. package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.ts +0 -310
  201. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.d.ts +0 -5
  202. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.d.ts.map +0 -1
  203. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js +0 -361
  204. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js.map +0 -1
  205. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.ts +0 -408
  206. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.d.ts +0 -68
  207. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.d.ts.map +0 -1
  208. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js +0 -300
  209. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js.map +0 -1
  210. package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.ts +0 -381
  211. package/dist/plugins/fusion-plugin-roadmap/src/server/index.d.ts +0 -3
  212. package/dist/plugins/fusion-plugin-roadmap/src/server/index.ts +0 -1
  213. package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-handoff.test.ts +0 -445
  214. package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-ordering.test.ts +0 -334
  215. package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-store.test.ts +0 -1318
  216. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-handoff.ts +0 -163
  217. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.d.ts +0 -37
  218. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.d.ts.map +0 -1
  219. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js +0 -188
  220. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js.map +0 -1
  221. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.ts +0 -311
  222. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.d.ts +0 -299
  223. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.d.ts.map +0 -1
  224. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.js +0 -765
  225. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.js.map +0 -1
  226. package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.ts +0 -1001
@@ -0,0 +1,1672 @@
1
+ // ../plugin-sdk/src/index.ts
2
+ function definePlugin(plugin2) {
3
+ return plugin2;
4
+ }
5
+
6
+ // ../../plugins/fusion-plugin-roadmap/src/store/roadmap-store.js
7
+ import { EventEmitter } from "node:events";
8
+
9
+ // ../../plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js
10
+ function compareOrderedEntities(a, b) {
11
+ if (a.orderIndex !== b.orderIndex) {
12
+ return a.orderIndex - b.orderIndex;
13
+ }
14
+ if (a.createdAt !== b.createdAt) {
15
+ return a.createdAt.localeCompare(b.createdAt);
16
+ }
17
+ return a.id.localeCompare(b.id);
18
+ }
19
+ function clampInsertionIndex(targetIndex, length) {
20
+ if (!Number.isFinite(targetIndex)) {
21
+ return length;
22
+ }
23
+ const normalized = Math.trunc(targetIndex);
24
+ if (normalized < 0) {
25
+ return 0;
26
+ }
27
+ if (normalized > length) {
28
+ return length;
29
+ }
30
+ return normalized;
31
+ }
32
+ function assertScopedRoadmapMilestones(milestones, roadmapId) {
33
+ for (const milestone of milestones) {
34
+ if (milestone.roadmapId !== roadmapId) {
35
+ throw new Error(`Milestone ${milestone.id} does not belong to roadmap ${roadmapId}`);
36
+ }
37
+ }
38
+ }
39
+ function assertScopedMilestoneFeatures(features, milestoneId) {
40
+ for (const feature of features) {
41
+ if (feature.milestoneId !== milestoneId) {
42
+ throw new Error(`Feature ${feature.id} does not belong to milestone ${milestoneId}`);
43
+ }
44
+ }
45
+ }
46
+ function assertScopedMoveFeatures(features, fromMilestoneId, toMilestoneId) {
47
+ const validMilestoneIds = /* @__PURE__ */ new Set([fromMilestoneId, toMilestoneId]);
48
+ for (const feature of features) {
49
+ if (!validMilestoneIds.has(feature.milestoneId)) {
50
+ throw new Error(`Feature ${feature.id} is outside the affected milestone scope (${fromMilestoneId} \u2192 ${toMilestoneId})`);
51
+ }
52
+ }
53
+ }
54
+ function assertExactIdSet(entityLabel, actualIds, orderedIds) {
55
+ const requestedIds = /* @__PURE__ */ new Set();
56
+ for (const id of orderedIds) {
57
+ if (requestedIds.has(id)) {
58
+ throw new Error(`Duplicate ${entityLabel} id in requested order: ${id}`);
59
+ }
60
+ requestedIds.add(id);
61
+ }
62
+ if (actualIds.length !== orderedIds.length) {
63
+ throw new Error(`Expected ${actualIds.length} ${entityLabel} ids but received ${orderedIds.length}`);
64
+ }
65
+ const actualIdSet = new Set(actualIds);
66
+ for (const id of orderedIds) {
67
+ if (!actualIdSet.has(id)) {
68
+ throw new Error(`${capitalize(entityLabel)} ${id} not found in scoped list`);
69
+ }
70
+ }
71
+ for (const id of actualIds) {
72
+ if (!requestedIds.has(id)) {
73
+ throw new Error(`Missing ${entityLabel} id in requested order: ${id}`);
74
+ }
75
+ }
76
+ }
77
+ function capitalize(value) {
78
+ return value.charAt(0).toUpperCase() + value.slice(1);
79
+ }
80
+ function assignContiguousOrder(items) {
81
+ return items.map((item, orderIndex) => {
82
+ if (item.orderIndex === orderIndex) {
83
+ return { ...item };
84
+ }
85
+ return {
86
+ ...item,
87
+ orderIndex
88
+ };
89
+ });
90
+ }
91
+ function normalizeRoadmapMilestoneOrder(milestones) {
92
+ if (milestones.length === 0) {
93
+ return [];
94
+ }
95
+ assertScopedRoadmapMilestones(milestones, milestones[0].roadmapId);
96
+ return assignContiguousOrder([...milestones].sort(compareOrderedEntities));
97
+ }
98
+ function applyRoadmapMilestoneReorder(milestones, input) {
99
+ assertScopedRoadmapMilestones(milestones, input.roadmapId);
100
+ const normalized = normalizeRoadmapMilestoneOrder(milestones);
101
+ const ids = normalized.map((milestone) => milestone.id);
102
+ assertExactIdSet("milestone", ids, input.orderedMilestoneIds);
103
+ const byId = new Map(normalized.map((milestone) => [milestone.id, milestone]));
104
+ return assignContiguousOrder(input.orderedMilestoneIds.map((id) => byId.get(id)));
105
+ }
106
+ function normalizeRoadmapFeatureOrder(features) {
107
+ if (features.length === 0) {
108
+ return [];
109
+ }
110
+ assertScopedMilestoneFeatures(features, features[0].milestoneId);
111
+ return assignContiguousOrder([...features].sort(compareOrderedEntities));
112
+ }
113
+ function applyRoadmapFeatureReorder(features, input) {
114
+ assertScopedMilestoneFeatures(features, input.milestoneId);
115
+ const normalized = normalizeRoadmapFeatureOrder(features);
116
+ const ids = normalized.map((feature) => feature.id);
117
+ assertExactIdSet("feature", ids, input.orderedFeatureIds);
118
+ const byId = new Map(normalized.map((feature) => [feature.id, feature]));
119
+ return assignContiguousOrder(input.orderedFeatureIds.map((id) => byId.get(id)));
120
+ }
121
+ function moveRoadmapFeature(features, input) {
122
+ assertScopedMoveFeatures(features, input.fromMilestoneId, input.toMilestoneId);
123
+ const existingFeature = features.find((feature) => feature.id === input.featureId);
124
+ if (!existingFeature) {
125
+ throw new Error(`Feature ${input.featureId} not found in affected milestone scope`);
126
+ }
127
+ if (existingFeature.milestoneId !== input.fromMilestoneId) {
128
+ throw new Error(`Feature ${input.featureId} does not belong to milestone ${input.fromMilestoneId}`);
129
+ }
130
+ const sourceFeatures = normalizeRoadmapFeatureOrder(features.filter((feature) => feature.milestoneId === input.fromMilestoneId));
131
+ const sourceWithoutFeature = sourceFeatures.filter((feature) => feature.id !== input.featureId);
132
+ if (input.fromMilestoneId === input.toMilestoneId) {
133
+ const insertionIndex2 = clampInsertionIndex(input.targetOrderIndex, sourceWithoutFeature.length);
134
+ const reordered = [...sourceWithoutFeature];
135
+ reordered.splice(insertionIndex2, 0, {
136
+ ...existingFeature,
137
+ milestoneId: input.toMilestoneId,
138
+ orderIndex: insertionIndex2
139
+ });
140
+ const normalized = assignContiguousOrder(reordered);
141
+ const movedFeature2 = normalized.find((feature) => feature.id === input.featureId);
142
+ return {
143
+ movedFeature: movedFeature2,
144
+ affectedFeatures: normalized,
145
+ sourceMilestoneFeatures: normalized,
146
+ targetMilestoneFeatures: normalized
147
+ };
148
+ }
149
+ const targetFeatures = normalizeRoadmapFeatureOrder(features.filter((feature) => feature.milestoneId === input.toMilestoneId));
150
+ const insertionIndex = clampInsertionIndex(input.targetOrderIndex, targetFeatures.length);
151
+ const targetWithInsertedFeature = [...targetFeatures];
152
+ targetWithInsertedFeature.splice(insertionIndex, 0, {
153
+ ...existingFeature,
154
+ milestoneId: input.toMilestoneId,
155
+ orderIndex: insertionIndex
156
+ });
157
+ const normalizedSource = assignContiguousOrder(sourceWithoutFeature);
158
+ const normalizedTarget = assignContiguousOrder(targetWithInsertedFeature);
159
+ const movedFeature = normalizedTarget.find((feature) => feature.id === input.featureId);
160
+ return {
161
+ movedFeature,
162
+ affectedFeatures: [...normalizedSource, ...normalizedTarget],
163
+ sourceMilestoneFeatures: normalizedSource,
164
+ targetMilestoneFeatures: normalizedTarget
165
+ };
166
+ }
167
+
168
+ // ../../plugins/fusion-plugin-roadmap/src/store/roadmap-store.js
169
+ var RoadmapStore = class extends EventEmitter {
170
+ db;
171
+ /**
172
+ * Creates a new RoadmapStore instance.
173
+ *
174
+ * @param db - Shared Database instance (same instance used by TaskStore)
175
+ */
176
+ constructor(db) {
177
+ super();
178
+ this.db = db;
179
+ this.setMaxListeners(50);
180
+ this.ensureSchema();
181
+ }
182
+ ensureSchema() {
183
+ this.db.exec(`
184
+ CREATE TABLE IF NOT EXISTS roadmaps (
185
+ id TEXT PRIMARY KEY,
186
+ title TEXT NOT NULL,
187
+ description TEXT,
188
+ createdAt TEXT NOT NULL,
189
+ updatedAt TEXT NOT NULL
190
+ );
191
+
192
+ CREATE TABLE IF NOT EXISTS roadmap_milestones (
193
+ id TEXT PRIMARY KEY,
194
+ roadmapId TEXT NOT NULL,
195
+ title TEXT NOT NULL,
196
+ description TEXT,
197
+ orderIndex INTEGER NOT NULL,
198
+ createdAt TEXT NOT NULL,
199
+ updatedAt TEXT NOT NULL,
200
+ FOREIGN KEY (roadmapId) REFERENCES roadmaps(id) ON DELETE CASCADE
201
+ );
202
+
203
+ CREATE TABLE IF NOT EXISTS roadmap_features (
204
+ id TEXT PRIMARY KEY,
205
+ milestoneId TEXT NOT NULL,
206
+ title TEXT NOT NULL,
207
+ description TEXT,
208
+ orderIndex INTEGER NOT NULL,
209
+ createdAt TEXT NOT NULL,
210
+ updatedAt TEXT NOT NULL,
211
+ FOREIGN KEY (milestoneId) REFERENCES roadmap_milestones(id) ON DELETE CASCADE
212
+ );
213
+
214
+ CREATE INDEX IF NOT EXISTS idxRoadmapMilestonesRoadmapOrder
215
+ ON roadmap_milestones(roadmapId, orderIndex, createdAt, id);
216
+
217
+ CREATE INDEX IF NOT EXISTS idxRoadmapFeaturesMilestoneOrder
218
+ ON roadmap_features(milestoneId, orderIndex, createdAt, id);
219
+ `);
220
+ }
221
+ // ── ID Generators ───────────────────────────────────────────────────
222
+ generateRoadmapId() {
223
+ const timestamp = Date.now();
224
+ const random = Math.random().toString(36).substring(2, 6).toUpperCase();
225
+ return `RM-${timestamp.toString(36).toUpperCase()}-${random}`;
226
+ }
227
+ generateMilestoneId() {
228
+ const timestamp = Date.now();
229
+ const random = Math.random().toString(36).substring(2, 6).toUpperCase();
230
+ return `RMS-${timestamp.toString(36).toUpperCase()}-${random}`;
231
+ }
232
+ generateFeatureId() {
233
+ const timestamp = Date.now();
234
+ const random = Math.random().toString(36).substring(2, 6).toUpperCase();
235
+ return `RF-${timestamp.toString(36).toUpperCase()}-${random}`;
236
+ }
237
+ // ── Row-to-Object Converters ───────────────────────────────────────
238
+ rowToRoadmap(row) {
239
+ return {
240
+ id: row.id,
241
+ title: row.title,
242
+ description: row.description || void 0,
243
+ createdAt: row.createdAt,
244
+ updatedAt: row.updatedAt
245
+ };
246
+ }
247
+ rowToMilestone(row) {
248
+ return {
249
+ id: row.id,
250
+ roadmapId: row.roadmapId,
251
+ title: row.title,
252
+ description: row.description || void 0,
253
+ orderIndex: row.orderIndex,
254
+ createdAt: row.createdAt,
255
+ updatedAt: row.updatedAt
256
+ };
257
+ }
258
+ rowToFeature(row) {
259
+ return {
260
+ id: row.id,
261
+ milestoneId: row.milestoneId,
262
+ title: row.title,
263
+ description: row.description || void 0,
264
+ orderIndex: row.orderIndex,
265
+ createdAt: row.createdAt,
266
+ updatedAt: row.updatedAt
267
+ };
268
+ }
269
+ // ── Roadmap CRUD ─────────────────────────────────────────────────
270
+ /**
271
+ * Create a new roadmap.
272
+ *
273
+ * @param input - Roadmap creation input
274
+ * @returns The created roadmap
275
+ */
276
+ createRoadmap(input) {
277
+ const now = (/* @__PURE__ */ new Date()).toISOString();
278
+ const id = this.generateRoadmapId();
279
+ const roadmap = {
280
+ id,
281
+ title: input.title,
282
+ description: input.description,
283
+ createdAt: now,
284
+ updatedAt: now
285
+ };
286
+ this.db.prepare(`
287
+ INSERT INTO roadmaps (id, title, description, createdAt, updatedAt)
288
+ VALUES (?, ?, ?, ?, ?)
289
+ `).run(roadmap.id, roadmap.title, roadmap.description ?? null, roadmap.createdAt, roadmap.updatedAt);
290
+ this.db.bumpLastModified();
291
+ this.emit("roadmap:created", roadmap);
292
+ return roadmap;
293
+ }
294
+ /**
295
+ * Get a roadmap by ID.
296
+ *
297
+ * @param id - Roadmap ID
298
+ * @returns The roadmap, or undefined if not found
299
+ */
300
+ getRoadmap(id) {
301
+ const row = this.db.prepare("SELECT * FROM roadmaps WHERE id = ?").get(id);
302
+ if (!row)
303
+ return void 0;
304
+ return this.rowToRoadmap(row);
305
+ }
306
+ /**
307
+ * List all roadmaps, ordered by creation date (newest first).
308
+ *
309
+ * @returns Array of roadmaps
310
+ */
311
+ listRoadmaps() {
312
+ const rows = this.db.prepare("SELECT * FROM roadmaps ORDER BY createdAt DESC").all();
313
+ return rows.map((row) => this.rowToRoadmap(row));
314
+ }
315
+ /**
316
+ * Update a roadmap.
317
+ *
318
+ * @param id - Roadmap ID
319
+ * @param updates - Partial roadmap updates
320
+ * @returns The updated roadmap
321
+ * @throws Error if roadmap not found
322
+ */
323
+ updateRoadmap(id, updates) {
324
+ const roadmap = this.getRoadmap(id);
325
+ if (!roadmap) {
326
+ throw new Error(`Roadmap ${id} not found`);
327
+ }
328
+ const updated = {
329
+ ...roadmap,
330
+ ...updates,
331
+ id,
332
+ // Prevent changing ID
333
+ createdAt: roadmap.createdAt,
334
+ // Prevent changing creation time
335
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
336
+ };
337
+ this.db.prepare(`
338
+ UPDATE roadmaps SET
339
+ title = ?,
340
+ description = ?,
341
+ updatedAt = ?
342
+ WHERE id = ?
343
+ `).run(updated.title, updated.description ?? null, updated.updatedAt, updated.id);
344
+ this.db.bumpLastModified();
345
+ this.emit("roadmap:updated", updated);
346
+ return updated;
347
+ }
348
+ /**
349
+ * Delete a roadmap and all its milestones/features (cascading).
350
+ *
351
+ * @param id - Roadmap ID
352
+ * @throws Error if roadmap not found
353
+ */
354
+ deleteRoadmap(id) {
355
+ const roadmap = this.getRoadmap(id);
356
+ if (!roadmap) {
357
+ throw new Error(`Roadmap ${id} not found`);
358
+ }
359
+ this.db.prepare("DELETE FROM roadmaps WHERE id = ?").run(id);
360
+ this.db.bumpLastModified();
361
+ this.emit("roadmap:deleted", id);
362
+ }
363
+ // ── Milestone CRUD ────────────────────────────────────────────────
364
+ /**
365
+ * Add a milestone to a roadmap.
366
+ * Automatically computes the orderIndex (max + 1).
367
+ *
368
+ * @param roadmapId - Parent roadmap ID
369
+ * @param input - Milestone creation input
370
+ * @returns The created milestone
371
+ * @throws Error if roadmap not found
372
+ */
373
+ createMilestone(roadmapId, input) {
374
+ const roadmap = this.getRoadmap(roadmapId);
375
+ if (!roadmap) {
376
+ throw new Error(`Roadmap ${roadmapId} not found`);
377
+ }
378
+ const now = (/* @__PURE__ */ new Date()).toISOString();
379
+ const id = this.generateMilestoneId();
380
+ const existingMilestones = this.listMilestones(roadmapId);
381
+ const orderIndex = existingMilestones.length > 0 ? Math.max(...existingMilestones.map((m) => m.orderIndex)) + 1 : 0;
382
+ const milestone = {
383
+ id,
384
+ roadmapId,
385
+ title: input.title,
386
+ description: input.description,
387
+ orderIndex,
388
+ createdAt: now,
389
+ updatedAt: now
390
+ };
391
+ this.db.prepare(`
392
+ INSERT INTO roadmap_milestones (id, roadmapId, title, description, orderIndex, createdAt, updatedAt)
393
+ VALUES (?, ?, ?, ?, ?, ?, ?)
394
+ `).run(milestone.id, milestone.roadmapId, milestone.title, milestone.description ?? null, milestone.orderIndex, milestone.createdAt, milestone.updatedAt);
395
+ this.db.bumpLastModified();
396
+ this.emit("milestone:created", milestone);
397
+ return milestone;
398
+ }
399
+ /**
400
+ * Get a milestone by ID.
401
+ *
402
+ * @param id - Milestone ID
403
+ * @returns The milestone, or undefined if not found
404
+ */
405
+ getMilestone(id) {
406
+ const row = this.db.prepare("SELECT * FROM roadmap_milestones WHERE id = ?").get(id);
407
+ if (!row)
408
+ return void 0;
409
+ return this.rowToMilestone(row);
410
+ }
411
+ /**
412
+ * List milestones for a roadmap, ordered deterministically.
413
+ *
414
+ * Uses deterministic ordering: ORDER BY orderIndex ASC, createdAt ASC, id ASC
415
+ * to ensure consistent results when stored order data is incomplete or conflicting.
416
+ *
417
+ * @param roadmapId - Roadmap ID
418
+ * @returns Array of milestones in deterministic order
419
+ */
420
+ listMilestones(roadmapId) {
421
+ const rows = this.db.prepare("SELECT * FROM roadmap_milestones WHERE roadmapId = ? ORDER BY orderIndex ASC, createdAt ASC, id ASC").all(roadmapId);
422
+ return rows.map((row) => this.rowToMilestone(row));
423
+ }
424
+ /**
425
+ * Update a milestone.
426
+ *
427
+ * @param id - Milestone ID
428
+ * @param updates - Partial milestone updates
429
+ * @returns The updated milestone
430
+ * @throws Error if milestone not found
431
+ */
432
+ updateMilestone(id, updates) {
433
+ const milestone = this.getMilestone(id);
434
+ if (!milestone) {
435
+ throw new Error(`Milestone ${id} not found`);
436
+ }
437
+ const updated = {
438
+ ...milestone,
439
+ ...updates,
440
+ id,
441
+ // Prevent changing ID
442
+ roadmapId: milestone.roadmapId,
443
+ // Prevent moving to different roadmap
444
+ createdAt: milestone.createdAt,
445
+ // Prevent changing creation time
446
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
447
+ };
448
+ this.db.prepare(`
449
+ UPDATE roadmap_milestones SET
450
+ title = ?,
451
+ description = ?,
452
+ updatedAt = ?
453
+ WHERE id = ?
454
+ `).run(updated.title, updated.description ?? null, updated.updatedAt, updated.id);
455
+ this.db.bumpLastModified();
456
+ this.emit("milestone:updated", updated);
457
+ return updated;
458
+ }
459
+ /**
460
+ * Delete a milestone and all its features (cascading).
461
+ *
462
+ * @param id - Milestone ID
463
+ * @throws Error if milestone not found
464
+ */
465
+ deleteMilestone(id) {
466
+ const milestone = this.getMilestone(id);
467
+ if (!milestone) {
468
+ throw new Error(`Milestone ${id} not found`);
469
+ }
470
+ this.db.prepare("DELETE FROM roadmap_milestones WHERE id = ?").run(id);
471
+ this.db.bumpLastModified();
472
+ this.emit("milestone:deleted", id);
473
+ }
474
+ // ── Feature CRUD ─────────────────────────────────────────────────
475
+ /**
476
+ * Add a feature to a milestone.
477
+ * Automatically computes the orderIndex (max + 1).
478
+ *
479
+ * @param milestoneId - Parent milestone ID
480
+ * @param input - Feature creation input
481
+ * @returns The created feature
482
+ * @throws Error if milestone not found
483
+ */
484
+ createFeature(milestoneId, input) {
485
+ const milestone = this.getMilestone(milestoneId);
486
+ if (!milestone) {
487
+ throw new Error(`Milestone ${milestoneId} not found`);
488
+ }
489
+ const now = (/* @__PURE__ */ new Date()).toISOString();
490
+ const id = this.generateFeatureId();
491
+ const existingFeatures = this.listFeatures(milestoneId);
492
+ const orderIndex = existingFeatures.length > 0 ? Math.max(...existingFeatures.map((f) => f.orderIndex)) + 1 : 0;
493
+ const feature = {
494
+ id,
495
+ milestoneId,
496
+ title: input.title,
497
+ description: input.description,
498
+ orderIndex,
499
+ createdAt: now,
500
+ updatedAt: now
501
+ };
502
+ this.db.prepare(`
503
+ INSERT INTO roadmap_features (id, milestoneId, title, description, orderIndex, createdAt, updatedAt)
504
+ VALUES (?, ?, ?, ?, ?, ?, ?)
505
+ `).run(feature.id, feature.milestoneId, feature.title, feature.description ?? null, feature.orderIndex, feature.createdAt, feature.updatedAt);
506
+ this.db.bumpLastModified();
507
+ this.emit("feature:created", feature);
508
+ return feature;
509
+ }
510
+ /**
511
+ * Get a feature by ID.
512
+ *
513
+ * @param id - Feature ID
514
+ * @returns The feature, or undefined if not found
515
+ */
516
+ getFeature(id) {
517
+ const row = this.db.prepare("SELECT * FROM roadmap_features WHERE id = ?").get(id);
518
+ if (!row)
519
+ return void 0;
520
+ return this.rowToFeature(row);
521
+ }
522
+ /**
523
+ * List features for a milestone, ordered deterministically.
524
+ *
525
+ * Uses deterministic ordering: ORDER BY orderIndex ASC, createdAt ASC, id ASC
526
+ * to ensure consistent results when stored order data is incomplete or conflicting.
527
+ *
528
+ * @param milestoneId - Milestone ID
529
+ * @returns Array of features in deterministic order
530
+ */
531
+ listFeatures(milestoneId) {
532
+ const rows = this.db.prepare("SELECT * FROM roadmap_features WHERE milestoneId = ? ORDER BY orderIndex ASC, createdAt ASC, id ASC").all(milestoneId);
533
+ return rows.map((row) => this.rowToFeature(row));
534
+ }
535
+ /**
536
+ * Update a feature.
537
+ *
538
+ * @param id - Feature ID
539
+ * @param updates - Partial feature updates
540
+ * @returns The updated feature
541
+ * @throws Error if feature not found
542
+ */
543
+ updateFeature(id, updates) {
544
+ const feature = this.getFeature(id);
545
+ if (!feature) {
546
+ throw new Error(`Feature ${id} not found`);
547
+ }
548
+ const updated = {
549
+ ...feature,
550
+ ...updates,
551
+ id,
552
+ // Prevent changing ID
553
+ milestoneId: feature.milestoneId,
554
+ // Prevent moving via update (use moveFeature instead)
555
+ createdAt: feature.createdAt,
556
+ // Prevent changing creation time
557
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
558
+ };
559
+ this.db.prepare(`
560
+ UPDATE roadmap_features SET
561
+ title = ?,
562
+ description = ?,
563
+ updatedAt = ?
564
+ WHERE id = ?
565
+ `).run(updated.title, updated.description ?? null, updated.updatedAt, updated.id);
566
+ this.db.bumpLastModified();
567
+ this.emit("feature:updated", updated);
568
+ return updated;
569
+ }
570
+ /**
571
+ * Delete a feature.
572
+ *
573
+ * @param id - Feature ID
574
+ * @throws Error if feature not found
575
+ */
576
+ deleteFeature(id) {
577
+ const feature = this.getFeature(id);
578
+ if (!feature) {
579
+ throw new Error(`Feature ${id} not found`);
580
+ }
581
+ this.db.prepare("DELETE FROM roadmap_features WHERE id = ?").run(id);
582
+ this.db.bumpLastModified();
583
+ this.emit("feature:deleted", feature);
584
+ }
585
+ // ── Reorder Operations ────────────────────────────────────────────
586
+ /**
587
+ * Reorder milestones within a roadmap.
588
+ *
589
+ * Applies an explicit reorder input and persists the full normalized order.
590
+ * The input must contain all milestone IDs exactly once.
591
+ *
592
+ * @param input - Reorder input with complete milestone ID list
593
+ * @returns The reordered milestones in their new order
594
+ * @throws Error if milestone set is incomplete, duplicate, or not found
595
+ */
596
+ reorderMilestones(input) {
597
+ const roadmap = this.getRoadmap(input.roadmapId);
598
+ if (!roadmap) {
599
+ throw new Error(`Roadmap ${input.roadmapId} not found`);
600
+ }
601
+ const milestones = this.listMilestones(input.roadmapId);
602
+ const reordered = applyRoadmapMilestoneReorder(milestones, input);
603
+ this.db.transaction(() => {
604
+ for (const milestone of reordered) {
605
+ this.db.prepare(`
606
+ UPDATE roadmap_milestones SET orderIndex = ?, updatedAt = ? WHERE id = ?
607
+ `).run(milestone.orderIndex, (/* @__PURE__ */ new Date()).toISOString(), milestone.id);
608
+ }
609
+ });
610
+ this.db.bumpLastModified();
611
+ this.emit("milestone:reordered", { roadmapId: input.roadmapId, milestones: reordered });
612
+ return reordered;
613
+ }
614
+ /**
615
+ * Reorder features within a milestone.
616
+ *
617
+ * Applies an explicit reorder input and persists the full normalized order.
618
+ * The input must contain all feature IDs for the milestone exactly once.
619
+ *
620
+ * @param input - Reorder input with complete feature ID list
621
+ * @returns The reordered features in their new order
622
+ * @throws Error if feature set is incomplete, duplicate, or not found
623
+ */
624
+ reorderFeatures(input) {
625
+ const milestone = this.getMilestone(input.milestoneId);
626
+ if (!milestone) {
627
+ throw new Error(`Milestone ${input.milestoneId} not found`);
628
+ }
629
+ if (milestone.roadmapId !== input.roadmapId) {
630
+ throw new Error(`Milestone ${input.milestoneId} does not belong to roadmap ${input.roadmapId}`);
631
+ }
632
+ const features = this.listFeatures(input.milestoneId);
633
+ const reordered = applyRoadmapFeatureReorder(features, input);
634
+ this.db.transaction(() => {
635
+ for (const feature of reordered) {
636
+ this.db.prepare(`
637
+ UPDATE roadmap_features SET orderIndex = ?, updatedAt = ? WHERE id = ?
638
+ `).run(feature.orderIndex, (/* @__PURE__ */ new Date()).toISOString(), feature.id);
639
+ }
640
+ });
641
+ this.db.bumpLastModified();
642
+ this.emit("feature:reordered", { milestoneId: input.milestoneId, features: reordered });
643
+ return reordered;
644
+ }
645
+ /**
646
+ * Move a feature, including cross-milestone moves.
647
+ *
648
+ * Atomically renumbers both the source and destination milestone scopes.
649
+ *
650
+ * @param input - Move input with source/destination milestone info
651
+ * @returns The moved feature and both affected milestone feature lists
652
+ * @throws Error if feature or milestone not found, or scope validation fails
653
+ */
654
+ moveFeature(input) {
655
+ const roadmap = this.getRoadmap(input.roadmapId);
656
+ if (!roadmap) {
657
+ throw new Error(`Roadmap ${input.roadmapId} not found`);
658
+ }
659
+ const fromMilestone = this.getMilestone(input.fromMilestoneId);
660
+ const toMilestone = this.getMilestone(input.toMilestoneId);
661
+ if (!fromMilestone) {
662
+ throw new Error(`Source milestone ${input.fromMilestoneId} not found`);
663
+ }
664
+ if (!toMilestone) {
665
+ throw new Error(`Destination milestone ${input.toMilestoneId} not found`);
666
+ }
667
+ if (fromMilestone.roadmapId !== input.roadmapId) {
668
+ throw new Error(`Source milestone ${input.fromMilestoneId} does not belong to roadmap ${input.roadmapId}`);
669
+ }
670
+ if (toMilestone.roadmapId !== input.roadmapId) {
671
+ throw new Error(`Destination milestone ${input.toMilestoneId} does not belong to roadmap ${input.roadmapId}`);
672
+ }
673
+ const sourceFeatures = this.listFeatures(input.fromMilestoneId);
674
+ const targetFeatures = this.listFeatures(input.toMilestoneId);
675
+ const allFeatures = input.fromMilestoneId === input.toMilestoneId ? sourceFeatures : [...sourceFeatures, ...targetFeatures];
676
+ const result = moveRoadmapFeature(allFeatures, input);
677
+ this.db.transaction(() => {
678
+ for (const feature of result.affectedFeatures) {
679
+ this.db.prepare(`
680
+ UPDATE roadmap_features SET milestoneId = ?, orderIndex = ?, updatedAt = ? WHERE id = ?
681
+ `).run(feature.milestoneId, feature.orderIndex, (/* @__PURE__ */ new Date()).toISOString(), feature.id);
682
+ }
683
+ });
684
+ this.db.bumpLastModified();
685
+ this.emit("feature:moved", {
686
+ feature: result.movedFeature,
687
+ fromMilestoneId: input.fromMilestoneId,
688
+ toMilestoneId: input.toMilestoneId
689
+ });
690
+ return {
691
+ movedFeature: result.movedFeature,
692
+ sourceMilestoneFeatures: result.sourceMilestoneFeatures,
693
+ targetMilestoneFeatures: result.targetMilestoneFeatures
694
+ };
695
+ }
696
+ // ── Hierarchy Operations ───────────────────────────────────────────
697
+ /**
698
+ * Get a milestone with all of its features in deterministic order.
699
+ *
700
+ * @param id - Milestone ID
701
+ * @returns The milestone with features, or undefined if not found
702
+ */
703
+ getMilestoneWithFeatures(id) {
704
+ const milestone = this.getMilestone(id);
705
+ if (!milestone)
706
+ return void 0;
707
+ return {
708
+ ...milestone,
709
+ features: this.listFeatures(id)
710
+ };
711
+ }
712
+ /**
713
+ * Get a roadmap with its full hierarchy (milestones → features).
714
+ *
715
+ * @param id - Roadmap ID
716
+ * @returns The roadmap with hierarchy, or undefined if not found
717
+ */
718
+ getRoadmapWithHierarchy(id) {
719
+ const roadmap = this.getRoadmap(id);
720
+ if (!roadmap)
721
+ return void 0;
722
+ return {
723
+ ...roadmap,
724
+ milestones: this.listMilestones(id).map((milestone) => ({
725
+ ...milestone,
726
+ features: this.listFeatures(milestone.id)
727
+ }))
728
+ };
729
+ }
730
+ // ── Export / Handoff Operations ────────────────────────────────────
731
+ /**
732
+ * Get a flat export bundle for a roadmap.
733
+ *
734
+ * Returns all roadmap data in a flat structure suitable for persistence,
735
+ * APIs, import/export, and sync jobs. Entities are separated so downstream
736
+ * persistence layers can upsert by table/collection.
737
+ *
738
+ * @param roadmapId - Roadmap ID
739
+ * @returns The export bundle with ordered entities
740
+ * @throws Error if roadmap not found
741
+ */
742
+ getRoadmapExport(roadmapId) {
743
+ const roadmap = this.getRoadmap(roadmapId);
744
+ if (!roadmap) {
745
+ throw new Error(`Roadmap ${roadmapId} not found`);
746
+ }
747
+ const milestones = this.listMilestones(roadmapId);
748
+ const allFeatures = [];
749
+ for (const milestone of milestones) {
750
+ const features = this.listFeatures(milestone.id);
751
+ allFeatures.push(...features);
752
+ }
753
+ return {
754
+ roadmap,
755
+ milestones,
756
+ features: allFeatures
757
+ };
758
+ }
759
+ /**
760
+ * Get a mission planning handoff payload for a roadmap.
761
+ *
762
+ * Converts the roadmap into a mission planning structure while preserving
763
+ * source IDs and deterministic order. Does not couple to MissionStore internals.
764
+ *
765
+ * @param roadmapId - Roadmap ID
766
+ * @returns The mission planning handoff payload
767
+ * @throws Error if roadmap not found
768
+ */
769
+ getRoadmapMissionHandoff(roadmapId) {
770
+ const roadmap = this.getRoadmap(roadmapId);
771
+ if (!roadmap) {
772
+ throw new Error(`Roadmap ${roadmapId} not found`);
773
+ }
774
+ const milestones = this.listMilestones(roadmapId);
775
+ return {
776
+ sourceRoadmapId: roadmap.id,
777
+ title: roadmap.title,
778
+ description: roadmap.description,
779
+ milestones: milestones.map((milestone) => {
780
+ const features = this.listFeatures(milestone.id);
781
+ return {
782
+ sourceMilestoneId: milestone.id,
783
+ title: milestone.title,
784
+ description: milestone.description,
785
+ orderIndex: milestone.orderIndex,
786
+ features: features.map((feature) => ({
787
+ sourceFeatureId: feature.id,
788
+ title: feature.title,
789
+ description: feature.description,
790
+ orderIndex: feature.orderIndex
791
+ }))
792
+ };
793
+ })
794
+ };
795
+ }
796
+ /**
797
+ * Get a task planning handoff payload for a single roadmap feature.
798
+ *
799
+ * Returns a self-contained handoff payload for converting a roadmap feature
800
+ * into task planning flows without coupling to MissionStore internals.
801
+ *
802
+ * @param roadmapId - Parent roadmap ID (for validation)
803
+ * @param milestoneId - Parent milestone ID (for validation)
804
+ * @param featureId - Feature ID to generate handoff for
805
+ * @returns The task planning handoff payload
806
+ * @throws Error if any entity is not found or if ownership validation fails
807
+ */
808
+ getRoadmapFeatureHandoff(roadmapId, milestoneId, featureId) {
809
+ const roadmap = this.getRoadmap(roadmapId);
810
+ if (!roadmap) {
811
+ throw new Error(`Roadmap ${roadmapId} not found`);
812
+ }
813
+ const milestone = this.getMilestone(milestoneId);
814
+ if (!milestone) {
815
+ throw new Error(`Milestone ${milestoneId} not found`);
816
+ }
817
+ if (milestone.roadmapId !== roadmapId) {
818
+ throw new Error(`Milestone ${milestoneId} does not belong to roadmap ${roadmapId}`);
819
+ }
820
+ const feature = this.getFeature(featureId);
821
+ if (!feature) {
822
+ throw new Error(`Feature ${featureId} not found`);
823
+ }
824
+ if (feature.milestoneId !== milestoneId) {
825
+ throw new Error(`Feature ${featureId} does not belong to milestone ${milestoneId}`);
826
+ }
827
+ const source = {
828
+ roadmapId: roadmap.id,
829
+ milestoneId: milestone.id,
830
+ featureId: feature.id,
831
+ roadmapTitle: roadmap.title,
832
+ milestoneTitle: milestone.title,
833
+ milestoneOrderIndex: milestone.orderIndex,
834
+ featureOrderIndex: feature.orderIndex
835
+ };
836
+ return {
837
+ source,
838
+ title: feature.title,
839
+ description: feature.description
840
+ };
841
+ }
842
+ /**
843
+ * Get a mission planning handoff payload for a roadmap.
844
+ *
845
+ * Alias for getRoadmapMissionHandoff() for API consistency.
846
+ * Converts the roadmap into a mission planning structure while preserving
847
+ * source IDs and deterministic order.
848
+ *
849
+ * @param roadmapId - Roadmap ID
850
+ * @returns The mission planning handoff payload
851
+ * @throws Error if roadmap not found
852
+ */
853
+ getMissionPlanningHandoff(roadmapId) {
854
+ return this.getRoadmapMissionHandoff(roadmapId);
855
+ }
856
+ /**
857
+ * List all task planning handoff payloads for a roadmap.
858
+ *
859
+ * Returns a flat list of all feature handoffs in deterministic order
860
+ * (milestone order index, then feature order index).
861
+ *
862
+ * @param roadmapId - Roadmap ID
863
+ * @returns Array of task planning handoff payloads for all features
864
+ * @throws Error if roadmap not found
865
+ */
866
+ listFeatureTaskPlanningHandoffs(roadmapId) {
867
+ const roadmap = this.getRoadmap(roadmapId);
868
+ if (!roadmap) {
869
+ throw new Error(`Roadmap ${roadmapId} not found`);
870
+ }
871
+ const milestones = this.listMilestones(roadmapId);
872
+ const handoffs = [];
873
+ for (const milestone of milestones) {
874
+ const features = this.listFeatures(milestone.id);
875
+ for (const feature of features) {
876
+ const source = {
877
+ roadmapId: roadmap.id,
878
+ milestoneId: milestone.id,
879
+ featureId: feature.id,
880
+ roadmapTitle: roadmap.title,
881
+ milestoneTitle: milestone.title,
882
+ milestoneOrderIndex: milestone.orderIndex,
883
+ featureOrderIndex: feature.orderIndex
884
+ };
885
+ handoffs.push({
886
+ source,
887
+ title: feature.title,
888
+ description: feature.description
889
+ });
890
+ }
891
+ }
892
+ return handoffs;
893
+ }
894
+ };
895
+
896
+ // ../../plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js
897
+ var injectedCreateAiSession;
898
+ var MILESTONE_SUGGESTION_SYSTEM_PROMPT = `You are a milestone planning assistant for a product roadmap system.
899
+
900
+ Your job is to suggest logical milestones that would help achieve a user's roadmap goal.
901
+
902
+ ## Guidelines
903
+
904
+ 1. **Think about phases**: Break the goal into logical phases
905
+ 2. **Use clear titles**: Milestone titles should be concise and descriptive
906
+ 3. **Add context**: Include a brief description explaining what this milestone encompasses
907
+ 4. **Order matters**: List milestones in the order they should be completed
908
+ 5. **Realistic scope**: Each milestone should be achievable in 2-4 weeks
909
+
910
+ ## Output Format
911
+
912
+ Respond with ONLY a valid JSON array of milestone suggestions.`;
913
+ var MAX_GOAL_PROMPT_LENGTH = 4e3;
914
+ var SUGGESTION_TIMEOUT_MS = 12e4;
915
+ var DEFAULT_SUGGESTION_COUNT = 5;
916
+ var MAX_SUGGESTION_COUNT = 10;
917
+ var MIN_SUGGESTION_COUNT = 1;
918
+ var MAX_PARSE_RETRIES = 1;
919
+ function validateSuggestionInput(input) {
920
+ if (!input || typeof input !== "object") {
921
+ throw new ValidationError("Request body must be an object");
922
+ }
923
+ const { goalPrompt, count } = input;
924
+ if (typeof goalPrompt !== "string" || !goalPrompt.trim()) {
925
+ throw new ValidationError("goalPrompt is required and must be a non-empty string");
926
+ }
927
+ if (goalPrompt.length > MAX_GOAL_PROMPT_LENGTH) {
928
+ throw new ValidationError(`goalPrompt exceeds maximum length of ${MAX_GOAL_PROMPT_LENGTH} characters`);
929
+ }
930
+ if (count !== void 0) {
931
+ if (typeof count !== "number" || !Number.isInteger(count))
932
+ throw new ValidationError("count must be an integer");
933
+ if (count < MIN_SUGGESTION_COUNT || count > MAX_SUGGESTION_COUNT) {
934
+ throw new ValidationError(`count must be between ${MIN_SUGGESTION_COUNT} and ${MAX_SUGGESTION_COUNT}`);
935
+ }
936
+ }
937
+ }
938
+ function extractJsonCandidate(text) {
939
+ if (!text || !text.trim())
940
+ return null;
941
+ const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
942
+ const source = codeBlockMatch?.[1]?.trim() || text.trim();
943
+ const startIndex = source.indexOf("[");
944
+ if (startIndex < 0)
945
+ return null;
946
+ let depth = 0;
947
+ let inString = false;
948
+ let escaped = false;
949
+ for (let index = startIndex; index < source.length; index++) {
950
+ const char = source[index];
951
+ if (inString) {
952
+ if (escaped) {
953
+ escaped = false;
954
+ } else if (char === "\\") {
955
+ escaped = true;
956
+ } else if (char === '"') {
957
+ inString = false;
958
+ }
959
+ continue;
960
+ }
961
+ if (char === '"') {
962
+ inString = true;
963
+ continue;
964
+ }
965
+ if (char === "[")
966
+ depth++;
967
+ if (char === "]") {
968
+ depth--;
969
+ if (depth === 0) {
970
+ return source.slice(startIndex, index + 1).trim();
971
+ }
972
+ }
973
+ }
974
+ return source.slice(startIndex).trim();
975
+ }
976
+ function repairJson(text) {
977
+ let repaired = text.replace(/,\s*([}\]])/g, "$1");
978
+ let depthBraces = 0;
979
+ let depthBrackets = 0;
980
+ for (const ch of repaired) {
981
+ if (ch === "{")
982
+ depthBraces++;
983
+ if (ch === "}")
984
+ depthBraces--;
985
+ if (ch === "[")
986
+ depthBrackets++;
987
+ if (ch === "]")
988
+ depthBrackets--;
989
+ }
990
+ repaired += "]".repeat(Math.max(0, depthBrackets));
991
+ repaired += "}".repeat(Math.max(0, depthBraces));
992
+ return repaired;
993
+ }
994
+ function parseMilestoneSuggestions(text) {
995
+ const candidate = extractJsonCandidate(text);
996
+ if (!candidate)
997
+ throw new ParseError("AI returned no valid JSON. Please try again.");
998
+ let parsed;
999
+ try {
1000
+ parsed = JSON.parse(candidate);
1001
+ } catch {
1002
+ parsed = JSON.parse(repairJson(candidate));
1003
+ }
1004
+ if (!Array.isArray(parsed)) {
1005
+ throw new ParseError("AI response must be a JSON array of milestone suggestions");
1006
+ }
1007
+ const suggestions = [];
1008
+ for (const item of parsed) {
1009
+ if (!item || typeof item !== "object")
1010
+ continue;
1011
+ const row = item;
1012
+ if (typeof row.title !== "string" || !row.title.trim())
1013
+ continue;
1014
+ suggestions.push({
1015
+ title: row.title.trim(),
1016
+ description: typeof row.description === "string" && row.description.trim() ? row.description.trim() : void 0
1017
+ });
1018
+ }
1019
+ if (suggestions.length === 0)
1020
+ throw new ParseError("AI returned no valid milestone suggestions");
1021
+ return suggestions;
1022
+ }
1023
+ function pickFactory(explicit) {
1024
+ return explicit ?? injectedCreateAiSession;
1025
+ }
1026
+ async function runPrompt(createAiSession, options, prompt) {
1027
+ const agent = await createAiSession(options);
1028
+ await agent.session.prompt(prompt);
1029
+ const lastMessage = agent.session.state.messages.filter((m) => m.role === "assistant").pop();
1030
+ let text = "";
1031
+ if (lastMessage?.content) {
1032
+ if (typeof lastMessage.content === "string")
1033
+ text = lastMessage.content;
1034
+ else {
1035
+ text = lastMessage.content.filter((c) => c.type === "text").map((c) => c.text).join("");
1036
+ }
1037
+ }
1038
+ return { text, dispose: agent.session.dispose };
1039
+ }
1040
+ async function generateMilestoneSuggestions(goalPrompt, count = DEFAULT_SUGGESTION_COUNT, rootDir, modelProvider, modelId, createAiSession) {
1041
+ const factory = pickFactory(createAiSession);
1042
+ if (!factory)
1043
+ throw new ServiceUnavailableError("AI service is not available");
1044
+ if (!rootDir)
1045
+ throw new Error("rootDir is required for AI-powered suggestion generation");
1046
+ const result = await Promise.race([
1047
+ (async () => {
1048
+ let dispose;
1049
+ try {
1050
+ let response = await runPrompt(factory, {
1051
+ cwd: rootDir,
1052
+ systemPrompt: MILESTONE_SUGGESTION_SYSTEM_PROMPT,
1053
+ tools: "readonly",
1054
+ ...modelProvider && modelId ? { defaultProvider: modelProvider, defaultModelId: modelId } : {}
1055
+ }, `Please suggest ${count} milestones for the following roadmap goal:
1056
+
1057
+ ${goalPrompt.trim()}`);
1058
+ dispose = response.dispose;
1059
+ let suggestions;
1060
+ let lastError;
1061
+ for (let attempt = 0; attempt <= MAX_PARSE_RETRIES; attempt++) {
1062
+ try {
1063
+ suggestions = parseMilestoneSuggestions(response.text);
1064
+ break;
1065
+ } catch (error) {
1066
+ lastError = error instanceof Error ? error : new Error(String(error));
1067
+ if (attempt === MAX_PARSE_RETRIES)
1068
+ break;
1069
+ response = await runPrompt(factory, {
1070
+ cwd: rootDir,
1071
+ systemPrompt: MILESTONE_SUGGESTION_SYSTEM_PROMPT,
1072
+ tools: "readonly",
1073
+ ...modelProvider && modelId ? { defaultProvider: modelProvider, defaultModelId: modelId } : {}
1074
+ }, "Your previous response could not be parsed as JSON. Respond with only a JSON array.");
1075
+ dispose = response.dispose;
1076
+ }
1077
+ }
1078
+ if (!suggestions) {
1079
+ throw new ParseError(`Failed to parse AI response after ${MAX_PARSE_RETRIES + 1} attempts: ${lastError?.message ?? "Unknown error"}`);
1080
+ }
1081
+ return suggestions.slice(0, count);
1082
+ } finally {
1083
+ dispose?.();
1084
+ }
1085
+ })(),
1086
+ new Promise((_, reject) => globalThis.setTimeout(() => reject(new ServiceUnavailableError("AI suggestion generation timed out. Please try again.")), SUGGESTION_TIMEOUT_MS))
1087
+ ]);
1088
+ return result;
1089
+ }
1090
+ var FEATURE_SUGGESTION_SYSTEM_PROMPT = `You are a feature planning assistant for a product roadmap system.`;
1091
+ var MAX_FEATURE_PROMPT_LENGTH = 2e3;
1092
+ function validateFeatureSuggestionInput(input) {
1093
+ if (!input || typeof input !== "object" || Array.isArray(input))
1094
+ throw new ValidationError("Request body must be an object");
1095
+ const { prompt, count } = input;
1096
+ if (prompt !== void 0) {
1097
+ if (typeof prompt !== "string")
1098
+ throw new ValidationError("prompt must be a string");
1099
+ if (prompt.length > MAX_FEATURE_PROMPT_LENGTH)
1100
+ throw new ValidationError(`prompt exceeds maximum length of ${MAX_FEATURE_PROMPT_LENGTH} characters`);
1101
+ }
1102
+ if (count !== void 0) {
1103
+ if (typeof count !== "number" || !Number.isInteger(count))
1104
+ throw new ValidationError("count must be an integer");
1105
+ if (count < MIN_SUGGESTION_COUNT || count > MAX_SUGGESTION_COUNT) {
1106
+ throw new ValidationError(`count must be between ${MIN_SUGGESTION_COUNT} and ${MAX_SUGGESTION_COUNT}`);
1107
+ }
1108
+ }
1109
+ }
1110
+ function buildMilestoneContextString(context) {
1111
+ const lines = [];
1112
+ lines.push(`Roadmap: ${context.roadmapTitle}`);
1113
+ if (context.roadmapDescription)
1114
+ lines.push(`Description: ${context.roadmapDescription}`);
1115
+ lines.push("", `Milestone: ${context.milestoneTitle}`);
1116
+ if (context.milestoneDescription)
1117
+ lines.push(`Description: ${context.milestoneDescription}`);
1118
+ if (context.existingFeatureTitles.length > 0) {
1119
+ lines.push("", "Existing features in this milestone:");
1120
+ for (const title of context.existingFeatureTitles)
1121
+ lines.push(` - ${title}`);
1122
+ }
1123
+ return lines.join("\n");
1124
+ }
1125
+ function parseFeatureSuggestions(text) {
1126
+ return parseMilestoneSuggestions(text);
1127
+ }
1128
+ async function generateFeatureSuggestions(context, count = DEFAULT_SUGGESTION_COUNT, prompt, rootDir, modelProvider, modelId, createAiSession) {
1129
+ const factory = pickFactory(createAiSession);
1130
+ if (!factory)
1131
+ throw new ServiceUnavailableError("AI service is not available");
1132
+ if (!rootDir)
1133
+ throw new Error("rootDir is required for AI-powered suggestion generation");
1134
+ const systemPrompt = `${FEATURE_SUGGESTION_SYSTEM_PROMPT}
1135
+
1136
+ ${buildMilestoneContextString(context)}`;
1137
+ const userMessage = prompt?.trim() ? `Please suggest ${count} features for the milestone described above.
1138
+
1139
+ Additional guidance:
1140
+ ${prompt.trim()}` : `Please suggest ${count} features for the milestone described above.`;
1141
+ const result = await Promise.race([
1142
+ (async () => {
1143
+ const { text, dispose } = await runPrompt(factory, {
1144
+ cwd: rootDir,
1145
+ systemPrompt,
1146
+ tools: "readonly",
1147
+ ...modelProvider && modelId ? { defaultProvider: modelProvider, defaultModelId: modelId } : {}
1148
+ }, userMessage);
1149
+ try {
1150
+ return parseFeatureSuggestions(text).slice(0, count);
1151
+ } finally {
1152
+ dispose?.();
1153
+ }
1154
+ })(),
1155
+ new Promise((_, reject) => globalThis.setTimeout(() => reject(new ServiceUnavailableError("AI suggestion generation timed out. Please try again.")), SUGGESTION_TIMEOUT_MS))
1156
+ ]);
1157
+ return result;
1158
+ }
1159
+ var ValidationError = class extends Error {
1160
+ constructor(message) {
1161
+ super(message);
1162
+ this.name = "ValidationError";
1163
+ }
1164
+ };
1165
+ var ParseError = class extends Error {
1166
+ constructor(message) {
1167
+ super(message);
1168
+ this.name = "ParseError";
1169
+ }
1170
+ };
1171
+ var ServiceUnavailableError = class extends Error {
1172
+ constructor(message) {
1173
+ super(message);
1174
+ this.name = "ServiceUnavailableError";
1175
+ }
1176
+ };
1177
+
1178
+ // ../../plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js
1179
+ var roadmapStoreCache = /* @__PURE__ */ new WeakMap();
1180
+ function getRoadmapStore(ctx) {
1181
+ const taskStoreWithRoadmaps = ctx.taskStore;
1182
+ if (typeof taskStoreWithRoadmaps.getRoadmapStore === "function") {
1183
+ return taskStoreWithRoadmaps.getRoadmapStore();
1184
+ }
1185
+ const key = ctx.taskStore;
1186
+ const cached = roadmapStoreCache.get(key);
1187
+ if (cached)
1188
+ return cached;
1189
+ const store = new RoadmapStore(ctx.taskStore.getDatabase());
1190
+ roadmapStoreCache.set(key, store);
1191
+ return store;
1192
+ }
1193
+ function asRequest(req) {
1194
+ return req;
1195
+ }
1196
+ function badRequest(message) {
1197
+ return { status: 400, body: { error: message } };
1198
+ }
1199
+ function notFound(message) {
1200
+ return { status: 404, body: { error: message } };
1201
+ }
1202
+ function serverError(message) {
1203
+ return { status: 500, body: { error: message } };
1204
+ }
1205
+ function noContent() {
1206
+ return { status: 204 };
1207
+ }
1208
+ function routeHandler(handler) {
1209
+ return async (req, ctx) => {
1210
+ const roadmapStore = getRoadmapStore(ctx);
1211
+ try {
1212
+ return await handler(asRequest(req), ctx, roadmapStore);
1213
+ } catch (error) {
1214
+ if (error instanceof Error && error.message.toLowerCase().includes("not found")) {
1215
+ return notFound(error.message);
1216
+ }
1217
+ return serverError(error instanceof Error ? error.message : "Internal server error");
1218
+ }
1219
+ };
1220
+ }
1221
+ function validateTitle(title) {
1222
+ if (!title || typeof title !== "string" || !title.trim()) {
1223
+ throw new Error("title is required");
1224
+ }
1225
+ if (title.length > 200) {
1226
+ throw new Error("title must not exceed 200 characters");
1227
+ }
1228
+ return title.trim();
1229
+ }
1230
+ function validateDescription(desc) {
1231
+ if (desc === void 0 || desc === null)
1232
+ return void 0;
1233
+ if (typeof desc !== "string") {
1234
+ throw new Error("description must be a string");
1235
+ }
1236
+ if (desc.length > 5e3) {
1237
+ throw new Error("description must not exceed 5000 characters");
1238
+ }
1239
+ return desc.trim() || void 0;
1240
+ }
1241
+ function validateStringArray(arr, fieldName) {
1242
+ if (!Array.isArray(arr)) {
1243
+ throw new Error(`${fieldName} must be an array`);
1244
+ }
1245
+ if (!arr.every((item) => typeof item === "string")) {
1246
+ throw new Error(`${fieldName} must be an array of strings`);
1247
+ }
1248
+ return arr;
1249
+ }
1250
+ function createRoadmapPluginRoutes() {
1251
+ return [
1252
+ {
1253
+ method: "GET",
1254
+ path: "/roadmaps",
1255
+ handler: routeHandler((_req, _ctx, roadmapStore) => roadmapStore.listRoadmaps())
1256
+ },
1257
+ {
1258
+ method: "POST",
1259
+ path: "/roadmaps",
1260
+ handler: routeHandler((req, _ctx, roadmapStore) => {
1261
+ const body = req.body;
1262
+ try {
1263
+ return {
1264
+ status: 201,
1265
+ body: roadmapStore.createRoadmap({
1266
+ title: validateTitle(body?.title),
1267
+ description: validateDescription(body?.description)
1268
+ })
1269
+ };
1270
+ } catch (error) {
1271
+ return badRequest(error instanceof Error ? error.message : "Invalid input");
1272
+ }
1273
+ })
1274
+ },
1275
+ {
1276
+ method: "GET",
1277
+ path: "/roadmaps/:roadmapId",
1278
+ handler: routeHandler((req, _ctx, roadmapStore) => {
1279
+ const roadmap = roadmapStore.getRoadmapWithHierarchy(req.params.roadmapId);
1280
+ return roadmap ? roadmap : notFound(`Roadmap ${req.params.roadmapId} not found`);
1281
+ })
1282
+ },
1283
+ {
1284
+ method: "PATCH",
1285
+ path: "/roadmaps/:roadmapId",
1286
+ handler: routeHandler((req, _ctx, roadmapStore) => {
1287
+ const body = req.body;
1288
+ try {
1289
+ return roadmapStore.updateRoadmap(req.params.roadmapId, {
1290
+ title: body.title !== void 0 ? validateTitle(body.title) : void 0,
1291
+ description: body.description !== void 0 ? validateDescription(body.description) : void 0
1292
+ });
1293
+ } catch (error) {
1294
+ return badRequest(error instanceof Error ? error.message : "Invalid input");
1295
+ }
1296
+ })
1297
+ },
1298
+ { method: "DELETE", path: "/roadmaps/:roadmapId", handler: routeHandler((req, _ctx, roadmapStore) => {
1299
+ roadmapStore.deleteRoadmap(req.params.roadmapId);
1300
+ return noContent();
1301
+ }) },
1302
+ {
1303
+ method: "POST",
1304
+ path: "/roadmaps/:roadmapId/milestones",
1305
+ handler: routeHandler((req, _ctx, roadmapStore) => {
1306
+ const body = req.body;
1307
+ try {
1308
+ return {
1309
+ status: 201,
1310
+ body: roadmapStore.createMilestone(req.params.roadmapId, {
1311
+ title: validateTitle(body?.title),
1312
+ description: validateDescription(body?.description)
1313
+ })
1314
+ };
1315
+ } catch (error) {
1316
+ return badRequest(error instanceof Error ? error.message : "Invalid input");
1317
+ }
1318
+ })
1319
+ },
1320
+ {
1321
+ method: "POST",
1322
+ path: "/roadmaps/:roadmapId/milestones/reorder",
1323
+ handler: routeHandler((req, _ctx, roadmapStore) => {
1324
+ try {
1325
+ const body = req.body;
1326
+ roadmapStore.reorderMilestones({ roadmapId: req.params.roadmapId, orderedMilestoneIds: validateStringArray(body?.orderedMilestoneIds, "orderedMilestoneIds") });
1327
+ return noContent();
1328
+ } catch (error) {
1329
+ return badRequest(error instanceof Error ? error.message : "Invalid input");
1330
+ }
1331
+ })
1332
+ },
1333
+ {
1334
+ method: "PATCH",
1335
+ path: "/roadmaps/milestones/:milestoneId",
1336
+ handler: routeHandler((req, _ctx, roadmapStore) => {
1337
+ const body = req.body;
1338
+ try {
1339
+ return roadmapStore.updateMilestone(req.params.milestoneId, {
1340
+ title: body.title !== void 0 ? validateTitle(body.title) : void 0,
1341
+ description: body.description !== void 0 ? validateDescription(body.description) : void 0
1342
+ });
1343
+ } catch (error) {
1344
+ return badRequest(error instanceof Error ? error.message : "Invalid input");
1345
+ }
1346
+ })
1347
+ },
1348
+ { method: "DELETE", path: "/roadmaps/milestones/:milestoneId", handler: routeHandler((req, _ctx, roadmapStore) => {
1349
+ roadmapStore.deleteMilestone(req.params.milestoneId);
1350
+ return noContent();
1351
+ }) },
1352
+ {
1353
+ method: "POST",
1354
+ path: "/roadmaps/milestones/:milestoneId/features",
1355
+ handler: routeHandler((req, _ctx, roadmapStore) => {
1356
+ const body = req.body;
1357
+ try {
1358
+ return {
1359
+ status: 201,
1360
+ body: roadmapStore.createFeature(req.params.milestoneId, {
1361
+ title: validateTitle(body?.title),
1362
+ description: validateDescription(body?.description)
1363
+ })
1364
+ };
1365
+ } catch (error) {
1366
+ return badRequest(error instanceof Error ? error.message : "Invalid input");
1367
+ }
1368
+ })
1369
+ },
1370
+ {
1371
+ method: "POST",
1372
+ path: "/roadmaps/milestones/:milestoneId/features/reorder",
1373
+ handler: routeHandler((req, _ctx, roadmapStore) => {
1374
+ try {
1375
+ const body = req.body;
1376
+ const milestone = roadmapStore.getMilestone(req.params.milestoneId);
1377
+ if (!milestone)
1378
+ return notFound(`Milestone ${req.params.milestoneId} not found`);
1379
+ roadmapStore.reorderFeatures({ roadmapId: milestone.roadmapId, milestoneId: req.params.milestoneId, orderedFeatureIds: validateStringArray(body?.orderedFeatureIds, "orderedFeatureIds") });
1380
+ return noContent();
1381
+ } catch (error) {
1382
+ return badRequest(error instanceof Error ? error.message : "Invalid input");
1383
+ }
1384
+ })
1385
+ },
1386
+ {
1387
+ method: "PATCH",
1388
+ path: "/roadmaps/features/:featureId",
1389
+ handler: routeHandler((req, _ctx, roadmapStore) => {
1390
+ const body = req.body;
1391
+ try {
1392
+ return roadmapStore.updateFeature(req.params.featureId, {
1393
+ title: body.title !== void 0 ? validateTitle(body.title) : void 0,
1394
+ description: body.description !== void 0 ? validateDescription(body.description) : void 0
1395
+ });
1396
+ } catch (error) {
1397
+ return badRequest(error instanceof Error ? error.message : "Invalid input");
1398
+ }
1399
+ })
1400
+ },
1401
+ { method: "DELETE", path: "/roadmaps/features/:featureId", handler: routeHandler((req, _ctx, roadmapStore) => {
1402
+ roadmapStore.deleteFeature(req.params.featureId);
1403
+ return noContent();
1404
+ }) },
1405
+ {
1406
+ method: "POST",
1407
+ path: "/roadmaps/features/:featureId/move",
1408
+ handler: routeHandler((req, _ctx, roadmapStore) => {
1409
+ const body = req.body;
1410
+ if (!body?.targetMilestoneId)
1411
+ return badRequest("targetMilestoneId is required");
1412
+ if (typeof body.targetIndex !== "number")
1413
+ return badRequest("targetIndex must be a number");
1414
+ const feature = roadmapStore.getFeature(req.params.featureId);
1415
+ if (!feature)
1416
+ return notFound(`Feature ${req.params.featureId} not found`);
1417
+ const fromMilestone = roadmapStore.getMilestone(feature.milestoneId);
1418
+ if (!fromMilestone)
1419
+ return notFound(`Source milestone ${feature.milestoneId} not found`);
1420
+ const toMilestone = roadmapStore.getMilestone(body.targetMilestoneId);
1421
+ if (!toMilestone)
1422
+ return notFound(`Target milestone ${body.targetMilestoneId} not found`);
1423
+ roadmapStore.moveFeature({
1424
+ roadmapId: fromMilestone.roadmapId,
1425
+ featureId: req.params.featureId,
1426
+ fromMilestoneId: feature.milestoneId,
1427
+ toMilestoneId: body.targetMilestoneId,
1428
+ targetOrderIndex: body.targetIndex
1429
+ });
1430
+ return noContent();
1431
+ })
1432
+ },
1433
+ {
1434
+ method: "POST",
1435
+ path: "/roadmaps/:roadmapId/suggestions/milestones",
1436
+ handler: routeHandler(async (req, ctx, roadmapStore) => {
1437
+ const roadmap = roadmapStore.getRoadmap(req.params.roadmapId);
1438
+ if (!roadmap)
1439
+ return notFound(`Roadmap ${req.params.roadmapId} not found`);
1440
+ try {
1441
+ validateSuggestionInput(req.body);
1442
+ } catch (error) {
1443
+ if (error instanceof ValidationError)
1444
+ return badRequest(error.message);
1445
+ throw error;
1446
+ }
1447
+ try {
1448
+ const body = req.body;
1449
+ const suggestions = await generateMilestoneSuggestions(body.goalPrompt, body.count, ctx.taskStore.getRootDir(), void 0, void 0, ctx.createAiSession);
1450
+ return { suggestions };
1451
+ } catch (error) {
1452
+ if (error instanceof ParseError)
1453
+ return serverError(error.message);
1454
+ if (error instanceof ServiceUnavailableError) {
1455
+ return { status: 503, body: { error: error.message } };
1456
+ }
1457
+ throw error;
1458
+ }
1459
+ })
1460
+ },
1461
+ {
1462
+ method: "POST",
1463
+ path: "/roadmaps/milestones/:milestoneId/suggestions/features",
1464
+ handler: routeHandler(async (req, ctx, roadmapStore) => {
1465
+ const milestone = roadmapStore.getMilestone(req.params.milestoneId);
1466
+ if (!milestone)
1467
+ return notFound(`Milestone ${req.params.milestoneId} not found`);
1468
+ const roadmap = roadmapStore.getRoadmap(milestone.roadmapId);
1469
+ if (!roadmap)
1470
+ return notFound(`Roadmap ${milestone.roadmapId} not found`);
1471
+ try {
1472
+ validateFeatureSuggestionInput(req.body);
1473
+ } catch (error) {
1474
+ if (error instanceof ValidationError)
1475
+ return badRequest(error.message);
1476
+ throw error;
1477
+ }
1478
+ try {
1479
+ const body = req.body;
1480
+ const suggestions = await generateFeatureSuggestions({
1481
+ roadmapTitle: roadmap.title,
1482
+ roadmapDescription: roadmap.description,
1483
+ milestoneTitle: milestone.title,
1484
+ milestoneDescription: milestone.description,
1485
+ existingFeatureTitles: roadmapStore.listFeatures(milestone.id).map((feature) => feature.title)
1486
+ }, body.count, body.prompt, ctx.taskStore.getRootDir(), void 0, void 0, ctx.createAiSession);
1487
+ return { suggestions };
1488
+ } catch (error) {
1489
+ if (error instanceof ParseError)
1490
+ return serverError(error.message);
1491
+ if (error instanceof ServiceUnavailableError) {
1492
+ return { status: 503, body: { error: error.message } };
1493
+ }
1494
+ throw error;
1495
+ }
1496
+ })
1497
+ },
1498
+ {
1499
+ method: "GET",
1500
+ path: "/roadmaps/:roadmapId/export",
1501
+ handler: routeHandler((req, _ctx, roadmapStore) => roadmapStore.getRoadmapExport(req.params.roadmapId))
1502
+ },
1503
+ {
1504
+ method: "GET",
1505
+ path: "/roadmaps/:roadmapId/handoff",
1506
+ handler: routeHandler((req, _ctx, roadmapStore) => ({
1507
+ mission: roadmapStore.getMissionPlanningHandoff(req.params.roadmapId),
1508
+ features: roadmapStore.listFeatureTaskPlanningHandoffs(req.params.roadmapId)
1509
+ }))
1510
+ },
1511
+ {
1512
+ method: "GET",
1513
+ path: "/roadmaps/:roadmapId/handoff/mission",
1514
+ handler: routeHandler((req, _ctx, roadmapStore) => roadmapStore.getMissionPlanningHandoff(req.params.roadmapId))
1515
+ },
1516
+ {
1517
+ method: "GET",
1518
+ path: "/roadmaps/:roadmapId/milestones/:milestoneId/features/:featureId/handoff/task",
1519
+ handler: routeHandler((req, _ctx, roadmapStore) => roadmapStore.getRoadmapFeatureHandoff(req.params.roadmapId, req.params.milestoneId, req.params.featureId))
1520
+ }
1521
+ ];
1522
+ }
1523
+
1524
+ // ../../plugins/fusion-plugin-roadmap/src/roadmap-schema.ts
1525
+ function ensureRoadmapSchema(db) {
1526
+ db.exec(`
1527
+ CREATE TABLE IF NOT EXISTS roadmaps (
1528
+ id TEXT PRIMARY KEY,
1529
+ title TEXT NOT NULL,
1530
+ description TEXT,
1531
+ createdAt TEXT NOT NULL,
1532
+ updatedAt TEXT NOT NULL
1533
+ );
1534
+
1535
+ CREATE TABLE IF NOT EXISTS roadmap_milestones (
1536
+ id TEXT PRIMARY KEY,
1537
+ roadmapId TEXT NOT NULL,
1538
+ title TEXT NOT NULL,
1539
+ description TEXT,
1540
+ orderIndex INTEGER NOT NULL,
1541
+ createdAt TEXT NOT NULL,
1542
+ updatedAt TEXT NOT NULL,
1543
+ FOREIGN KEY (roadmapId) REFERENCES roadmaps(id) ON DELETE CASCADE
1544
+ );
1545
+
1546
+ CREATE TABLE IF NOT EXISTS roadmap_features (
1547
+ id TEXT PRIMARY KEY,
1548
+ milestoneId TEXT NOT NULL,
1549
+ title TEXT NOT NULL,
1550
+ description TEXT,
1551
+ orderIndex INTEGER NOT NULL,
1552
+ createdAt TEXT NOT NULL,
1553
+ updatedAt TEXT NOT NULL,
1554
+ FOREIGN KEY (milestoneId) REFERENCES roadmap_milestones(id) ON DELETE CASCADE
1555
+ );
1556
+
1557
+ CREATE INDEX IF NOT EXISTS idxRoadmapMilestonesRoadmapOrder
1558
+ ON roadmap_milestones(roadmapId, orderIndex, createdAt, id);
1559
+
1560
+ CREATE INDEX IF NOT EXISTS idxRoadmapFeaturesMilestoneOrder
1561
+ ON roadmap_features(milestoneId, orderIndex, createdAt, id);
1562
+ `);
1563
+ }
1564
+
1565
+ // ../../plugins/fusion-plugin-roadmap/src/store/roadmap-handoff.ts
1566
+ function buildFeatureSourceRef(roadmap, milestone, feature) {
1567
+ return {
1568
+ roadmapId: roadmap.id,
1569
+ milestoneId: milestone.id,
1570
+ featureId: feature.id,
1571
+ roadmapTitle: roadmap.title,
1572
+ milestoneTitle: milestone.title,
1573
+ milestoneOrderIndex: milestone.orderIndex,
1574
+ featureOrderIndex: feature.orderIndex
1575
+ };
1576
+ }
1577
+ function mapFeatureToTaskHandoff(roadmap, milestone, feature) {
1578
+ return {
1579
+ source: buildFeatureSourceRef(roadmap, milestone, feature),
1580
+ title: feature.title,
1581
+ description: feature.description
1582
+ };
1583
+ }
1584
+ function mapRoadmapToMissionHandoff(roadmap, milestones, featuresByMilestoneId) {
1585
+ const normalizedMilestones = normalizeRoadmapMilestoneOrder(milestones);
1586
+ const milestoneHandoffs = normalizedMilestones.map((milestone) => {
1587
+ const rawFeatures = featuresByMilestoneId.get(milestone.id) ?? [];
1588
+ const normalizedFeatures = normalizeRoadmapFeatureOrder(rawFeatures);
1589
+ return {
1590
+ sourceMilestoneId: milestone.id,
1591
+ title: milestone.title,
1592
+ description: milestone.description,
1593
+ orderIndex: milestone.orderIndex,
1594
+ features: normalizedFeatures.map((feature) => ({
1595
+ sourceFeatureId: feature.id,
1596
+ title: feature.title,
1597
+ description: feature.description,
1598
+ orderIndex: feature.orderIndex
1599
+ }))
1600
+ };
1601
+ });
1602
+ return {
1603
+ sourceRoadmapId: roadmap.id,
1604
+ title: roadmap.title,
1605
+ description: roadmap.description,
1606
+ milestones: milestoneHandoffs
1607
+ };
1608
+ }
1609
+ function mapRoadmapWithHierarchyToMissionHandoff(roadmapWithHierarchy) {
1610
+ const featuresByMilestoneId = /* @__PURE__ */ new Map();
1611
+ for (const milestone of roadmapWithHierarchy.milestones) {
1612
+ featuresByMilestoneId.set(milestone.id, milestone.features);
1613
+ }
1614
+ return mapRoadmapToMissionHandoff(
1615
+ roadmapWithHierarchy,
1616
+ roadmapWithHierarchy.milestones,
1617
+ featuresByMilestoneId
1618
+ );
1619
+ }
1620
+ function mapAllFeaturesToTaskHandoffs(roadmap, milestones, featuresByMilestoneId) {
1621
+ const normalizedMilestones = normalizeRoadmapMilestoneOrder(milestones);
1622
+ const handoffs = [];
1623
+ for (const milestone of normalizedMilestones) {
1624
+ const rawFeatures = featuresByMilestoneId.get(milestone.id) ?? [];
1625
+ const normalizedFeatures = normalizeRoadmapFeatureOrder(rawFeatures);
1626
+ for (const feature of normalizedFeatures) {
1627
+ handoffs.push(mapFeatureToTaskHandoff(roadmap, milestone, feature));
1628
+ }
1629
+ }
1630
+ return handoffs;
1631
+ }
1632
+
1633
+ // ../../plugins/fusion-plugin-roadmap/src/index.ts
1634
+ var plugin = definePlugin({
1635
+ manifest: {
1636
+ id: "fusion-plugin-roadmap",
1637
+ name: "Roadmaps",
1638
+ version: "0.1.0",
1639
+ description: "Standalone roadmap planning plugin"
1640
+ },
1641
+ state: "installed",
1642
+ hooks: {
1643
+ onSchemaInit: ensureRoadmapSchema
1644
+ },
1645
+ routes: createRoadmapPluginRoutes(),
1646
+ dashboardViews: [
1647
+ {
1648
+ viewId: "roadmaps",
1649
+ label: "Roadmaps",
1650
+ componentPath: "./dashboard-view",
1651
+ icon: "Map",
1652
+ placement: "primary",
1653
+ order: 30
1654
+ }
1655
+ ]
1656
+ });
1657
+ var index_default = plugin;
1658
+ export {
1659
+ RoadmapStore,
1660
+ applyRoadmapFeatureReorder,
1661
+ applyRoadmapMilestoneReorder,
1662
+ createRoadmapPluginRoutes,
1663
+ index_default as default,
1664
+ ensureRoadmapSchema,
1665
+ mapAllFeaturesToTaskHandoffs,
1666
+ mapFeatureToTaskHandoff,
1667
+ mapRoadmapToMissionHandoff,
1668
+ mapRoadmapWithHierarchyToMissionHandoff,
1669
+ moveRoadmapFeature,
1670
+ normalizeRoadmapFeatureOrder,
1671
+ normalizeRoadmapMilestoneOrder
1672
+ };