@cliangdev/flux-plugin 0.2.0 → 0.3.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 (108) hide show
  1. package/README.md +11 -7
  2. package/agents/coder.md +150 -25
  3. package/bin/install.cjs +171 -16
  4. package/commands/breakdown.md +47 -10
  5. package/commands/dashboard.md +29 -0
  6. package/commands/flux.md +92 -12
  7. package/commands/implement.md +166 -17
  8. package/commands/linear.md +6 -5
  9. package/commands/prd.md +996 -82
  10. package/manifest.json +2 -1
  11. package/package.json +9 -11
  12. package/skills/flux-orchestrator/SKILL.md +11 -3
  13. package/skills/prd-writer/SKILL.md +761 -0
  14. package/skills/ux-ui-design/SKILL.md +346 -0
  15. package/skills/ux-ui-design/references/design-tokens.md +359 -0
  16. package/src/__tests__/version.test.ts +37 -0
  17. package/src/adapters/local/.gitkeep +0 -0
  18. package/src/dashboard/__tests__/api.test.ts +211 -0
  19. package/src/dashboard/browser.ts +35 -0
  20. package/src/dashboard/public/app.js +869 -0
  21. package/src/dashboard/public/index.html +90 -0
  22. package/src/dashboard/public/styles.css +807 -0
  23. package/src/dashboard/public/vendor/highlight.css +10 -0
  24. package/src/dashboard/public/vendor/highlight.min.js +8422 -0
  25. package/src/dashboard/public/vendor/marked.min.js +2210 -0
  26. package/src/dashboard/server.ts +296 -0
  27. package/src/dashboard/watchers.ts +83 -0
  28. package/src/server/__tests__/config.test.ts +163 -0
  29. package/src/server/adapters/__tests__/a-client-linear.test.ts +197 -0
  30. package/src/server/adapters/__tests__/adapter-factory.test.ts +230 -0
  31. package/src/server/adapters/__tests__/dependency-ops.test.ts +429 -0
  32. package/src/server/adapters/__tests__/document-ops.test.ts +306 -0
  33. package/src/server/adapters/__tests__/linear-adapter.test.ts +91 -0
  34. package/src/server/adapters/__tests__/linear-config.test.ts +425 -0
  35. package/src/server/adapters/__tests__/linear-criteria-parser.test.ts +287 -0
  36. package/src/server/adapters/__tests__/linear-description-test.ts +238 -0
  37. package/src/server/adapters/__tests__/linear-epic-crud.test.ts +496 -0
  38. package/src/server/adapters/__tests__/linear-mappers-description.test.ts +276 -0
  39. package/src/server/adapters/__tests__/linear-mappers-epic.test.ts +294 -0
  40. package/src/server/adapters/__tests__/linear-mappers-prd.test.ts +300 -0
  41. package/src/server/adapters/__tests__/linear-mappers-task.test.ts +197 -0
  42. package/src/server/adapters/__tests__/linear-prd-crud.test.ts +620 -0
  43. package/src/server/adapters/__tests__/linear-stats.test.ts +450 -0
  44. package/src/server/adapters/__tests__/linear-task-crud.test.ts +534 -0
  45. package/src/server/adapters/__tests__/linear-types.test.ts +243 -0
  46. package/src/server/adapters/__tests__/status-ops.test.ts +441 -0
  47. package/src/server/adapters/factory.ts +90 -0
  48. package/src/server/adapters/index.ts +9 -0
  49. package/src/server/adapters/linear/adapter.ts +1141 -0
  50. package/src/server/adapters/linear/client.ts +169 -0
  51. package/src/server/adapters/linear/config.ts +152 -0
  52. package/src/server/adapters/linear/helpers/criteria-parser.ts +197 -0
  53. package/src/server/adapters/linear/helpers/index.ts +7 -0
  54. package/src/server/adapters/linear/index.ts +16 -0
  55. package/src/server/adapters/linear/mappers/description.ts +136 -0
  56. package/src/server/adapters/linear/mappers/epic.ts +81 -0
  57. package/src/server/adapters/linear/mappers/index.ts +27 -0
  58. package/src/server/adapters/linear/mappers/prd.ts +178 -0
  59. package/src/server/adapters/linear/mappers/task.ts +82 -0
  60. package/src/server/adapters/linear/types.ts +264 -0
  61. package/src/server/adapters/local-adapter.ts +1009 -0
  62. package/src/server/adapters/types.ts +293 -0
  63. package/src/server/config.ts +73 -0
  64. package/src/server/db/__tests__/queries.test.ts +473 -0
  65. package/src/server/db/ids.ts +17 -0
  66. package/src/server/db/index.ts +69 -0
  67. package/src/server/db/queries.ts +142 -0
  68. package/src/server/db/refs.ts +60 -0
  69. package/src/server/db/schema.ts +97 -0
  70. package/src/server/db/sqlite.ts +10 -0
  71. package/src/server/index.ts +81 -0
  72. package/src/server/tools/__tests__/crud.test.ts +411 -0
  73. package/src/server/tools/__tests__/get-version.test.ts +27 -0
  74. package/src/server/tools/__tests__/mcp-interface.test.ts +479 -0
  75. package/src/server/tools/__tests__/query.test.ts +405 -0
  76. package/src/server/tools/__tests__/z-configure-linear.test.ts +511 -0
  77. package/src/server/tools/__tests__/z-get-linear-url.test.ts +108 -0
  78. package/src/server/tools/configure-linear.ts +373 -0
  79. package/src/server/tools/create-epic.ts +44 -0
  80. package/src/server/tools/create-prd.ts +40 -0
  81. package/src/server/tools/create-task.ts +47 -0
  82. package/src/server/tools/criteria.ts +50 -0
  83. package/src/server/tools/delete-entity.ts +76 -0
  84. package/src/server/tools/dependencies.ts +55 -0
  85. package/src/server/tools/get-entity.ts +240 -0
  86. package/src/server/tools/get-linear-url.ts +28 -0
  87. package/src/server/tools/get-stats.ts +52 -0
  88. package/src/server/tools/get-version.ts +20 -0
  89. package/src/server/tools/index.ts +158 -0
  90. package/src/server/tools/init-project.ts +108 -0
  91. package/src/server/tools/query-entities.ts +167 -0
  92. package/src/server/tools/render-status.ts +219 -0
  93. package/src/server/tools/update-entity.ts +140 -0
  94. package/src/server/tools/update-status.ts +166 -0
  95. package/src/server/utils/__tests__/mcp-response.test.ts +331 -0
  96. package/src/server/utils/logger.ts +9 -0
  97. package/src/server/utils/mcp-response.ts +254 -0
  98. package/src/server/utils/status-transitions.ts +160 -0
  99. package/src/status-line/__tests__/status-line.test.ts +215 -0
  100. package/src/status-line/index.ts +147 -0
  101. package/src/utils/__tests__/chalk-import.test.ts +32 -0
  102. package/src/utils/__tests__/display.test.ts +97 -0
  103. package/src/utils/__tests__/status-renderer.test.ts +310 -0
  104. package/src/utils/display.ts +62 -0
  105. package/src/utils/status-renderer.ts +214 -0
  106. package/src/version.ts +5 -0
  107. package/dist/server/index.js +0 -87063
  108. package/skills/prd-template/SKILL.md +0 -242
