@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,534 @@
1
+ /**
2
+ * Task CRUD Tests for LinearAdapter
3
+ *
4
+ * Tests createTask, getTask, updateTask, listTasks, deleteTask operations.
5
+ * Uses the simplified mocking approach with fetchIssue/fetchIssues/hydrateIssue.
6
+ */
7
+
8
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
9
+ import type { HydratedIssue, LinearAdapter } from "../linear/adapter.js";
10
+ import type { LinearConfig } from "../linear/types.js";
11
+ import type { CreateTaskInput, UpdateTaskInput } from "../types.js";
12
+
13
+ /**
14
+ * Helper to create a mock HydratedIssue
15
+ */
16
+ function createMockIssue(
17
+ overrides: Partial<HydratedIssue> = {},
18
+ ): HydratedIssue {
19
+ return {
20
+ id: "issue_abc123",
21
+ identifier: "ENG-42",
22
+ title: "Test Issue",
23
+ description: "Test description",
24
+ stateName: "Backlog",
25
+ stateType: "backlog",
26
+ labels: ["task"],
27
+ parentIdentifier: "ENG-1",
28
+ priority: 3,
29
+ createdAt: new Date("2024-01-01T00:00:00Z"),
30
+ updatedAt: new Date("2024-01-01T00:00:00Z"),
31
+ _raw: {},
32
+ ...overrides,
33
+ };
34
+ }
35
+
36
+ describe("LinearAdapter - Task CRUD", () => {
37
+ const mockConfig: LinearConfig = {
38
+ apiKey: "lin_api_test123",
39
+ teamId: "TEAM-123",
40
+ projectId: "proj_container",
41
+ defaultLabels: {
42
+ prd: "prd",
43
+ epic: "epic",
44
+ task: "task",
45
+ },
46
+ };
47
+
48
+ let adapter: LinearAdapter;
49
+
50
+ beforeEach(async () => {
51
+ const { LinearAdapter: LA } = await import("../linear/adapter.js");
52
+ adapter = new LA(mockConfig);
53
+ });
54
+
55
+ describe("createTask", () => {
56
+ test("creates child Issue under epic with required fields", async () => {
57
+ const mockEpic = createMockIssue({
58
+ id: "epic_abc123",
59
+ identifier: "ENG-42",
60
+ title: "Auth Epic",
61
+ labels: ["epic"],
62
+ });
63
+
64
+ const mockCreatedTask = createMockIssue({
65
+ id: "task_xyz789",
66
+ identifier: "ENG-43",
67
+ title: "Implement login API",
68
+ stateName: "Backlog",
69
+ labels: ["task"],
70
+ parentIdentifier: "ENG-42",
71
+ priority: 3,
72
+ });
73
+
74
+ (adapter as any).fetchIssue = mock(async () => mockEpic);
75
+ (adapter as any).getLabelId = mock(async () => "label_task_123");
76
+ (adapter as any).getStateId = mock(async () => "state_todo_123");
77
+ (adapter as any).client = {
78
+ execute: mock(async (fn: () => Promise<any>) => fn()),
79
+ client: {
80
+ createIssue: mock(async () => ({ issue: { id: "task_xyz789" } })),
81
+ },
82
+ };
83
+ (adapter as any).hydrateIssue = mock(async () => mockCreatedTask);
84
+
85
+ const input: CreateTaskInput = {
86
+ epicRef: "ENG-42",
87
+ title: "Implement login API",
88
+ };
89
+
90
+ const result = await adapter.createTask(input);
91
+
92
+ expect(result.id).toBe("task_xyz789");
93
+ expect(result.epicId).toBe("ENG-42");
94
+ expect(result.ref).toBe("ENG-43");
95
+ expect(result.title).toBe("Implement login API");
96
+ expect(result.status).toBe("PENDING");
97
+ expect(result.priority).toBe("MEDIUM");
98
+ });
99
+
100
+ test("creates task with description and priority", async () => {
101
+ const mockEpic = createMockIssue({
102
+ id: "epic_abc123",
103
+ identifier: "ENG-42",
104
+ labels: ["epic"],
105
+ });
106
+
107
+ const mockCreatedTask = createMockIssue({
108
+ id: "task_xyz790",
109
+ identifier: "ENG-44",
110
+ title: "Implement logout API",
111
+ description: "Add logout endpoint",
112
+ stateName: "Backlog",
113
+ labels: ["task"],
114
+ parentIdentifier: "ENG-42",
115
+ priority: 2, // HIGH
116
+ });
117
+
118
+ (adapter as any).fetchIssue = mock(async () => mockEpic);
119
+ (adapter as any).getLabelId = mock(async () => "label_task_123");
120
+ (adapter as any).getStateId = mock(async () => "state_todo_123");
121
+ (adapter as any).client = {
122
+ execute: mock(async (fn: () => Promise<any>) => fn()),
123
+ client: {
124
+ createIssue: mock(async () => ({ issue: { id: "task_xyz790" } })),
125
+ },
126
+ };
127
+ (adapter as any).hydrateIssue = mock(async () => mockCreatedTask);
128
+
129
+ const input: CreateTaskInput = {
130
+ epicRef: "ENG-42",
131
+ title: "Implement logout API",
132
+ description: "Add logout endpoint",
133
+ priority: "HIGH",
134
+ };
135
+
136
+ const result = await adapter.createTask(input);
137
+
138
+ expect(result.description).toBe("Add logout endpoint");
139
+ expect(result.priority).toBe("HIGH");
140
+ });
141
+
142
+ test("creates task with acceptance criteria embedded in description", async () => {
143
+ const mockEpic = createMockIssue({
144
+ id: "epic_abc123",
145
+ identifier: "ENG-42",
146
+ labels: ["epic"],
147
+ });
148
+
149
+ const expectedDescription =
150
+ "## Acceptance Criteria\n\n- [ ] Email validation works\n- [ ] Password strength check";
151
+
152
+ const mockCreatedTask = createMockIssue({
153
+ id: "task_xyz791",
154
+ identifier: "ENG-45",
155
+ title: "Implement user registration",
156
+ description: expectedDescription,
157
+ labels: ["task"],
158
+ parentIdentifier: "ENG-42",
159
+ });
160
+
161
+ (adapter as any).fetchIssue = mock(async () => mockEpic);
162
+ (adapter as any).getLabelId = mock(async () => "label_task_123");
163
+ (adapter as any).getStateId = mock(async () => "state_todo_123");
164
+ (adapter as any).client = {
165
+ execute: mock(async (fn: () => Promise<any>) => fn()),
166
+ client: {
167
+ createIssue: mock(async () => ({ issue: { id: "task_xyz791" } })),
168
+ },
169
+ };
170
+ (adapter as any).hydrateIssue = mock(async () => mockCreatedTask);
171
+
172
+ const input: CreateTaskInput = {
173
+ epicRef: "ENG-42",
174
+ title: "Implement user registration",
175
+ acceptanceCriteria: [
176
+ "Email validation works",
177
+ "Password strength check",
178
+ ],
179
+ };
180
+
181
+ const result = await adapter.createTask(input);
182
+
183
+ expect(result.description).toContain("## Acceptance Criteria");
184
+ expect(result.description).toContain("Email validation works");
185
+ });
186
+
187
+ test("throws error if epic not found", async () => {
188
+ (adapter as any).fetchIssue = mock(async () => null);
189
+
190
+ const input: CreateTaskInput = {
191
+ epicRef: "ENG-999",
192
+ title: "Task for missing epic",
193
+ };
194
+
195
+ await expect(adapter.createTask(input)).rejects.toThrow(
196
+ "Epic not found: ENG-999",
197
+ );
198
+ });
199
+ });
200
+
201
+ describe("getTask", () => {
202
+ test("retrieves task by ref (identifier)", async () => {
203
+ const mockTask = createMockIssue({
204
+ id: "task_xyz789",
205
+ identifier: "ENG-43",
206
+ title: "Implement login API",
207
+ description: "Add authentication endpoint",
208
+ stateName: "In Progress",
209
+ stateType: "started",
210
+ labels: ["task"],
211
+ parentIdentifier: "ENG-42",
212
+ priority: 2,
213
+ });
214
+
215
+ (adapter as any).fetchIssue = mock(async () => mockTask);
216
+
217
+ const result = await adapter.getTask("ENG-43");
218
+
219
+ expect(result).not.toBeNull();
220
+ expect(result?.id).toBe("task_xyz789");
221
+ expect(result?.epicId).toBe("ENG-42");
222
+ expect(result?.ref).toBe("ENG-43");
223
+ expect(result?.title).toBe("Implement login API");
224
+ expect(result?.status).toBe("IN_PROGRESS");
225
+ expect(result?.priority).toBe("HIGH");
226
+ });
227
+
228
+ test("returns null if task not found", async () => {
229
+ (adapter as any).fetchIssue = mock(async () => null);
230
+
231
+ const result = await adapter.getTask("ENG-999");
232
+
233
+ expect(result).toBeNull();
234
+ });
235
+
236
+ test("returns task if issue has parentIdentifier (even without task label)", async () => {
237
+ const mockTask = createMockIssue({
238
+ id: "task_xyz789",
239
+ identifier: "ENG-43",
240
+ labels: [], // No labels
241
+ parentIdentifier: "ENG-42", // But has parent
242
+ });
243
+
244
+ (adapter as any).fetchIssue = mock(async () => mockTask);
245
+
246
+ const result = await adapter.getTask("ENG-43");
247
+
248
+ expect(result).not.toBeNull();
249
+ expect(result?.epicId).toBe("ENG-42");
250
+ });
251
+ });
252
+
253
+ describe("updateTask", () => {
254
+ test("updates task title", async () => {
255
+ const mockTask = createMockIssue({
256
+ id: "task_xyz789",
257
+ identifier: "ENG-43",
258
+ title: "Old Title",
259
+ labels: ["task"],
260
+ parentIdentifier: "ENG-42",
261
+ _raw: { update: mock(async () => ({})) },
262
+ });
263
+
264
+ const mockUpdatedTask = createMockIssue({
265
+ id: "task_xyz789",
266
+ identifier: "ENG-43",
267
+ title: "Updated login API",
268
+ labels: ["task"],
269
+ parentIdentifier: "ENG-42",
270
+ updatedAt: new Date("2024-01-04T00:00:00Z"),
271
+ });
272
+
273
+ // fetchIssue returns original first, then updated
274
+ let fetchCount = 0;
275
+ (adapter as any).fetchIssue = mock(async () => {
276
+ fetchCount++;
277
+ return fetchCount === 1 ? mockTask : mockUpdatedTask;
278
+ });
279
+ (adapter as any).client = {
280
+ execute: mock(async (fn: () => Promise<any>) => fn()),
281
+ };
282
+
283
+ const input: UpdateTaskInput = {
284
+ title: "Updated login API",
285
+ };
286
+
287
+ const result = await adapter.updateTask("ENG-43", input);
288
+
289
+ expect(result.title).toBe("Updated login API");
290
+ });
291
+
292
+ test("updates task status", async () => {
293
+ const mockTask = createMockIssue({
294
+ id: "task_xyz789",
295
+ identifier: "ENG-43",
296
+ stateName: "Backlog",
297
+ labels: ["task"],
298
+ parentIdentifier: "ENG-42",
299
+ _raw: { update: mock(async () => ({})) },
300
+ });
301
+
302
+ const mockUpdatedTask = createMockIssue({
303
+ id: "task_xyz789",
304
+ identifier: "ENG-43",
305
+ stateName: "In Progress",
306
+ stateType: "started",
307
+ labels: ["task"],
308
+ parentIdentifier: "ENG-42",
309
+ });
310
+
311
+ // fetchIssue returns original first, then updated
312
+ let fetchCount = 0;
313
+ (adapter as any).fetchIssue = mock(async () => {
314
+ fetchCount++;
315
+ return fetchCount === 1 ? mockTask : mockUpdatedTask;
316
+ });
317
+ (adapter as any).client = {
318
+ execute: mock(async (fn: () => Promise<any>) => fn()),
319
+ client: {
320
+ team: mock(async () => ({
321
+ states: mock(async () => ({
322
+ nodes: [
323
+ { id: "state_1", name: "Backlog" },
324
+ { id: "state_2", name: "In Progress" },
325
+ { id: "state_3", name: "Done" },
326
+ ],
327
+ })),
328
+ })),
329
+ },
330
+ };
331
+
332
+ const input: UpdateTaskInput = {
333
+ status: "IN_PROGRESS",
334
+ };
335
+
336
+ const result = await adapter.updateTask("ENG-43", input);
337
+
338
+ expect(result.status).toBe("IN_PROGRESS");
339
+ });
340
+
341
+ test("updates task priority", async () => {
342
+ const mockTask = createMockIssue({
343
+ id: "task_xyz789",
344
+ identifier: "ENG-43",
345
+ priority: 3,
346
+ labels: ["task"],
347
+ parentIdentifier: "ENG-42",
348
+ _raw: { update: mock(async () => ({})) },
349
+ });
350
+
351
+ const mockUpdatedTask = createMockIssue({
352
+ id: "task_xyz789",
353
+ identifier: "ENG-43",
354
+ priority: 2, // HIGH
355
+ labels: ["task"],
356
+ parentIdentifier: "ENG-42",
357
+ });
358
+
359
+ // fetchIssue returns original first, then updated
360
+ let fetchCount = 0;
361
+ (adapter as any).fetchIssue = mock(async () => {
362
+ fetchCount++;
363
+ return fetchCount === 1 ? mockTask : mockUpdatedTask;
364
+ });
365
+ (adapter as any).client = {
366
+ execute: mock(async (fn: () => Promise<any>) => fn()),
367
+ };
368
+
369
+ const input: UpdateTaskInput = {
370
+ priority: "HIGH",
371
+ };
372
+
373
+ const result = await adapter.updateTask("ENG-43", input);
374
+
375
+ expect(result.priority).toBe("HIGH");
376
+ });
377
+
378
+ test("throws error if task not found", async () => {
379
+ (adapter as any).fetchIssue = mock(async () => null);
380
+
381
+ const input: UpdateTaskInput = {
382
+ title: "Updated title",
383
+ };
384
+
385
+ await expect(adapter.updateTask("ENG-999", input)).rejects.toThrow(
386
+ "Task not found: ENG-999",
387
+ );
388
+ });
389
+ });
390
+
391
+ describe("listTasks", () => {
392
+ test("returns paginated tasks filtered by epicRef", async () => {
393
+ const mockEpic = createMockIssue({
394
+ id: "epic_abc123",
395
+ identifier: "ENG-42",
396
+ labels: ["epic"],
397
+ });
398
+
399
+ const mockTasks = [
400
+ createMockIssue({
401
+ id: "task_1",
402
+ identifier: "ENG-43",
403
+ title: "Task 1",
404
+ stateName: "Backlog",
405
+ labels: ["task"],
406
+ parentIdentifier: "ENG-42",
407
+ }),
408
+ createMockIssue({
409
+ id: "task_2",
410
+ identifier: "ENG-44",
411
+ title: "Task 2",
412
+ stateName: "In Progress",
413
+ stateType: "started",
414
+ labels: ["task"],
415
+ parentIdentifier: "ENG-42",
416
+ priority: 2,
417
+ }),
418
+ ];
419
+
420
+ (adapter as any).fetchIssue = mock(async () => mockEpic);
421
+ (adapter as any).client = {
422
+ execute: mock(async (fn: () => Promise<any>) => fn()),
423
+ client: {
424
+ issues: mock(async () => ({
425
+ nodes: mockTasks.map((t) => ({ ...t })),
426
+ pageInfo: { hasNextPage: false },
427
+ })),
428
+ },
429
+ };
430
+ (adapter as any).hydrateIssue = mock(async (issue: any) => {
431
+ return mockTasks.find((t) => t.id === issue.id) || mockTasks[0];
432
+ });
433
+
434
+ const result = await adapter.listTasks(
435
+ { epicRef: "ENG-42" },
436
+ { limit: 50, offset: 0 },
437
+ );
438
+
439
+ expect(result.items).toHaveLength(2);
440
+ expect(result.total).toBe(2);
441
+ expect(result.limit).toBe(50);
442
+ expect(result.offset).toBe(0);
443
+
444
+ expect(result.items[0].ref).toBe("ENG-43");
445
+ expect(result.items[0].epicId).toBe("ENG-42");
446
+ expect(result.items[1].ref).toBe("ENG-44");
447
+ });
448
+
449
+ test("returns empty result when no tasks found", async () => {
450
+ const mockEpic = createMockIssue({
451
+ id: "epic_abc123",
452
+ identifier: "ENG-42",
453
+ labels: ["epic"],
454
+ });
455
+
456
+ (adapter as any).fetchIssue = mock(async () => mockEpic);
457
+ (adapter as any).client = {
458
+ execute: mock(async (fn: () => Promise<any>) => fn()),
459
+ client: {
460
+ issues: mock(async () => ({
461
+ nodes: [],
462
+ pageInfo: { hasNextPage: false },
463
+ })),
464
+ },
465
+ };
466
+
467
+ const result = await adapter.listTasks({ epicRef: "ENG-42" });
468
+
469
+ expect(result.items).toHaveLength(0);
470
+ expect(result.total).toBe(0);
471
+ expect(result.hasMore).toBe(false);
472
+ });
473
+
474
+ test("throws error if epic not found", async () => {
475
+ (adapter as any).fetchIssue = mock(async () => null);
476
+
477
+ await expect(adapter.listTasks({ epicRef: "ENG-999" })).rejects.toThrow(
478
+ "Epic not found: ENG-999",
479
+ );
480
+ });
481
+
482
+ test("uses default pagination when not specified", async () => {
483
+ (adapter as any).client = {
484
+ execute: mock(async (fn: () => Promise<any>) => fn()),
485
+ client: {
486
+ issues: mock(async () => ({
487
+ nodes: [],
488
+ pageInfo: { hasNextPage: false },
489
+ })),
490
+ },
491
+ };
492
+
493
+ const result = await adapter.listTasks();
494
+
495
+ expect(result.limit).toBe(50);
496
+ expect(result.offset).toBe(0);
497
+ });
498
+ });
499
+
500
+ describe("deleteTask", () => {
501
+ test("archives task and returns deleted ref", async () => {
502
+ const mockArchive = mock(async () => ({ success: true }));
503
+ const mockTask = createMockIssue({
504
+ id: "task_xyz789",
505
+ identifier: "ENG-43",
506
+ labels: ["task"],
507
+ parentIdentifier: "ENG-42",
508
+ _raw: { archive: mockArchive },
509
+ });
510
+
511
+ (adapter as any).fetchIssue = mock(async () => mockTask);
512
+ (adapter as any).client = {
513
+ execute: mock(async (fn: () => Promise<any>) => fn()),
514
+ };
515
+
516
+ const result = await adapter.deleteTask("ENG-43");
517
+
518
+ expect(result.deleted).toBe("ENG-43");
519
+ expect(result.cascade.criteria).toBe(0);
520
+ expect(result.cascade.tasks).toBe(0);
521
+ expect(result.cascade.epics).toBe(0);
522
+ expect(result.cascade.dependencies).toBe(0);
523
+ expect(mockArchive).toHaveBeenCalled();
524
+ });
525
+
526
+ test("throws error if task not found", async () => {
527
+ (adapter as any).fetchIssue = mock(async () => null);
528
+
529
+ await expect(adapter.deleteTask("ENG-999")).rejects.toThrow(
530
+ "Task not found: ENG-999",
531
+ );
532
+ });
533
+ });
534
+ });