@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.
- package/README.md +6 -0
- package/dist/bin.js +28004 -16888
- package/dist/client/assets/AgentDetailView-B7QRcHJH.css +1 -0
- package/dist/client/assets/AgentDetailView-DwLmRXTY.js +18 -0
- package/dist/client/assets/{AgentsView-B3jYk8Kt.js → AgentsView-D-N6aA0P.js} +12 -7
- package/dist/client/assets/ChatView-DnCdKu8Z.js +1 -0
- package/dist/client/assets/{DevServerView-DyGDEiBP.js → DevServerView-BiA1nYtt.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-D5UIeIl6.js → DirectoryPicker-DvBviDG6.js} +1 -1
- package/dist/client/assets/{DocumentsView-DNHu1T8K.js → DocumentsView-BWXOxpuq.js} +1 -1
- package/dist/client/assets/{EvalsView-CpRobtDi.js → EvalsView-CJFbtL7i.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-DOY_oZi7.js → ExperimentalAgentOnboardingModal-DuGIPd0B.js} +1 -1
- package/dist/client/assets/InsightsView-BBpRiolN.js +11 -0
- package/dist/client/assets/{MemoryView-PSc5lGJt.js → MemoryView-48LuNkKk.js} +2 -2
- package/dist/client/assets/NodesView-CGQWSNZM.js +14 -0
- package/dist/client/assets/{PiExtensionsManager-DL_QcN56.js → PiExtensionsManager-i-7UL2oh.js} +2 -2
- package/dist/client/assets/PluginManager-DoSAykD6.js +1 -0
- package/dist/client/assets/{ResearchView-BzCcDAS4.css → ResearchView-BEI4ZSGs.css} +1 -1
- package/dist/client/assets/ResearchView-XZuRtOxE.js +1 -0
- package/dist/client/assets/SettingsModal-Ci0_sqbU.css +1 -0
- package/dist/client/assets/{SettingsModal-CUCyaAyE.js → SettingsModal-CmeF8CN4.js} +1 -1
- package/dist/client/assets/SettingsModal-DBcjf9Bu.js +31 -0
- package/dist/client/assets/SettingsModal-DWKgRxBA.css +1 -0
- package/dist/client/assets/{SetupWizardModal-BKscasuh.js → SetupWizardModal-CgtvpMX9.js} +1 -1
- package/dist/client/assets/{SkillsView-BdELqTy7.js → SkillsView-DErYRumF.js} +1 -1
- package/dist/client/assets/StashRecoveryView-B_8WIQEo.css +1 -0
- package/dist/client/assets/StashRecoveryView-QJrNS4Vg.js +1 -0
- package/dist/client/assets/{TodoView-DFNGBDNV.js → TodoView-BD9NRwq0.js} +2 -2
- package/dist/client/assets/createLucideIcon-BazL2hk5.js +21 -0
- package/dist/client/assets/dashboard-view-BWGH_fAq.js +63 -0
- package/dist/client/assets/dashboard-view-BoTzlP8b.css +1 -0
- package/dist/client/assets/dashboard-view-DdGlfuu-.css +1 -0
- package/dist/client/assets/dashboard-view-Ws9_ZnKu.js +21 -0
- package/dist/client/assets/{folder-open-k1xmUMyr.js → folder-open-CHSlllzf.js} +1 -1
- package/dist/client/assets/index-DCovGm5b.css +1 -0
- package/dist/client/assets/index-bEwSVl7B.js +692 -0
- package/dist/client/assets/{star-ne32r3Y4.js → star-BgVwWAPz.js} +1 -1
- package/dist/client/assets/{upload-MS-2Gx53.js → upload-CAzycxr9.js} +1 -1
- package/dist/client/assets/{users-C519GSjH.js → users-CZnxCCCJ.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/package.json +1 -1
- package/dist/droid-cli/src/__tests__/index.test.ts +228 -0
- package/dist/extension.js +15810 -10205
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/pi-claude-cli/src/__tests__/provider.test.ts +36 -22
- package/dist/pi-claude-cli/src/provider.ts +7 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/manifest.json +24 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +44 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/TestRunnerPanel.test.tsx +99 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/config-flow.test.ts +91 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/dashboard-view.test.tsx +40 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/dashboard-views.test.ts +46 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/draft-store.test.ts +50 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/exec-mock.ts +80 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/fixtures.test.ts +40 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/registry.ts +82 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/generator.test.ts +54 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manage-view.test.tsx +98 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manifest.test.ts +36 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/registration.test.ts +29 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/run-routes.test.ts +98 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/runner.test.ts +55 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/runtime-availability.test.ts +61 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/validation.test.ts +30 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/wizard-routes.test.ts +61 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/workflow-integration.test.ts +19 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/dashboard-view.css +43 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/dashboard-view.tsx +49 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/generator.ts +95 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/redact.ts +9 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/runner.ts +79 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/types.ts +31 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/index.ts +58 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/manage/EditDraftModal.tsx +75 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/manage/useDrafts.ts +73 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/manage-view.css +79 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/manage-view.tsx +122 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/routes/wizard-routes.ts +272 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/run/TestRunnerPanel.css +70 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/run/TestRunnerPanel.tsx +98 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/run/useRunGeneratedCli.ts +37 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/runtime/__tests__/executor-runtime-env.test.ts +191 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/runtime/executor-runtime-env.ts +75 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/storage/draft-store.ts +85 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/__tests__/cli-press-store.test.ts +128 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/__tests__/credentials.test.ts +62 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/cli-press-store.ts +427 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/cli-press-types.ts +110 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/credentials.ts +95 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/steps.tsx +55 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/types.ts +33 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/validation.ts +63 -0
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/manifest.json +10 -0
- package/dist/plugins/fusion-plugin-reports/package.json +18 -2
- package/dist/plugins/fusion-plugin-reports/src/__tests__/approval.test.ts +164 -0
- package/dist/plugins/fusion-plugin-reports/src/__tests__/manifest.test.ts +14 -0
- package/dist/plugins/fusion-plugin-reports/src/__tests__/routes-approval.test.ts +109 -0
- package/dist/plugins/fusion-plugin-reports/src/__tests__/scaffold.test.ts +60 -0
- package/dist/plugins/fusion-plugin-reports/src/__tests__/share-blocks.test.ts +83 -0
- package/dist/plugins/fusion-plugin-reports/src/aggregation.ts +23 -0
- package/dist/plugins/fusion-plugin-reports/src/approval.ts +97 -0
- package/dist/plugins/fusion-plugin-reports/src/cadence.ts +23 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/ReportsView.css +82 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/ReportsView.tsx +24 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportComparisonDrawer.test.tsx +12 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportDetailPanel.test.tsx +12 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportFiltersBar.test.tsx +14 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportsView.test.tsx +27 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/api.test.ts +19 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/useReportSectionDiff.test.ts +11 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/useReports.test.ts +13 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/api.ts +85 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportApprovalPanel.css +59 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportApprovalPanel.tsx +58 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportComparisonDrawer.tsx +21 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportDetailPanel.tsx +29 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportEmptyState.tsx +3 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportFiltersBar.tsx +19 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportListItem.tsx +8 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ShareBlocksPanel.css +29 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ShareBlocksPanel.tsx +43 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/__tests__/ReportApprovalPanel.test.tsx +38 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/__tests__/ShareBlocksPanel.test.tsx +24 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/test-setup.ts +18 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/types.ts +22 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/useReportPreview.ts +44 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/useReportSectionDiff.ts +59 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/useReports.ts +71 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/useViewportMode.ts +13 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard-view.tsx +6 -0
- package/dist/plugins/fusion-plugin-reports/src/index.ts +48 -2
- package/dist/plugins/fusion-plugin-reports/src/pipeline.ts +58 -0
- package/dist/plugins/fusion-plugin-reports/src/render/__tests__/escape.test.ts +20 -0
- package/dist/plugins/fusion-plugin-reports/src/render/__tests__/html-template.test.ts +110 -0
- package/dist/plugins/fusion-plugin-reports/src/render/__tests__/standalone-html.test.ts +66 -0
- package/dist/plugins/fusion-plugin-reports/src/render/escape.ts +12 -0
- package/dist/plugins/fusion-plugin-reports/src/render/html-styles.ts +40 -0
- package/dist/plugins/fusion-plugin-reports/src/render/html-template.ts +137 -0
- package/dist/plugins/fusion-plugin-reports/src/render/index.ts +4 -0
- package/dist/plugins/fusion-plugin-reports/src/render/standalone-html.ts +75 -0
- package/dist/plugins/fusion-plugin-reports/src/report-schema.ts +31 -0
- package/dist/plugins/fusion-plugin-reports/src/routes/__tests__/report-export-routes.test.ts +104 -0
- package/dist/plugins/fusion-plugin-reports/src/routes/report-approval-routes.ts +98 -0
- package/dist/plugins/fusion-plugin-reports/src/routes/report-export-routes.ts +77 -0
- package/dist/plugins/fusion-plugin-reports/src/routes/report-list-routes.ts +72 -0
- package/dist/plugins/fusion-plugin-reports/src/runs-store.ts +69 -0
- package/dist/plugins/fusion-plugin-reports/src/share-blocks.ts +82 -0
- package/dist/plugins/fusion-plugin-reports/src/store/report-store.ts +51 -2
- package/dist/plugins/fusion-plugin-reports/src/store/report-types.ts +6 -1
- package/dist/plugins/fusion-plugin-roadmap/bundled.js +1672 -0
- package/dist/plugins/fusion-plugin-roadmap/manifest.json +1 -1
- package/dist/plugins/fusion-plugin-roadmap/package.json +4 -41
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +2 -3
- package/skill/fusion/references/engine-tools.md +1 -1
- package/skill/fusion/references/extension-tools.md +3 -3
- package/skill/fusion/references/fusion-capabilities.md +1 -1
- package/dist/client/assets/AgentDetailView-BwJaLqZh.css +0 -1
- package/dist/client/assets/AgentDetailView-ZbHEbYRT.js +0 -18
- package/dist/client/assets/ChatView-DhPkiEGs.js +0 -1
- package/dist/client/assets/InsightsView-vp0RE8Mg.js +0 -11
- package/dist/client/assets/NodesView-DMj6HGeC.js +0 -14
- package/dist/client/assets/PluginManager-BtYKm8IT.js +0 -1
- package/dist/client/assets/ResearchView-BhWqfdV0.js +0 -1
- package/dist/client/assets/SettingsModal-BAgB4_AR.js +0 -31
- package/dist/client/assets/SettingsModal-BNSrO1M9.css +0 -1
- package/dist/client/assets/SettingsModal-DzsLquBu.css +0 -1
- package/dist/client/assets/index-Qq2JOOWx.css +0 -1
- package/dist/client/assets/index-TFYXEVpn.js +0 -692
- package/dist/plugins/fusion-plugin-roadmap/src/__tests__/api-client.test.ts +0 -101
- package/dist/plugins/fusion-plugin-roadmap/src/__tests__/index.test.ts +0 -92
- package/dist/plugins/fusion-plugin-roadmap/src/__tests__/roadmap-routes.test.ts +0 -48
- package/dist/plugins/fusion-plugin-roadmap/src/__tests__/roadmap-suggestions.test.ts +0 -31
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.css +0 -1299
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.tsx +0 -2559
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/__tests__/RoadmapsView.test.tsx +0 -1144
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/__tests__/useRoadmaps.test.ts +0 -1756
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/api.ts +0 -70
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/test-setup.ts +0 -7
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/types.ts +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useConfirm.ts +0 -8
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useRoadmaps.ts +0 -1188
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard/useViewportMode.ts +0 -20
- package/dist/plugins/fusion-plugin-roadmap/src/dashboard-view.tsx +0 -6
- package/dist/plugins/fusion-plugin-roadmap/src/index.ts +0 -74
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-routes.ts +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-schema.ts +0 -41
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-suggestions.d.ts +0 -15
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-suggestions.ts +0 -15
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.d.ts +0 -283
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.js +0 -21
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/roadmap-types.ts +0 -310
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.d.ts +0 -5
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js +0 -361
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-routes.ts +0 -408
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.d.ts +0 -68
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js +0 -300
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/routes/roadmap-suggestions.ts +0 -381
- package/dist/plugins/fusion-plugin-roadmap/src/server/index.d.ts +0 -3
- package/dist/plugins/fusion-plugin-roadmap/src/server/index.ts +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-handoff.test.ts +0 -445
- package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-ordering.test.ts +0 -334
- package/dist/plugins/fusion-plugin-roadmap/src/store/__tests__/roadmap-store.test.ts +0 -1318
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-handoff.ts +0 -163
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.d.ts +0 -37
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js +0 -188
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.ts +0 -311
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.d.ts +0 -299
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.d.ts.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.js +0 -765
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.js.map +0 -1
- package/dist/plugins/fusion-plugin-roadmap/src/store/roadmap-store.ts +0 -1001
|
@@ -1,765 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RoadmapStore - Data layer for standalone roadmap persistence.
|
|
3
|
-
*
|
|
4
|
-
* Manages CRUD operations for roadmaps, milestones, and features.
|
|
5
|
-
* Provides deterministic ordering via covering indexes and atomic reorder/move operations.
|
|
6
|
-
*
|
|
7
|
-
* Ordering invariants:
|
|
8
|
-
* - milestone ordering is scoped to a single roadmap and must be contiguous + 0-based
|
|
9
|
-
* - feature ordering is scoped to a single milestone and must be contiguous + 0-based
|
|
10
|
-
* - all list/read queries use deterministic ordering: ORDER BY orderIndex ASC, createdAt ASC, id ASC
|
|
11
|
-
* - cross-milestone feature moves atomically renumber both affected milestone scopes
|
|
12
|
-
*/
|
|
13
|
-
import { EventEmitter } from "node:events";
|
|
14
|
-
import { applyRoadmapMilestoneReorder, applyRoadmapFeatureReorder, moveRoadmapFeature, } from "./roadmap-ordering.js";
|
|
15
|
-
// ── RoadmapStore Class ──────────────────────────────────────────────
|
|
16
|
-
export class RoadmapStore extends EventEmitter {
|
|
17
|
-
db;
|
|
18
|
-
/**
|
|
19
|
-
* Creates a new RoadmapStore instance.
|
|
20
|
-
*
|
|
21
|
-
* @param db - Shared Database instance (same instance used by TaskStore)
|
|
22
|
-
*/
|
|
23
|
-
constructor(db) {
|
|
24
|
-
super();
|
|
25
|
-
this.db = db;
|
|
26
|
-
this.setMaxListeners(50);
|
|
27
|
-
this.ensureSchema();
|
|
28
|
-
}
|
|
29
|
-
ensureSchema() {
|
|
30
|
-
this.db.exec(`
|
|
31
|
-
CREATE TABLE IF NOT EXISTS roadmaps (
|
|
32
|
-
id TEXT PRIMARY KEY,
|
|
33
|
-
title TEXT NOT NULL,
|
|
34
|
-
description TEXT,
|
|
35
|
-
createdAt TEXT NOT NULL,
|
|
36
|
-
updatedAt TEXT NOT NULL
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
CREATE TABLE IF NOT EXISTS roadmap_milestones (
|
|
40
|
-
id TEXT PRIMARY KEY,
|
|
41
|
-
roadmapId TEXT NOT NULL,
|
|
42
|
-
title TEXT NOT NULL,
|
|
43
|
-
description TEXT,
|
|
44
|
-
orderIndex INTEGER NOT NULL,
|
|
45
|
-
createdAt TEXT NOT NULL,
|
|
46
|
-
updatedAt TEXT NOT NULL,
|
|
47
|
-
FOREIGN KEY (roadmapId) REFERENCES roadmaps(id) ON DELETE CASCADE
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
CREATE TABLE IF NOT EXISTS roadmap_features (
|
|
51
|
-
id TEXT PRIMARY KEY,
|
|
52
|
-
milestoneId TEXT NOT NULL,
|
|
53
|
-
title TEXT NOT NULL,
|
|
54
|
-
description TEXT,
|
|
55
|
-
orderIndex INTEGER NOT NULL,
|
|
56
|
-
createdAt TEXT NOT NULL,
|
|
57
|
-
updatedAt TEXT NOT NULL,
|
|
58
|
-
FOREIGN KEY (milestoneId) REFERENCES roadmap_milestones(id) ON DELETE CASCADE
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
CREATE INDEX IF NOT EXISTS idxRoadmapMilestonesRoadmapOrder
|
|
62
|
-
ON roadmap_milestones(roadmapId, orderIndex, createdAt, id);
|
|
63
|
-
|
|
64
|
-
CREATE INDEX IF NOT EXISTS idxRoadmapFeaturesMilestoneOrder
|
|
65
|
-
ON roadmap_features(milestoneId, orderIndex, createdAt, id);
|
|
66
|
-
`);
|
|
67
|
-
}
|
|
68
|
-
// ── ID Generators ───────────────────────────────────────────────────
|
|
69
|
-
generateRoadmapId() {
|
|
70
|
-
const timestamp = Date.now();
|
|
71
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
72
|
-
return `RM-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
73
|
-
}
|
|
74
|
-
generateMilestoneId() {
|
|
75
|
-
const timestamp = Date.now();
|
|
76
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
77
|
-
return `RMS-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
78
|
-
}
|
|
79
|
-
generateFeatureId() {
|
|
80
|
-
const timestamp = Date.now();
|
|
81
|
-
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
82
|
-
return `RF-${timestamp.toString(36).toUpperCase()}-${random}`;
|
|
83
|
-
}
|
|
84
|
-
// ── Row-to-Object Converters ───────────────────────────────────────
|
|
85
|
-
rowToRoadmap(row) {
|
|
86
|
-
return {
|
|
87
|
-
id: row.id,
|
|
88
|
-
title: row.title,
|
|
89
|
-
description: row.description || undefined,
|
|
90
|
-
createdAt: row.createdAt,
|
|
91
|
-
updatedAt: row.updatedAt,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
rowToMilestone(row) {
|
|
95
|
-
return {
|
|
96
|
-
id: row.id,
|
|
97
|
-
roadmapId: row.roadmapId,
|
|
98
|
-
title: row.title,
|
|
99
|
-
description: row.description || undefined,
|
|
100
|
-
orderIndex: row.orderIndex,
|
|
101
|
-
createdAt: row.createdAt,
|
|
102
|
-
updatedAt: row.updatedAt,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
rowToFeature(row) {
|
|
106
|
-
return {
|
|
107
|
-
id: row.id,
|
|
108
|
-
milestoneId: row.milestoneId,
|
|
109
|
-
title: row.title,
|
|
110
|
-
description: row.description || undefined,
|
|
111
|
-
orderIndex: row.orderIndex,
|
|
112
|
-
createdAt: row.createdAt,
|
|
113
|
-
updatedAt: row.updatedAt,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
// ── Roadmap CRUD ─────────────────────────────────────────────────
|
|
117
|
-
/**
|
|
118
|
-
* Create a new roadmap.
|
|
119
|
-
*
|
|
120
|
-
* @param input - Roadmap creation input
|
|
121
|
-
* @returns The created roadmap
|
|
122
|
-
*/
|
|
123
|
-
createRoadmap(input) {
|
|
124
|
-
const now = new Date().toISOString();
|
|
125
|
-
const id = this.generateRoadmapId();
|
|
126
|
-
const roadmap = {
|
|
127
|
-
id,
|
|
128
|
-
title: input.title,
|
|
129
|
-
description: input.description,
|
|
130
|
-
createdAt: now,
|
|
131
|
-
updatedAt: now,
|
|
132
|
-
};
|
|
133
|
-
this.db.prepare(`
|
|
134
|
-
INSERT INTO roadmaps (id, title, description, createdAt, updatedAt)
|
|
135
|
-
VALUES (?, ?, ?, ?, ?)
|
|
136
|
-
`).run(roadmap.id, roadmap.title, roadmap.description ?? null, roadmap.createdAt, roadmap.updatedAt);
|
|
137
|
-
this.db.bumpLastModified();
|
|
138
|
-
this.emit("roadmap:created", roadmap);
|
|
139
|
-
return roadmap;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Get a roadmap by ID.
|
|
143
|
-
*
|
|
144
|
-
* @param id - Roadmap ID
|
|
145
|
-
* @returns The roadmap, or undefined if not found
|
|
146
|
-
*/
|
|
147
|
-
getRoadmap(id) {
|
|
148
|
-
const row = this.db.prepare("SELECT * FROM roadmaps WHERE id = ?").get(id);
|
|
149
|
-
if (!row)
|
|
150
|
-
return undefined;
|
|
151
|
-
return this.rowToRoadmap(row);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* List all roadmaps, ordered by creation date (newest first).
|
|
155
|
-
*
|
|
156
|
-
* @returns Array of roadmaps
|
|
157
|
-
*/
|
|
158
|
-
listRoadmaps() {
|
|
159
|
-
const rows = this.db.prepare("SELECT * FROM roadmaps ORDER BY createdAt DESC").all();
|
|
160
|
-
return rows.map((row) => this.rowToRoadmap(row));
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* Update a roadmap.
|
|
164
|
-
*
|
|
165
|
-
* @param id - Roadmap ID
|
|
166
|
-
* @param updates - Partial roadmap updates
|
|
167
|
-
* @returns The updated roadmap
|
|
168
|
-
* @throws Error if roadmap not found
|
|
169
|
-
*/
|
|
170
|
-
updateRoadmap(id, updates) {
|
|
171
|
-
const roadmap = this.getRoadmap(id);
|
|
172
|
-
if (!roadmap) {
|
|
173
|
-
throw new Error(`Roadmap ${id} not found`);
|
|
174
|
-
}
|
|
175
|
-
const updated = {
|
|
176
|
-
...roadmap,
|
|
177
|
-
...updates,
|
|
178
|
-
id, // Prevent changing ID
|
|
179
|
-
createdAt: roadmap.createdAt, // Prevent changing creation time
|
|
180
|
-
updatedAt: new Date().toISOString(),
|
|
181
|
-
};
|
|
182
|
-
this.db.prepare(`
|
|
183
|
-
UPDATE roadmaps SET
|
|
184
|
-
title = ?,
|
|
185
|
-
description = ?,
|
|
186
|
-
updatedAt = ?
|
|
187
|
-
WHERE id = ?
|
|
188
|
-
`).run(updated.title, updated.description ?? null, updated.updatedAt, updated.id);
|
|
189
|
-
this.db.bumpLastModified();
|
|
190
|
-
this.emit("roadmap:updated", updated);
|
|
191
|
-
return updated;
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Delete a roadmap and all its milestones/features (cascading).
|
|
195
|
-
*
|
|
196
|
-
* @param id - Roadmap ID
|
|
197
|
-
* @throws Error if roadmap not found
|
|
198
|
-
*/
|
|
199
|
-
deleteRoadmap(id) {
|
|
200
|
-
const roadmap = this.getRoadmap(id);
|
|
201
|
-
if (!roadmap) {
|
|
202
|
-
throw new Error(`Roadmap ${id} not found`);
|
|
203
|
-
}
|
|
204
|
-
// SQLite FK cascade will handle milestones and features
|
|
205
|
-
this.db.prepare("DELETE FROM roadmaps WHERE id = ?").run(id);
|
|
206
|
-
this.db.bumpLastModified();
|
|
207
|
-
this.emit("roadmap:deleted", id);
|
|
208
|
-
}
|
|
209
|
-
// ── Milestone CRUD ────────────────────────────────────────────────
|
|
210
|
-
/**
|
|
211
|
-
* Add a milestone to a roadmap.
|
|
212
|
-
* Automatically computes the orderIndex (max + 1).
|
|
213
|
-
*
|
|
214
|
-
* @param roadmapId - Parent roadmap ID
|
|
215
|
-
* @param input - Milestone creation input
|
|
216
|
-
* @returns The created milestone
|
|
217
|
-
* @throws Error if roadmap not found
|
|
218
|
-
*/
|
|
219
|
-
createMilestone(roadmapId, input) {
|
|
220
|
-
const roadmap = this.getRoadmap(roadmapId);
|
|
221
|
-
if (!roadmap) {
|
|
222
|
-
throw new Error(`Roadmap ${roadmapId} not found`);
|
|
223
|
-
}
|
|
224
|
-
const now = new Date().toISOString();
|
|
225
|
-
const id = this.generateMilestoneId();
|
|
226
|
-
// Compute next orderIndex
|
|
227
|
-
const existingMilestones = this.listMilestones(roadmapId);
|
|
228
|
-
const orderIndex = existingMilestones.length > 0
|
|
229
|
-
? Math.max(...existingMilestones.map((m) => m.orderIndex)) + 1
|
|
230
|
-
: 0;
|
|
231
|
-
const milestone = {
|
|
232
|
-
id,
|
|
233
|
-
roadmapId,
|
|
234
|
-
title: input.title,
|
|
235
|
-
description: input.description,
|
|
236
|
-
orderIndex,
|
|
237
|
-
createdAt: now,
|
|
238
|
-
updatedAt: now,
|
|
239
|
-
};
|
|
240
|
-
this.db.prepare(`
|
|
241
|
-
INSERT INTO roadmap_milestones (id, roadmapId, title, description, orderIndex, createdAt, updatedAt)
|
|
242
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
243
|
-
`).run(milestone.id, milestone.roadmapId, milestone.title, milestone.description ?? null, milestone.orderIndex, milestone.createdAt, milestone.updatedAt);
|
|
244
|
-
this.db.bumpLastModified();
|
|
245
|
-
this.emit("milestone:created", milestone);
|
|
246
|
-
return milestone;
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Get a milestone by ID.
|
|
250
|
-
*
|
|
251
|
-
* @param id - Milestone ID
|
|
252
|
-
* @returns The milestone, or undefined if not found
|
|
253
|
-
*/
|
|
254
|
-
getMilestone(id) {
|
|
255
|
-
const row = this.db.prepare("SELECT * FROM roadmap_milestones WHERE id = ?").get(id);
|
|
256
|
-
if (!row)
|
|
257
|
-
return undefined;
|
|
258
|
-
return this.rowToMilestone(row);
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* List milestones for a roadmap, ordered deterministically.
|
|
262
|
-
*
|
|
263
|
-
* Uses deterministic ordering: ORDER BY orderIndex ASC, createdAt ASC, id ASC
|
|
264
|
-
* to ensure consistent results when stored order data is incomplete or conflicting.
|
|
265
|
-
*
|
|
266
|
-
* @param roadmapId - Roadmap ID
|
|
267
|
-
* @returns Array of milestones in deterministic order
|
|
268
|
-
*/
|
|
269
|
-
listMilestones(roadmapId) {
|
|
270
|
-
const rows = this.db.prepare("SELECT * FROM roadmap_milestones WHERE roadmapId = ? ORDER BY orderIndex ASC, createdAt ASC, id ASC").all(roadmapId);
|
|
271
|
-
return rows.map((row) => this.rowToMilestone(row));
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Update a milestone.
|
|
275
|
-
*
|
|
276
|
-
* @param id - Milestone ID
|
|
277
|
-
* @param updates - Partial milestone updates
|
|
278
|
-
* @returns The updated milestone
|
|
279
|
-
* @throws Error if milestone not found
|
|
280
|
-
*/
|
|
281
|
-
updateMilestone(id, updates) {
|
|
282
|
-
const milestone = this.getMilestone(id);
|
|
283
|
-
if (!milestone) {
|
|
284
|
-
throw new Error(`Milestone ${id} not found`);
|
|
285
|
-
}
|
|
286
|
-
const updated = {
|
|
287
|
-
...milestone,
|
|
288
|
-
...updates,
|
|
289
|
-
id, // Prevent changing ID
|
|
290
|
-
roadmapId: milestone.roadmapId, // Prevent moving to different roadmap
|
|
291
|
-
createdAt: milestone.createdAt, // Prevent changing creation time
|
|
292
|
-
updatedAt: new Date().toISOString(),
|
|
293
|
-
};
|
|
294
|
-
this.db.prepare(`
|
|
295
|
-
UPDATE roadmap_milestones SET
|
|
296
|
-
title = ?,
|
|
297
|
-
description = ?,
|
|
298
|
-
updatedAt = ?
|
|
299
|
-
WHERE id = ?
|
|
300
|
-
`).run(updated.title, updated.description ?? null, updated.updatedAt, updated.id);
|
|
301
|
-
this.db.bumpLastModified();
|
|
302
|
-
this.emit("milestone:updated", updated);
|
|
303
|
-
return updated;
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Delete a milestone and all its features (cascading).
|
|
307
|
-
*
|
|
308
|
-
* @param id - Milestone ID
|
|
309
|
-
* @throws Error if milestone not found
|
|
310
|
-
*/
|
|
311
|
-
deleteMilestone(id) {
|
|
312
|
-
const milestone = this.getMilestone(id);
|
|
313
|
-
if (!milestone) {
|
|
314
|
-
throw new Error(`Milestone ${id} not found`);
|
|
315
|
-
}
|
|
316
|
-
// SQLite FK cascade will handle features
|
|
317
|
-
this.db.prepare("DELETE FROM roadmap_milestones WHERE id = ?").run(id);
|
|
318
|
-
this.db.bumpLastModified();
|
|
319
|
-
this.emit("milestone:deleted", id);
|
|
320
|
-
}
|
|
321
|
-
// ── Feature CRUD ─────────────────────────────────────────────────
|
|
322
|
-
/**
|
|
323
|
-
* Add a feature to a milestone.
|
|
324
|
-
* Automatically computes the orderIndex (max + 1).
|
|
325
|
-
*
|
|
326
|
-
* @param milestoneId - Parent milestone ID
|
|
327
|
-
* @param input - Feature creation input
|
|
328
|
-
* @returns The created feature
|
|
329
|
-
* @throws Error if milestone not found
|
|
330
|
-
*/
|
|
331
|
-
createFeature(milestoneId, input) {
|
|
332
|
-
const milestone = this.getMilestone(milestoneId);
|
|
333
|
-
if (!milestone) {
|
|
334
|
-
throw new Error(`Milestone ${milestoneId} not found`);
|
|
335
|
-
}
|
|
336
|
-
const now = new Date().toISOString();
|
|
337
|
-
const id = this.generateFeatureId();
|
|
338
|
-
// Compute next orderIndex
|
|
339
|
-
const existingFeatures = this.listFeatures(milestoneId);
|
|
340
|
-
const orderIndex = existingFeatures.length > 0
|
|
341
|
-
? Math.max(...existingFeatures.map((f) => f.orderIndex)) + 1
|
|
342
|
-
: 0;
|
|
343
|
-
const feature = {
|
|
344
|
-
id,
|
|
345
|
-
milestoneId,
|
|
346
|
-
title: input.title,
|
|
347
|
-
description: input.description,
|
|
348
|
-
orderIndex,
|
|
349
|
-
createdAt: now,
|
|
350
|
-
updatedAt: now,
|
|
351
|
-
};
|
|
352
|
-
this.db.prepare(`
|
|
353
|
-
INSERT INTO roadmap_features (id, milestoneId, title, description, orderIndex, createdAt, updatedAt)
|
|
354
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
355
|
-
`).run(feature.id, feature.milestoneId, feature.title, feature.description ?? null, feature.orderIndex, feature.createdAt, feature.updatedAt);
|
|
356
|
-
this.db.bumpLastModified();
|
|
357
|
-
this.emit("feature:created", feature);
|
|
358
|
-
return feature;
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Get a feature by ID.
|
|
362
|
-
*
|
|
363
|
-
* @param id - Feature ID
|
|
364
|
-
* @returns The feature, or undefined if not found
|
|
365
|
-
*/
|
|
366
|
-
getFeature(id) {
|
|
367
|
-
const row = this.db.prepare("SELECT * FROM roadmap_features WHERE id = ?").get(id);
|
|
368
|
-
if (!row)
|
|
369
|
-
return undefined;
|
|
370
|
-
return this.rowToFeature(row);
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* List features for a milestone, ordered deterministically.
|
|
374
|
-
*
|
|
375
|
-
* Uses deterministic ordering: ORDER BY orderIndex ASC, createdAt ASC, id ASC
|
|
376
|
-
* to ensure consistent results when stored order data is incomplete or conflicting.
|
|
377
|
-
*
|
|
378
|
-
* @param milestoneId - Milestone ID
|
|
379
|
-
* @returns Array of features in deterministic order
|
|
380
|
-
*/
|
|
381
|
-
listFeatures(milestoneId) {
|
|
382
|
-
const rows = this.db.prepare("SELECT * FROM roadmap_features WHERE milestoneId = ? ORDER BY orderIndex ASC, createdAt ASC, id ASC").all(milestoneId);
|
|
383
|
-
return rows.map((row) => this.rowToFeature(row));
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Update a feature.
|
|
387
|
-
*
|
|
388
|
-
* @param id - Feature ID
|
|
389
|
-
* @param updates - Partial feature updates
|
|
390
|
-
* @returns The updated feature
|
|
391
|
-
* @throws Error if feature not found
|
|
392
|
-
*/
|
|
393
|
-
updateFeature(id, updates) {
|
|
394
|
-
const feature = this.getFeature(id);
|
|
395
|
-
if (!feature) {
|
|
396
|
-
throw new Error(`Feature ${id} not found`);
|
|
397
|
-
}
|
|
398
|
-
const updated = {
|
|
399
|
-
...feature,
|
|
400
|
-
...updates,
|
|
401
|
-
id, // Prevent changing ID
|
|
402
|
-
milestoneId: feature.milestoneId, // Prevent moving via update (use moveFeature instead)
|
|
403
|
-
createdAt: feature.createdAt, // Prevent changing creation time
|
|
404
|
-
updatedAt: new Date().toISOString(),
|
|
405
|
-
};
|
|
406
|
-
this.db.prepare(`
|
|
407
|
-
UPDATE roadmap_features SET
|
|
408
|
-
title = ?,
|
|
409
|
-
description = ?,
|
|
410
|
-
updatedAt = ?
|
|
411
|
-
WHERE id = ?
|
|
412
|
-
`).run(updated.title, updated.description ?? null, updated.updatedAt, updated.id);
|
|
413
|
-
this.db.bumpLastModified();
|
|
414
|
-
this.emit("feature:updated", updated);
|
|
415
|
-
return updated;
|
|
416
|
-
}
|
|
417
|
-
/**
|
|
418
|
-
* Delete a feature.
|
|
419
|
-
*
|
|
420
|
-
* @param id - Feature ID
|
|
421
|
-
* @throws Error if feature not found
|
|
422
|
-
*/
|
|
423
|
-
deleteFeature(id) {
|
|
424
|
-
const feature = this.getFeature(id);
|
|
425
|
-
if (!feature) {
|
|
426
|
-
throw new Error(`Feature ${id} not found`);
|
|
427
|
-
}
|
|
428
|
-
this.db.prepare("DELETE FROM roadmap_features WHERE id = ?").run(id);
|
|
429
|
-
this.db.bumpLastModified();
|
|
430
|
-
this.emit("feature:deleted", feature);
|
|
431
|
-
}
|
|
432
|
-
// ── Reorder Operations ────────────────────────────────────────────
|
|
433
|
-
/**
|
|
434
|
-
* Reorder milestones within a roadmap.
|
|
435
|
-
*
|
|
436
|
-
* Applies an explicit reorder input and persists the full normalized order.
|
|
437
|
-
* The input must contain all milestone IDs exactly once.
|
|
438
|
-
*
|
|
439
|
-
* @param input - Reorder input with complete milestone ID list
|
|
440
|
-
* @returns The reordered milestones in their new order
|
|
441
|
-
* @throws Error if milestone set is incomplete, duplicate, or not found
|
|
442
|
-
*/
|
|
443
|
-
reorderMilestones(input) {
|
|
444
|
-
// Validate roadmap exists
|
|
445
|
-
const roadmap = this.getRoadmap(input.roadmapId);
|
|
446
|
-
if (!roadmap) {
|
|
447
|
-
throw new Error(`Roadmap ${input.roadmapId} not found`);
|
|
448
|
-
}
|
|
449
|
-
// Load current milestones with deterministic ordering
|
|
450
|
-
const milestones = this.listMilestones(input.roadmapId);
|
|
451
|
-
// Apply the reorder using the pure ordering helper
|
|
452
|
-
const reordered = applyRoadmapMilestoneReorder(milestones, input);
|
|
453
|
-
// Persist in a transaction
|
|
454
|
-
this.db.transaction(() => {
|
|
455
|
-
for (const milestone of reordered) {
|
|
456
|
-
this.db.prepare(`
|
|
457
|
-
UPDATE roadmap_milestones SET orderIndex = ?, updatedAt = ? WHERE id = ?
|
|
458
|
-
`).run(milestone.orderIndex, new Date().toISOString(), milestone.id);
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
this.db.bumpLastModified();
|
|
462
|
-
this.emit("milestone:reordered", { roadmapId: input.roadmapId, milestones: reordered });
|
|
463
|
-
return reordered;
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Reorder features within a milestone.
|
|
467
|
-
*
|
|
468
|
-
* Applies an explicit reorder input and persists the full normalized order.
|
|
469
|
-
* The input must contain all feature IDs for the milestone exactly once.
|
|
470
|
-
*
|
|
471
|
-
* @param input - Reorder input with complete feature ID list
|
|
472
|
-
* @returns The reordered features in their new order
|
|
473
|
-
* @throws Error if feature set is incomplete, duplicate, or not found
|
|
474
|
-
*/
|
|
475
|
-
reorderFeatures(input) {
|
|
476
|
-
// Validate milestone exists and belongs to the roadmap
|
|
477
|
-
const milestone = this.getMilestone(input.milestoneId);
|
|
478
|
-
if (!milestone) {
|
|
479
|
-
throw new Error(`Milestone ${input.milestoneId} not found`);
|
|
480
|
-
}
|
|
481
|
-
if (milestone.roadmapId !== input.roadmapId) {
|
|
482
|
-
throw new Error(`Milestone ${input.milestoneId} does not belong to roadmap ${input.roadmapId}`);
|
|
483
|
-
}
|
|
484
|
-
// Load current features with deterministic ordering
|
|
485
|
-
const features = this.listFeatures(input.milestoneId);
|
|
486
|
-
// Apply the reorder using the pure ordering helper
|
|
487
|
-
const reordered = applyRoadmapFeatureReorder(features, input);
|
|
488
|
-
// Persist in a transaction
|
|
489
|
-
this.db.transaction(() => {
|
|
490
|
-
for (const feature of reordered) {
|
|
491
|
-
this.db.prepare(`
|
|
492
|
-
UPDATE roadmap_features SET orderIndex = ?, updatedAt = ? WHERE id = ?
|
|
493
|
-
`).run(feature.orderIndex, new Date().toISOString(), feature.id);
|
|
494
|
-
}
|
|
495
|
-
});
|
|
496
|
-
this.db.bumpLastModified();
|
|
497
|
-
this.emit("feature:reordered", { milestoneId: input.milestoneId, features: reordered });
|
|
498
|
-
return reordered;
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* Move a feature, including cross-milestone moves.
|
|
502
|
-
*
|
|
503
|
-
* Atomically renumbers both the source and destination milestone scopes.
|
|
504
|
-
*
|
|
505
|
-
* @param input - Move input with source/destination milestone info
|
|
506
|
-
* @returns The moved feature and both affected milestone feature lists
|
|
507
|
-
* @throws Error if feature or milestone not found, or scope validation fails
|
|
508
|
-
*/
|
|
509
|
-
moveFeature(input) {
|
|
510
|
-
// Validate roadmap exists
|
|
511
|
-
const roadmap = this.getRoadmap(input.roadmapId);
|
|
512
|
-
if (!roadmap) {
|
|
513
|
-
throw new Error(`Roadmap ${input.roadmapId} not found`);
|
|
514
|
-
}
|
|
515
|
-
// Validate both milestones exist and belong to the roadmap
|
|
516
|
-
const fromMilestone = this.getMilestone(input.fromMilestoneId);
|
|
517
|
-
const toMilestone = this.getMilestone(input.toMilestoneId);
|
|
518
|
-
if (!fromMilestone) {
|
|
519
|
-
throw new Error(`Source milestone ${input.fromMilestoneId} not found`);
|
|
520
|
-
}
|
|
521
|
-
if (!toMilestone) {
|
|
522
|
-
throw new Error(`Destination milestone ${input.toMilestoneId} not found`);
|
|
523
|
-
}
|
|
524
|
-
if (fromMilestone.roadmapId !== input.roadmapId) {
|
|
525
|
-
throw new Error(`Source milestone ${input.fromMilestoneId} does not belong to roadmap ${input.roadmapId}`);
|
|
526
|
-
}
|
|
527
|
-
if (toMilestone.roadmapId !== input.roadmapId) {
|
|
528
|
-
throw new Error(`Destination milestone ${input.toMilestoneId} does not belong to roadmap ${input.roadmapId}`);
|
|
529
|
-
}
|
|
530
|
-
// Load features from both milestones with deterministic ordering
|
|
531
|
-
const sourceFeatures = this.listFeatures(input.fromMilestoneId);
|
|
532
|
-
const targetFeatures = this.listFeatures(input.toMilestoneId);
|
|
533
|
-
// For same-milestone moves, pass only one list to avoid duplication
|
|
534
|
-
// For cross-milestone moves, pass the combined list
|
|
535
|
-
const allFeatures = input.fromMilestoneId === input.toMilestoneId
|
|
536
|
-
? sourceFeatures
|
|
537
|
-
: [...sourceFeatures, ...targetFeatures];
|
|
538
|
-
// Apply the move using the pure ordering helper
|
|
539
|
-
const result = moveRoadmapFeature(allFeatures, input);
|
|
540
|
-
// Persist in a transaction
|
|
541
|
-
this.db.transaction(() => {
|
|
542
|
-
// Update all affected features
|
|
543
|
-
for (const feature of result.affectedFeatures) {
|
|
544
|
-
this.db.prepare(`
|
|
545
|
-
UPDATE roadmap_features SET milestoneId = ?, orderIndex = ?, updatedAt = ? WHERE id = ?
|
|
546
|
-
`).run(feature.milestoneId, feature.orderIndex, new Date().toISOString(), feature.id);
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
this.db.bumpLastModified();
|
|
550
|
-
this.emit("feature:moved", {
|
|
551
|
-
feature: result.movedFeature,
|
|
552
|
-
fromMilestoneId: input.fromMilestoneId,
|
|
553
|
-
toMilestoneId: input.toMilestoneId,
|
|
554
|
-
});
|
|
555
|
-
return {
|
|
556
|
-
movedFeature: result.movedFeature,
|
|
557
|
-
sourceMilestoneFeatures: result.sourceMilestoneFeatures,
|
|
558
|
-
targetMilestoneFeatures: result.targetMilestoneFeatures,
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
// ── Hierarchy Operations ───────────────────────────────────────────
|
|
562
|
-
/**
|
|
563
|
-
* Get a milestone with all of its features in deterministic order.
|
|
564
|
-
*
|
|
565
|
-
* @param id - Milestone ID
|
|
566
|
-
* @returns The milestone with features, or undefined if not found
|
|
567
|
-
*/
|
|
568
|
-
getMilestoneWithFeatures(id) {
|
|
569
|
-
const milestone = this.getMilestone(id);
|
|
570
|
-
if (!milestone)
|
|
571
|
-
return undefined;
|
|
572
|
-
return {
|
|
573
|
-
...milestone,
|
|
574
|
-
features: this.listFeatures(id),
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
/**
|
|
578
|
-
* Get a roadmap with its full hierarchy (milestones → features).
|
|
579
|
-
*
|
|
580
|
-
* @param id - Roadmap ID
|
|
581
|
-
* @returns The roadmap with hierarchy, or undefined if not found
|
|
582
|
-
*/
|
|
583
|
-
getRoadmapWithHierarchy(id) {
|
|
584
|
-
const roadmap = this.getRoadmap(id);
|
|
585
|
-
if (!roadmap)
|
|
586
|
-
return undefined;
|
|
587
|
-
return {
|
|
588
|
-
...roadmap,
|
|
589
|
-
milestones: this.listMilestones(id).map((milestone) => ({
|
|
590
|
-
...milestone,
|
|
591
|
-
features: this.listFeatures(milestone.id),
|
|
592
|
-
})),
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
// ── Export / Handoff Operations ────────────────────────────────────
|
|
596
|
-
/**
|
|
597
|
-
* Get a flat export bundle for a roadmap.
|
|
598
|
-
*
|
|
599
|
-
* Returns all roadmap data in a flat structure suitable for persistence,
|
|
600
|
-
* APIs, import/export, and sync jobs. Entities are separated so downstream
|
|
601
|
-
* persistence layers can upsert by table/collection.
|
|
602
|
-
*
|
|
603
|
-
* @param roadmapId - Roadmap ID
|
|
604
|
-
* @returns The export bundle with ordered entities
|
|
605
|
-
* @throws Error if roadmap not found
|
|
606
|
-
*/
|
|
607
|
-
getRoadmapExport(roadmapId) {
|
|
608
|
-
const roadmap = this.getRoadmap(roadmapId);
|
|
609
|
-
if (!roadmap) {
|
|
610
|
-
throw new Error(`Roadmap ${roadmapId} not found`);
|
|
611
|
-
}
|
|
612
|
-
const milestones = this.listMilestones(roadmapId);
|
|
613
|
-
const allFeatures = [];
|
|
614
|
-
for (const milestone of milestones) {
|
|
615
|
-
const features = this.listFeatures(milestone.id);
|
|
616
|
-
allFeatures.push(...features);
|
|
617
|
-
}
|
|
618
|
-
return {
|
|
619
|
-
roadmap,
|
|
620
|
-
milestones,
|
|
621
|
-
features: allFeatures,
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
|
-
/**
|
|
625
|
-
* Get a mission planning handoff payload for a roadmap.
|
|
626
|
-
*
|
|
627
|
-
* Converts the roadmap into a mission planning structure while preserving
|
|
628
|
-
* source IDs and deterministic order. Does not couple to MissionStore internals.
|
|
629
|
-
*
|
|
630
|
-
* @param roadmapId - Roadmap ID
|
|
631
|
-
* @returns The mission planning handoff payload
|
|
632
|
-
* @throws Error if roadmap not found
|
|
633
|
-
*/
|
|
634
|
-
getRoadmapMissionHandoff(roadmapId) {
|
|
635
|
-
const roadmap = this.getRoadmap(roadmapId);
|
|
636
|
-
if (!roadmap) {
|
|
637
|
-
throw new Error(`Roadmap ${roadmapId} not found`);
|
|
638
|
-
}
|
|
639
|
-
const milestones = this.listMilestones(roadmapId);
|
|
640
|
-
return {
|
|
641
|
-
sourceRoadmapId: roadmap.id,
|
|
642
|
-
title: roadmap.title,
|
|
643
|
-
description: roadmap.description,
|
|
644
|
-
milestones: milestones.map((milestone) => {
|
|
645
|
-
const features = this.listFeatures(milestone.id);
|
|
646
|
-
return {
|
|
647
|
-
sourceMilestoneId: milestone.id,
|
|
648
|
-
title: milestone.title,
|
|
649
|
-
description: milestone.description,
|
|
650
|
-
orderIndex: milestone.orderIndex,
|
|
651
|
-
features: features.map((feature) => ({
|
|
652
|
-
sourceFeatureId: feature.id,
|
|
653
|
-
title: feature.title,
|
|
654
|
-
description: feature.description,
|
|
655
|
-
orderIndex: feature.orderIndex,
|
|
656
|
-
})),
|
|
657
|
-
};
|
|
658
|
-
}),
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
/**
|
|
662
|
-
* Get a task planning handoff payload for a single roadmap feature.
|
|
663
|
-
*
|
|
664
|
-
* Returns a self-contained handoff payload for converting a roadmap feature
|
|
665
|
-
* into task planning flows without coupling to MissionStore internals.
|
|
666
|
-
*
|
|
667
|
-
* @param roadmapId - Parent roadmap ID (for validation)
|
|
668
|
-
* @param milestoneId - Parent milestone ID (for validation)
|
|
669
|
-
* @param featureId - Feature ID to generate handoff for
|
|
670
|
-
* @returns The task planning handoff payload
|
|
671
|
-
* @throws Error if any entity is not found or if ownership validation fails
|
|
672
|
-
*/
|
|
673
|
-
getRoadmapFeatureHandoff(roadmapId, milestoneId, featureId) {
|
|
674
|
-
// Validate roadmap exists
|
|
675
|
-
const roadmap = this.getRoadmap(roadmapId);
|
|
676
|
-
if (!roadmap) {
|
|
677
|
-
throw new Error(`Roadmap ${roadmapId} not found`);
|
|
678
|
-
}
|
|
679
|
-
// Validate milestone exists and belongs to roadmap
|
|
680
|
-
const milestone = this.getMilestone(milestoneId);
|
|
681
|
-
if (!milestone) {
|
|
682
|
-
throw new Error(`Milestone ${milestoneId} not found`);
|
|
683
|
-
}
|
|
684
|
-
if (milestone.roadmapId !== roadmapId) {
|
|
685
|
-
throw new Error(`Milestone ${milestoneId} does not belong to roadmap ${roadmapId}`);
|
|
686
|
-
}
|
|
687
|
-
// Validate feature exists and belongs to milestone
|
|
688
|
-
const feature = this.getFeature(featureId);
|
|
689
|
-
if (!feature) {
|
|
690
|
-
throw new Error(`Feature ${featureId} not found`);
|
|
691
|
-
}
|
|
692
|
-
if (feature.milestoneId !== milestoneId) {
|
|
693
|
-
throw new Error(`Feature ${featureId} does not belong to milestone ${milestoneId}`);
|
|
694
|
-
}
|
|
695
|
-
// Build the source reference with ordering context
|
|
696
|
-
const source = {
|
|
697
|
-
roadmapId: roadmap.id,
|
|
698
|
-
milestoneId: milestone.id,
|
|
699
|
-
featureId: feature.id,
|
|
700
|
-
roadmapTitle: roadmap.title,
|
|
701
|
-
milestoneTitle: milestone.title,
|
|
702
|
-
milestoneOrderIndex: milestone.orderIndex,
|
|
703
|
-
featureOrderIndex: feature.orderIndex,
|
|
704
|
-
};
|
|
705
|
-
return {
|
|
706
|
-
source,
|
|
707
|
-
title: feature.title,
|
|
708
|
-
description: feature.description,
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* Get a mission planning handoff payload for a roadmap.
|
|
713
|
-
*
|
|
714
|
-
* Alias for getRoadmapMissionHandoff() for API consistency.
|
|
715
|
-
* Converts the roadmap into a mission planning structure while preserving
|
|
716
|
-
* source IDs and deterministic order.
|
|
717
|
-
*
|
|
718
|
-
* @param roadmapId - Roadmap ID
|
|
719
|
-
* @returns The mission planning handoff payload
|
|
720
|
-
* @throws Error if roadmap not found
|
|
721
|
-
*/
|
|
722
|
-
getMissionPlanningHandoff(roadmapId) {
|
|
723
|
-
return this.getRoadmapMissionHandoff(roadmapId);
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* List all task planning handoff payloads for a roadmap.
|
|
727
|
-
*
|
|
728
|
-
* Returns a flat list of all feature handoffs in deterministic order
|
|
729
|
-
* (milestone order index, then feature order index).
|
|
730
|
-
*
|
|
731
|
-
* @param roadmapId - Roadmap ID
|
|
732
|
-
* @returns Array of task planning handoff payloads for all features
|
|
733
|
-
* @throws Error if roadmap not found
|
|
734
|
-
*/
|
|
735
|
-
listFeatureTaskPlanningHandoffs(roadmapId) {
|
|
736
|
-
// Validate roadmap exists
|
|
737
|
-
const roadmap = this.getRoadmap(roadmapId);
|
|
738
|
-
if (!roadmap) {
|
|
739
|
-
throw new Error(`Roadmap ${roadmapId} not found`);
|
|
740
|
-
}
|
|
741
|
-
const milestones = this.listMilestones(roadmapId);
|
|
742
|
-
const handoffs = [];
|
|
743
|
-
for (const milestone of milestones) {
|
|
744
|
-
const features = this.listFeatures(milestone.id);
|
|
745
|
-
for (const feature of features) {
|
|
746
|
-
const source = {
|
|
747
|
-
roadmapId: roadmap.id,
|
|
748
|
-
milestoneId: milestone.id,
|
|
749
|
-
featureId: feature.id,
|
|
750
|
-
roadmapTitle: roadmap.title,
|
|
751
|
-
milestoneTitle: milestone.title,
|
|
752
|
-
milestoneOrderIndex: milestone.orderIndex,
|
|
753
|
-
featureOrderIndex: feature.orderIndex,
|
|
754
|
-
};
|
|
755
|
-
handoffs.push({
|
|
756
|
-
source,
|
|
757
|
-
title: feature.title,
|
|
758
|
-
description: feature.description,
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
return handoffs;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
//# sourceMappingURL=roadmap-store.js.map
|