@@ -0,0 +1,331 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type {
3
+ AcceptanceCriterion,
4
+ CascadeResult,
5
+ Epic,
6
+ PaginatedResult,
7
+ Prd,
8
+ Stats,
9
+ Task,
10
+ } from "../../adapters/types.js";
11
+ import {
12
+ toMcpCascadeResult,
13
+ toMcpCriterion,
14
+ toMcpEpic,
15
+ toMcpPaginatedEpics,
16
+ toMcpPaginatedPrds,
17
+ toMcpPaginatedTasks,
18
+ toMcpPrd,
19
+ toMcpStats,
20
+ toMcpTask,
21
+ } from "../mcp-response.js";
22
+
23
+ describe("MCP Response Transformations", () => {
24
+ describe("toMcpPrd", () => {
25
+ test("transforms PRD with all fields", () => {
26
+ const prd: Prd = {
27
+ id: "prd_123",
28
+ projectId: "proj_456",
29
+ ref: "TEST-P1",
30
+ title: "Test PRD",
31
+ description: "A description",
32
+ status: "DRAFT",
33
+ tag: "mvp",
34
+ folderPath: ".flux/prds/test",
35
+ createdAt: "2024-01-01T00:00:00Z",
36
+ updatedAt: "2024-01-02T00:00:00Z",
37
+ };
38
+
39
+ const result = toMcpPrd(prd);
40
+
41
+ expect(result.id).toBe("prd_123");
42
+ expect(result.project_id).toBe("proj_456");
43
+ expect(result.ref).toBe("TEST-P1");
44
+ expect(result.title).toBe("Test PRD");
45
+ expect(result.description).toBe("A description");
46
+ expect(result.status).toBe("DRAFT");
47
+ expect(result.tag).toBe("mvp");
48
+ expect(result.folder_path).toBe(".flux/prds/test");
49
+ expect(result.created_at).toBe("2024-01-01T00:00:00Z");
50
+ expect(result.updated_at).toBe("2024-01-02T00:00:00Z");
51
+ });
52
+
53
+ test("transforms PRD with optional fields undefined", () => {
54
+ const prd: Prd = {
55
+ id: "prd_123",
56
+ projectId: "proj_456",
57
+ ref: "TEST-P1",
58
+ title: "Test PRD",
59
+ status: "DRAFT",
60
+ createdAt: "2024-01-01T00:00:00Z",
61
+ updatedAt: "2024-01-02T00:00:00Z",
62
+ };
63
+
64
+ const result = toMcpPrd(prd);
65
+
66
+ expect(result.description).toBeUndefined();
67
+ expect(result.tag).toBeUndefined();
68
+ expect(result.folder_path).toBeUndefined();
69
+ });
70
+ });
71
+
72
+ describe("toMcpEpic", () => {
73
+ test("transforms Epic with all fields", () => {
74
+ const epic: Epic = {
75
+ id: "epic_123",
76
+ prdId: "prd_456",
77
+ ref: "TEST-E1",
78
+ title: "Test Epic",
79
+ description: "Epic description",
80
+ status: "IN_PROGRESS",
81
+ createdAt: "2024-01-01T00:00:00Z",
82
+ updatedAt: "2024-01-02T00:00:00Z",
83
+ };
84
+
85
+ const result = toMcpEpic(epic);
86
+
87
+ expect(result.id).toBe("epic_123");
88
+ expect(result.prd_id).toBe("prd_456");
89
+ expect(result.ref).toBe("TEST-E1");
90
+ expect(result.title).toBe("Test Epic");
91
+ expect(result.description).toBe("Epic description");
92
+ expect(result.status).toBe("IN_PROGRESS");
93
+ expect(result.created_at).toBe("2024-01-01T00:00:00Z");
94
+ expect(result.updated_at).toBe("2024-01-02T00:00:00Z");
95
+ });
96
+ });
97
+
98
+ describe("toMcpTask", () => {
99
+ test("transforms Task with all fields", () => {
100
+ const task: Task = {
101
+ id: "task_123",
102
+ epicId: "epic_456",
103
+ ref: "TEST-T1",
104
+ title: "Test Task",
105
+ description: "Task description",
106
+ status: "PENDING",
107
+ priority: "HIGH",
108
+ createdAt: "2024-01-01T00:00:00Z",
109
+ updatedAt: "2024-01-02T00:00:00Z",
110
+ };
111
+
112
+ const result = toMcpTask(task);
113
+
114
+ expect(result.id).toBe("task_123");
115
+ expect(result.epic_id).toBe("epic_456");
116
+ expect(result.ref).toBe("TEST-T1");
117
+ expect(result.title).toBe("Test Task");
118
+ expect(result.description).toBe("Task description");
119
+ expect(result.status).toBe("PENDING");
120
+ expect(result.priority).toBe("HIGH");
121
+ expect(result.created_at).toBe("2024-01-01T00:00:00Z");
122
+ expect(result.updated_at).toBe("2024-01-02T00:00:00Z");
123
+ });
124
+ });
125
+
126
+ describe("toMcpCriterion", () => {
127
+ test("transforms AcceptanceCriterion", () => {
128
+ const criterion: AcceptanceCriterion = {
129
+ id: "ac_123",
130
+ parentType: "epic",
131
+ parentId: "epic_456",
132
+ criteria: "Should work correctly",
133
+ isMet: true,
134
+ createdAt: "2024-01-01T00:00:00Z",
135
+ };
136
+
137
+ const result = toMcpCriterion(criterion);
138
+
139
+ expect(result.id).toBe("ac_123");
140
+ expect(result.parent_type).toBe("epic");
141
+ expect(result.parent_id).toBe("epic_456");
142
+ expect(result.criteria).toBe("Should work correctly");
143
+ expect(result.is_met).toBe(true);
144
+ expect(result.created_at).toBe("2024-01-01T00:00:00Z");
145
+ });
146
+
147
+ test("transforms criterion with is_met false", () => {
148
+ const criterion: AcceptanceCriterion = {
149
+ id: "ac_123",
150
+ parentType: "task",
151
+ parentId: "task_456",
152
+ criteria: "Not done yet",
153
+ isMet: false,
154
+ createdAt: "2024-01-01T00:00:00Z",
155
+ };
156
+
157
+ const result = toMcpCriterion(criterion);
158
+
159
+ expect(result.is_met).toBe(false);
160
+ expect(result.parent_type).toBe("task");
161
+ });
162
+ });
163
+
164
+ describe("toMcpStats", () => {
165
+ test("transforms Stats with all status counts", () => {
166
+ const stats: Stats = {
167
+ prds: {
168
+ total: 10,
169
+ draft: 3,
170
+ pendingReview: 2,
171
+ reviewed: 1,
172
+ approved: 2,
173
+ breakdownReady: 1,
174
+ completed: 1,
175
+ archived: 2,
176
+ },
177
+ epics: {
178
+ total: 20,
179
+ pending: 10,
180
+ inProgress: 5,
181
+ completed: 5,
182
+ },
183
+ tasks: {
184
+ total: 50,
185
+ pending: 25,
186
+ inProgress: 15,
187
+ completed: 10,
188
+ },
189
+ };
190
+
191
+ const result = toMcpStats(stats);
192
+
193
+ // PRD stats
194
+ expect(result.prds.total).toBe(10);
195
+ expect(result.prds.draft).toBe(3);
196
+ expect(result.prds.pending_review).toBe(2);
197
+ expect(result.prds.reviewed).toBe(1);
198
+ expect(result.prds.approved).toBe(2);
199
+ expect(result.prds.breakdown_ready).toBe(1);
200
+ expect(result.prds.completed).toBe(1);
201
+ expect(result.prds.archived).toBe(2);
202
+
203
+ // Epic stats
204
+ expect(result.epics.total).toBe(20);
205
+ expect(result.epics.pending).toBe(10);
206
+ expect(result.epics.in_progress).toBe(5);
207
+ expect(result.epics.completed).toBe(5);
208
+
209
+ // Task stats
210
+ expect(result.tasks.total).toBe(50);
211
+ expect(result.tasks.pending).toBe(25);
212
+ expect(result.tasks.in_progress).toBe(15);
213
+ expect(result.tasks.completed).toBe(10);
214
+ });
215
+ });
216
+
217
+ describe("toMcpPaginatedPrds", () => {
218
+ test("transforms paginated PRD result", () => {
219
+ const paginatedResult: PaginatedResult<Prd> = {
220
+ items: [
221
+ {
222
+ id: "prd_1",
223
+ projectId: "proj_1",
224
+ ref: "TEST-P1",
225
+ title: "PRD 1",
226
+ status: "DRAFT",
227
+ createdAt: "2024-01-01T00:00:00Z",
228
+ updatedAt: "2024-01-02T00:00:00Z",
229
+ },
230
+ {
231
+ id: "prd_2",
232
+ projectId: "proj_1",
233
+ ref: "TEST-P2",
234
+ title: "PRD 2",
235
+ status: "APPROVED",
236
+ createdAt: "2024-01-01T00:00:00Z",
237
+ updatedAt: "2024-01-02T00:00:00Z",
238
+ },
239
+ ],
240
+ total: 5,
241
+ limit: 2,
242
+ offset: 0,
243
+ hasMore: true,
244
+ };
245
+
246
+ const result = toMcpPaginatedPrds(paginatedResult);
247
+
248
+ expect(result.items.length).toBe(2);
249
+ expect(result.items[0].project_id).toBe("proj_1"); // camelCase -> snake_case
250
+ expect(result.total).toBe(5);
251
+ expect(result.limit).toBe(2);
252
+ expect(result.offset).toBe(0);
253
+ expect(result.has_more).toBe(true); // hasMore -> has_more
254
+ });
255
+ });
256
+
257
+ describe("toMcpPaginatedEpics", () => {
258
+ test("transforms paginated Epic result", () => {
259
+ const paginatedResult: PaginatedResult<Epic> = {
260
+ items: [
261
+ {
262
+ id: "epic_1",
263
+ prdId: "prd_1",
264
+ ref: "TEST-E1",
265
+ title: "Epic 1",
266
+ status: "PENDING",
267
+ createdAt: "2024-01-01T00:00:00Z",
268
+ updatedAt: "2024-01-02T00:00:00Z",
269
+ },
270
+ ],
271
+ total: 1,
272
+ limit: 10,
273
+ offset: 0,
274
+ hasMore: false,
275
+ };
276
+
277
+ const result = toMcpPaginatedEpics(paginatedResult);
278
+
279
+ expect(result.items.length).toBe(1);
280
+ expect(result.items[0].prd_id).toBe("prd_1");
281
+ expect(result.has_more).toBe(false);
282
+ });
283
+ });
284
+
285
+ describe("toMcpPaginatedTasks", () => {
286
+ test("transforms paginated Task result", () => {
287
+ const paginatedResult: PaginatedResult<Task> = {
288
+ items: [
289
+ {
290
+ id: "task_1",
291
+ epicId: "epic_1",
292
+ ref: "TEST-T1",
293
+ title: "Task 1",
294
+ status: "IN_PROGRESS",
295
+ priority: "MEDIUM",
296
+ createdAt: "2024-01-01T00:00:00Z",
297
+ updatedAt: "2024-01-02T00:00:00Z",
298
+ },
299
+ ],
300
+ total: 1,
301
+ limit: 10,
302
+ offset: 0,
303
+ hasMore: false,
304
+ };
305
+
306
+ const result = toMcpPaginatedTasks(paginatedResult);
307
+
308
+ expect(result.items.length).toBe(1);
309
+ expect(result.items[0].epic_id).toBe("epic_1");
310
+ expect(result.has_more).toBe(false);
311
+ });
312
+ });
313
+
314
+ describe("toMcpCascadeResult", () => {
315
+ test("transforms cascade deletion result", () => {
316
+ const cascade: CascadeResult = {
317
+ criteria: 5,
318
+ tasks: 10,
319
+ epics: 2,
320
+ dependencies: 3,
321
+ };
322
+
323
+ const result = toMcpCascadeResult(cascade);
324
+
325
+ expect(result.criteria).toBe(5);
326
+ expect(result.tasks).toBe(10);
327
+ expect(result.epics).toBe(2);
328
+ expect(result.dependencies).toBe(3);
329
+ });
330
+ });
331
+ });
@@ -0,0 +1,9 @@
1
+ export const logger = {
2
+ info: (msg: string, ...args: unknown[]) =>
3
+ console.error(`[INFO] ${msg}`, ...args),
4
+ error: (msg: string, ...args: unknown[]) =>
5
+ console.error(`[ERROR] ${msg}`, ...args),
6
+ debug: (msg: string, ...args: unknown[]) => {
7
+ if (process.env.DEBUG) console.error(`[DEBUG] ${msg}`, ...args);
8
+ },
9
+ };
@@ -0,0 +1,254 @@
1
+ /**
2
+ * MCP Response Utilities
3
+ *
4
+ * Transforms adapter responses (camelCase) to MCP format (snake_case).
5
+ * This ensures consistent API responses while keeping the adapter interface clean.
6
+ */
7
+
8
+ import type {
9
+ AcceptanceCriterion,
10
+ CascadeResult,
11
+ Epic,
12
+ PaginatedResult,
13
+ Prd,
14
+ Stats,
15
+ Task,
16
+ } from "../adapters/types.js";
17
+
18
+ // =============================================================================
19
+ // PRD Response Transformations
20
+ // =============================================================================
21
+
22
+ export interface McpPrd {
23
+ id: string;
24
+ project_id: string;
25
+ ref: string;
26
+ title: string;
27
+ description?: string;
28
+ status: string;
29
+ tag?: string;
30
+ folder_path?: string;
31
+ created_at: string;
32
+ updated_at: string;
33
+ }
34
+
35
+ export function toMcpPrd(prd: Prd): McpPrd {
36
+ return {
37
+ id: prd.id,
38
+ project_id: prd.projectId,
39
+ ref: prd.ref,
40
+ title: prd.title,
41
+ description: prd.description,
42
+ status: prd.status,
43
+ tag: prd.tag,
44
+ folder_path: prd.folderPath,
45
+ created_at: prd.createdAt,
46
+ updated_at: prd.updatedAt,
47
+ };
48
+ }
49
+
50
+ // =============================================================================
51
+ // Epic Response Transformations
52
+ // =============================================================================
53
+
54
+ export interface McpEpic {
55
+ id: string;
56
+ prd_id: string;
57
+ ref: string;
58
+ title: string;
59
+ description?: string;
60
+ status: string;
61
+ created_at: string;
62
+ updated_at: string;
63
+ }
64
+
65
+ export function toMcpEpic(epic: Epic): McpEpic {
66
+ return {
67
+ id: epic.id,
68
+ prd_id: epic.prdId,
69
+ ref: epic.ref,
70
+ title: epic.title,
71
+ description: epic.description,
72
+ status: epic.status,
73
+ created_at: epic.createdAt,
74
+ updated_at: epic.updatedAt,
75
+ };
76
+ }
77
+
78
+ // =============================================================================
79
+ // Task Response Transformations
80
+ // =============================================================================
81
+
82
+ export interface McpTask {
83
+ id: string;
84
+ epic_id: string;
85
+ ref: string;
86
+ title: string;
87
+ description?: string;
88
+ status: string;
89
+ priority: string;
90
+ created_at: string;
91
+ updated_at: string;
92
+ }
93
+
94
+ export function toMcpTask(task: Task): McpTask {
95
+ return {
96
+ id: task.id,
97
+ epic_id: task.epicId,
98
+ ref: task.ref,
99
+ title: task.title,
100
+ description: task.description,
101
+ status: task.status,
102
+ priority: task.priority,
103
+ created_at: task.createdAt,
104
+ updated_at: task.updatedAt,
105
+ };
106
+ }
107
+
108
+ // =============================================================================
109
+ // Acceptance Criteria Response Transformations
110
+ // =============================================================================
111
+
112
+ export interface McpCriterion {
113
+ id: string;
114
+ parent_type: string;
115
+ parent_id: string;
116
+ criteria: string;
117
+ is_met: boolean;
118
+ created_at: string;
119
+ }
120
+
121
+ export function toMcpCriterion(criterion: AcceptanceCriterion): McpCriterion {
122
+ return {
123
+ id: criterion.id,
124
+ parent_type: criterion.parentType,
125
+ parent_id: criterion.parentId,
126
+ criteria: criterion.criteria,
127
+ is_met: criterion.isMet,
128
+ created_at: criterion.createdAt,
129
+ };
130
+ }
131
+
132
+ // =============================================================================
133
+ // Stats Response Transformations
134
+ // =============================================================================
135
+
136
+ export interface McpStats {
137
+ prds: {
138
+ total: number;
139
+ draft: number;
140
+ pending_review: number;
141
+ reviewed: number;
142
+ approved: number;
143
+ breakdown_ready: number;
144
+ completed: number;
145
+ archived: number;
146
+ };
147
+ epics: {
148
+ total: number;
149
+ pending: number;
150
+ in_progress: number;
151
+ completed: number;
152
+ };
153
+ tasks: {
154
+ total: number;
155
+ pending: number;
156
+ in_progress: number;
157
+ completed: number;
158
+ };
159
+ }
160
+
161
+ export function toMcpStats(stats: Stats): McpStats {
162
+ return {
163
+ prds: {
164
+ total: stats.prds.total,
165
+ draft: stats.prds.draft,
166
+ pending_review: stats.prds.pendingReview,
167
+ reviewed: stats.prds.reviewed,
168
+ approved: stats.prds.approved,
169
+ breakdown_ready: stats.prds.breakdownReady,
170
+ completed: stats.prds.completed,
171
+ archived: stats.prds.archived,
172
+ },
173
+ epics: {
174
+ total: stats.epics.total,
175
+ pending: stats.epics.pending,
176
+ in_progress: stats.epics.inProgress,
177
+ completed: stats.epics.completed,
178
+ },
179
+ tasks: {
180
+ total: stats.tasks.total,
181
+ pending: stats.tasks.pending,
182
+ in_progress: stats.tasks.inProgress,
183
+ completed: stats.tasks.completed,
184
+ },
185
+ };
186
+ }
187
+
188
+ // =============================================================================
189
+ // Paginated Response Transformations
190
+ // =============================================================================
191
+
192
+ export interface McpPaginatedResult<T> {
193
+ items: T[];
194
+ total: number;
195
+ limit: number;
196
+ offset: number;
197
+ has_more: boolean;
198
+ }
199
+
200
+ export function toMcpPaginatedPrds(
201
+ result: PaginatedResult<Prd>,
202
+ ): McpPaginatedResult<McpPrd> {
203
+ return {
204
+ items: result.items.map(toMcpPrd),
205
+ total: result.total,
206
+ limit: result.limit,
207
+ offset: result.offset,
208
+ has_more: result.hasMore,
209
+ };
210
+ }
211
+
212
+ export function toMcpPaginatedEpics(
213
+ result: PaginatedResult<Epic>,
214
+ ): McpPaginatedResult<McpEpic> {
215
+ return {
216
+ items: result.items.map(toMcpEpic),
217
+ total: result.total,
218
+ limit: result.limit,
219
+ offset: result.offset,
220
+ has_more: result.hasMore,
221
+ };
222
+ }
223
+
224
+ export function toMcpPaginatedTasks(
225
+ result: PaginatedResult<Task>,
226
+ ): McpPaginatedResult<McpTask> {
227
+ return {
228
+ items: result.items.map(toMcpTask),
229
+ total: result.total,
230
+ limit: result.limit,
231
+ offset: result.offset,
232
+ has_more: result.hasMore,
233
+ };
234
+ }
235
+
236
+ // =============================================================================
237
+ // Cascade Result Transformation
238
+ // =============================================================================
239
+
240
+ export interface McpCascadeResult {
241
+ criteria: number;
242
+ tasks: number;
243
+ epics: number;
244
+ dependencies: number;
245
+ }
246
+
247
+ export function toMcpCascadeResult(cascade: CascadeResult): McpCascadeResult {
248
+ return {
249
+ criteria: cascade.criteria,
250
+ tasks: cascade.tasks,
251
+ epics: cascade.epics,
252
+ dependencies: cascade.dependencies,
253
+ };
254
+ }