@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,496 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+ import type { HydratedIssue, LinearAdapter } from "../linear/adapter.js";
3
+ import type { LinearConfig } from "../linear/types.js";
4
+
5
+ /**
6
+ * Helper to create a mock HydratedIssue
7
+ */
8
+ function createMockIssue(
9
+ overrides: Partial<HydratedIssue> = {},
10
+ ): HydratedIssue {
11
+ return {
12
+ id: "issue_abc123",
13
+ identifier: "ENG-42",
14
+ title: "Test Issue",
15
+ description: "Test description",
16
+ stateName: "Backlog",
17
+ stateType: "backlog",
18
+ labels: ["epic"],
19
+ parentIdentifier: "ENG-1",
20
+ priority: 3,
21
+ createdAt: new Date("2024-01-01T00:00:00Z"),
22
+ updatedAt: new Date("2024-01-01T00:00:00Z"),
23
+ _raw: {},
24
+ ...overrides,
25
+ };
26
+ }
27
+
28
+ describe("LinearAdapter - Epic CRUD", () => {
29
+ const mockConfig: LinearConfig = {
30
+ apiKey: "lin_api_test123",
31
+ teamId: "team_abc",
32
+ projectId: "proj_container",
33
+ defaultLabels: {
34
+ prd: "prd",
35
+ epic: "epic",
36
+ task: "task",
37
+ },
38
+ };
39
+
40
+ let adapter: LinearAdapter;
41
+
42
+ beforeEach(async () => {
43
+ const { LinearAdapter: LA } = await import("../linear/adapter.js");
44
+ adapter = new LA(mockConfig);
45
+ });
46
+
47
+ describe("createEpic", () => {
48
+ test("creates Linear issue with epic label under PRD parent", async () => {
49
+ const mockPrdIssue = createMockIssue({
50
+ id: "prd_issue_123",
51
+ identifier: "ENG-1",
52
+ title: "Test PRD",
53
+ labels: ["prd"],
54
+ });
55
+
56
+ const mockCreatedEpic = createMockIssue({
57
+ id: "issue_epic_123",
58
+ identifier: "ENG-42",
59
+ title: "User Authentication",
60
+ description: "Implement user auth",
61
+ stateName: "Backlog",
62
+ labels: ["epic"],
63
+ parentIdentifier: "ENG-1",
64
+ });
65
+
66
+ (adapter as any).fetchIssue = mock(async () => mockPrdIssue);
67
+ (adapter as any).getLabelId = mock(async () => "label_epic_123");
68
+ (adapter as any).getStateId = mock(async () => "state_todo_123");
69
+ (adapter as any).client = {
70
+ execute: mock(async (fn: () => Promise<any>) => fn()),
71
+ client: {
72
+ createIssue: mock(async () => ({ issue: { id: "issue_epic_123" } })),
73
+ },
74
+ };
75
+ (adapter as any).hydrateIssue = mock(async () => mockCreatedEpic);
76
+
77
+ const epic = await adapter.createEpic({
78
+ prdRef: "ENG-1",
79
+ title: "User Authentication",
80
+ description: "Implement user auth",
81
+ });
82
+
83
+ expect(epic.ref).toBe("ENG-42");
84
+ expect(epic.title).toBe("User Authentication");
85
+ expect(epic.description).toBe("Implement user auth");
86
+ expect(epic.status).toBe("PENDING");
87
+ expect(epic.prdId).toBe("ENG-1");
88
+ });
89
+
90
+ test("creates epic with acceptance criteria embedded in description", async () => {
91
+ const mockPrdIssue = createMockIssue({
92
+ id: "prd_issue_123",
93
+ identifier: "ENG-1",
94
+ labels: ["prd"],
95
+ });
96
+
97
+ const mockCreatedEpic = createMockIssue({
98
+ id: "issue_epic_124",
99
+ identifier: "ENG-43",
100
+ title: "API Development",
101
+ description:
102
+ "Build REST API\n\n## Acceptance Criteria\n\n- [ ] Endpoint returns 200\n- [ ] Response is JSON",
103
+ stateName: "Backlog",
104
+ labels: ["epic"],
105
+ parentIdentifier: "ENG-1",
106
+ });
107
+
108
+ (adapter as any).fetchIssue = mock(async () => mockPrdIssue);
109
+ (adapter as any).getLabelId = mock(async () => "label_epic_123");
110
+ (adapter as any).getStateId = mock(async () => "state_todo_123");
111
+ (adapter as any).client = {
112
+ execute: mock(async (fn: () => Promise<any>) => fn()),
113
+ client: {
114
+ createIssue: mock(async () => ({ issue: { id: "issue_epic_124" } })),
115
+ },
116
+ };
117
+ (adapter as any).hydrateIssue = mock(async () => mockCreatedEpic);
118
+
119
+ const epic = await adapter.createEpic({
120
+ prdRef: "ENG-1",
121
+ title: "API Development",
122
+ description: "Build REST API",
123
+ acceptanceCriteria: ["Endpoint returns 200", "Response is JSON"],
124
+ });
125
+
126
+ expect(epic.description).toContain("## Acceptance Criteria");
127
+ expect(epic.description).toContain("- [ ] Endpoint returns 200");
128
+ });
129
+
130
+ test("throws error if PRD not found", async () => {
131
+ (adapter as any).fetchIssue = mock(async () => null);
132
+
133
+ await expect(
134
+ adapter.createEpic({
135
+ prdRef: "INVALID-1",
136
+ title: "Test Epic",
137
+ }),
138
+ ).rejects.toThrow("PRD not found: INVALID-1");
139
+ });
140
+
141
+ test("throws error if PRD issue is not a PRD (no prd label)", async () => {
142
+ const mockNotPrdIssue = createMockIssue({
143
+ identifier: "ENG-1",
144
+ labels: ["epic"], // epic label, not prd
145
+ });
146
+
147
+ (adapter as any).fetchIssue = mock(async () => mockNotPrdIssue);
148
+
149
+ await expect(
150
+ adapter.createEpic({
151
+ prdRef: "ENG-1",
152
+ title: "Test Epic",
153
+ }),
154
+ ).rejects.toThrow("Issue ENG-1 is not a PRD");
155
+ });
156
+
157
+ test("throws error if epic label not found", async () => {
158
+ const mockPrdIssue = createMockIssue({
159
+ id: "prd_issue_123",
160
+ identifier: "ENG-1",
161
+ labels: ["prd"],
162
+ });
163
+
164
+ (adapter as any).fetchIssue = mock(async () => mockPrdIssue);
165
+ (adapter as any).getLabelId = mock(async () => {
166
+ throw new Error('Label "epic" not found in team');
167
+ });
168
+
169
+ await expect(
170
+ adapter.createEpic({
171
+ prdRef: "ENG-1",
172
+ title: "Test Epic",
173
+ }),
174
+ ).rejects.toThrow('Label "epic" not found in team');
175
+ });
176
+ });
177
+
178
+ describe("getEpic", () => {
179
+ test("retrieves epic by ref", async () => {
180
+ const mockEpic = createMockIssue({
181
+ id: "issue_epic_123",
182
+ identifier: "ENG-42",
183
+ title: "User Authentication",
184
+ description: "Implement user auth",
185
+ stateName: "Backlog",
186
+ labels: ["epic"],
187
+ parentIdentifier: "ENG-1",
188
+ });
189
+
190
+ (adapter as any).fetchIssue = mock(async () => mockEpic);
191
+
192
+ const epic = await adapter.getEpic("ENG-42");
193
+
194
+ expect(epic).not.toBeNull();
195
+ expect(epic?.ref).toBe("ENG-42");
196
+ expect(epic?.title).toBe("User Authentication");
197
+ expect(epic?.status).toBe("PENDING");
198
+ });
199
+
200
+ test("returns null if epic not found", async () => {
201
+ (adapter as any).fetchIssue = mock(async () => null);
202
+
203
+ const epic = await adapter.getEpic("INVALID-1");
204
+
205
+ expect(epic).toBeNull();
206
+ });
207
+
208
+ test("returns null if issue is not an epic", async () => {
209
+ const mockTaskIssue = createMockIssue({
210
+ identifier: "ENG-42",
211
+ labels: ["task"],
212
+ });
213
+
214
+ (adapter as any).fetchIssue = mock(async () => mockTaskIssue);
215
+
216
+ const epic = await adapter.getEpic("ENG-42");
217
+
218
+ expect(epic).toBeNull();
219
+ });
220
+ });
221
+
222
+ describe("updateEpic", () => {
223
+ test("updates epic title", async () => {
224
+ const mockEpic = createMockIssue({
225
+ identifier: "ENG-42",
226
+ title: "Old Title",
227
+ labels: ["epic"],
228
+ _raw: { update: mock(async () => ({})) },
229
+ });
230
+
231
+ const mockUpdatedEpic = createMockIssue({
232
+ identifier: "ENG-42",
233
+ title: "New Title",
234
+ labels: ["epic"],
235
+ parentIdentifier: "ENG-1",
236
+ });
237
+
238
+ // fetchIssue returns original first, then updated
239
+ let fetchCount = 0;
240
+ (adapter as any).fetchIssue = mock(async () => {
241
+ fetchCount++;
242
+ return fetchCount === 1 ? mockEpic : mockUpdatedEpic;
243
+ });
244
+ (adapter as any).client = {
245
+ execute: mock(async (fn: () => Promise<any>) => fn()),
246
+ };
247
+
248
+ const epic = await adapter.updateEpic("ENG-42", {
249
+ title: "New Title",
250
+ });
251
+
252
+ expect(epic.title).toBe("New Title");
253
+ });
254
+
255
+ test("updates epic status", async () => {
256
+ const mockEpic = createMockIssue({
257
+ identifier: "ENG-42",
258
+ stateName: "Backlog",
259
+ labels: ["epic"],
260
+ _raw: { update: mock(async () => ({})) },
261
+ });
262
+
263
+ const mockUpdatedEpic = createMockIssue({
264
+ identifier: "ENG-42",
265
+ stateName: "In Progress",
266
+ stateType: "started",
267
+ labels: ["epic"],
268
+ parentIdentifier: "ENG-1",
269
+ });
270
+
271
+ // fetchIssue returns original first, then updated
272
+ let fetchCount = 0;
273
+ (adapter as any).fetchIssue = mock(async () => {
274
+ fetchCount++;
275
+ return fetchCount === 1 ? mockEpic : mockUpdatedEpic;
276
+ });
277
+ (adapter as any).getStateId = mock(async () => "state_in_progress");
278
+ (adapter as any).client = {
279
+ execute: mock(async (fn: () => Promise<any>) => fn()),
280
+ };
281
+
282
+ const epic = await adapter.updateEpic("ENG-42", {
283
+ status: "IN_PROGRESS",
284
+ });
285
+
286
+ expect(epic.status).toBe("IN_PROGRESS");
287
+ });
288
+
289
+ test("throws error if epic not found", async () => {
290
+ (adapter as any).fetchIssue = mock(async () => null);
291
+
292
+ await expect(
293
+ adapter.updateEpic("INVALID-1", { title: "New Title" }),
294
+ ).rejects.toThrow("Epic not found: INVALID-1");
295
+ });
296
+ });
297
+
298
+ describe("listEpics", () => {
299
+ test("returns paginated list of epics filtered by PRD", async () => {
300
+ const mockPrdIssue = createMockIssue({
301
+ id: "prd_issue_123",
302
+ identifier: "ENG-1",
303
+ labels: ["prd"],
304
+ });
305
+
306
+ const mockEpics = [
307
+ createMockIssue({
308
+ id: "issue_epic_1",
309
+ identifier: "ENG-42",
310
+ title: "Epic 1",
311
+ stateName: "Backlog",
312
+ labels: ["epic"],
313
+ parentIdentifier: "ENG-1",
314
+ }),
315
+ createMockIssue({
316
+ id: "issue_epic_2",
317
+ identifier: "ENG-43",
318
+ title: "Epic 2",
319
+ stateName: "In Progress",
320
+ stateType: "started",
321
+ labels: ["epic"],
322
+ parentIdentifier: "ENG-1",
323
+ }),
324
+ ];
325
+
326
+ // fetchIssue called for PRD lookup
327
+ (adapter as any).fetchIssue = mock(async () => mockPrdIssue);
328
+ (adapter as any).fetchIssues = mock(async () => mockEpics);
329
+
330
+ const result = await adapter.listEpics(
331
+ { prdRef: "ENG-1" },
332
+ { limit: 10, offset: 0 },
333
+ );
334
+
335
+ expect(result.items.length).toBe(2);
336
+ expect(result.total).toBe(2);
337
+ expect(result.items[0].ref).toBe("ENG-42");
338
+ expect(result.items[1].ref).toBe("ENG-43");
339
+ expect(result.hasMore).toBe(false);
340
+ });
341
+
342
+ test("filters epics by status", async () => {
343
+ const mockEpics = [
344
+ createMockIssue({
345
+ id: "issue_epic_1",
346
+ identifier: "ENG-42",
347
+ title: "Epic 1",
348
+ stateName: "Backlog",
349
+ labels: ["epic"],
350
+ }),
351
+ ];
352
+
353
+ (adapter as any).fetchIssues = mock(async () => mockEpics);
354
+
355
+ const result = await adapter.listEpics(
356
+ { status: "PENDING" },
357
+ { limit: 10, offset: 0 },
358
+ );
359
+
360
+ expect(result.items.length).toBe(1);
361
+ expect(result.items[0].status).toBe("PENDING");
362
+ });
363
+
364
+ test("returns empty list when no epics match filters", async () => {
365
+ // When prdRef is provided, fetchIssue is called to look up the PRD
366
+ (adapter as any).fetchIssue = mock(async () =>
367
+ createMockIssue({
368
+ id: "prd_issue_123",
369
+ identifier: "ENG-1",
370
+ labels: ["prd"],
371
+ }),
372
+ );
373
+ (adapter as any).fetchIssues = mock(async () => []);
374
+
375
+ const result = await adapter.listEpics(
376
+ { prdRef: "ENG-1" },
377
+ { limit: 10, offset: 0 },
378
+ );
379
+
380
+ expect(result.items).toEqual([]);
381
+ expect(result.total).toBe(0);
382
+ expect(result.hasMore).toBe(false);
383
+ });
384
+
385
+ test("handles pagination correctly", async () => {
386
+ const mockEpics = Array.from({ length: 15 }, (_, i) =>
387
+ createMockIssue({
388
+ id: `issue_epic_${i}`,
389
+ identifier: `ENG-${i}`,
390
+ title: `Epic ${i}`,
391
+ labels: ["epic"],
392
+ }),
393
+ );
394
+
395
+ (adapter as any).fetchIssues = mock(async () => mockEpics);
396
+
397
+ const result = await adapter.listEpics({}, { limit: 10, offset: 0 });
398
+
399
+ expect(result.items.length).toBe(10);
400
+ expect(result.total).toBe(15);
401
+ expect(result.hasMore).toBe(true);
402
+ expect(result.offset).toBe(0);
403
+ expect(result.limit).toBe(10);
404
+ });
405
+
406
+ test("filters out non-epic issues", async () => {
407
+ const mockIssues = [
408
+ createMockIssue({
409
+ id: "issue_epic_1",
410
+ identifier: "ENG-42",
411
+ title: "Real Epic",
412
+ labels: ["epic"],
413
+ }),
414
+ createMockIssue({
415
+ id: "issue_task_1",
416
+ identifier: "ENG-43",
417
+ title: "This is a task",
418
+ labels: ["task"],
419
+ }),
420
+ ];
421
+
422
+ (adapter as any).fetchIssues = mock(async () => mockIssues);
423
+
424
+ const result = await adapter.listEpics({}, { limit: 10, offset: 0 });
425
+
426
+ expect(result.items.length).toBe(1);
427
+ expect(result.items[0].ref).toBe("ENG-42");
428
+ });
429
+ });
430
+
431
+ describe("deleteEpic", () => {
432
+ test("archives epic and returns cascade info", async () => {
433
+ const mockArchive = mock(async () => ({ success: true }));
434
+ const mockEpic = createMockIssue({
435
+ id: "issue_epic_123",
436
+ identifier: "ENG-42",
437
+ labels: ["epic"],
438
+ _raw: {
439
+ archive: mockArchive,
440
+ children: mock(async () => ({ nodes: [] })),
441
+ },
442
+ });
443
+
444
+ (adapter as any).fetchIssue = mock(async () => mockEpic);
445
+ (adapter as any).client = {
446
+ execute: mock(async (fn: () => Promise<any>) => fn()),
447
+ };
448
+
449
+ const result = await adapter.deleteEpic("ENG-42");
450
+
451
+ expect(result.deleted).toBe("ENG-42");
452
+ expect(result.cascade).toBeDefined();
453
+ expect(result.cascade.tasks).toBe(0);
454
+ expect(mockArchive).toHaveBeenCalled();
455
+ });
456
+
457
+ test("archives epic and its child tasks", async () => {
458
+ const mockTaskArchive = mock(async () => ({ success: true }));
459
+ const mockEpicArchive = mock(async () => ({ success: true }));
460
+
461
+ const mockEpic = createMockIssue({
462
+ id: "issue_epic_123",
463
+ identifier: "ENG-42",
464
+ labels: ["epic"],
465
+ _raw: {
466
+ archive: mockEpicArchive,
467
+ children: mock(async () => ({
468
+ nodes: [
469
+ { id: "task_1", archive: mockTaskArchive },
470
+ { id: "task_2", archive: mockTaskArchive },
471
+ ],
472
+ })),
473
+ },
474
+ });
475
+
476
+ (adapter as any).fetchIssue = mock(async () => mockEpic);
477
+ (adapter as any).client = {
478
+ execute: mock(async (fn: () => Promise<any>) => fn()),
479
+ };
480
+
481
+ const result = await adapter.deleteEpic("ENG-42");
482
+
483
+ expect(result.deleted).toBe("ENG-42");
484
+ expect(result.cascade.tasks).toBe(2);
485
+ expect(mockEpicArchive).toHaveBeenCalled();
486
+ });
487
+
488
+ test("throws error if epic not found", async () => {
489
+ (adapter as any).fetchIssue = mock(async () => null);
490
+
491
+ await expect(adapter.deleteEpic("INVALID-1")).rejects.toThrow(
492
+ "Epic not found: INVALID-1",
493
+ );
494
+ });
495
+ });
496
+ });