@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,450 @@
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: [],
19
+ parentIdentifier: undefined,
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 - getStats (Label-based)", () => {
29
+ const mockConfig: LinearConfig = {
30
+ apiKey: "lin_api_test123",
31
+ teamId: "team_123",
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("PRD stats (issue-based)", () => {
48
+ test("aggregates PRD counts by workflow state", async () => {
49
+ const mockIssues = [
50
+ createMockIssue({
51
+ id: "issue_1",
52
+ identifier: "ENG-1",
53
+ labels: ["prd"],
54
+ stateName: "Backlog",
55
+ stateType: "backlog",
56
+ }),
57
+ createMockIssue({
58
+ id: "issue_2",
59
+ identifier: "ENG-2",
60
+ labels: ["prd"],
61
+ stateName: "Backlog",
62
+ stateType: "backlog",
63
+ }),
64
+ createMockIssue({
65
+ id: "issue_3",
66
+ identifier: "ENG-3",
67
+ labels: ["prd"],
68
+ stateName: "In Progress",
69
+ stateType: "started",
70
+ }),
71
+ createMockIssue({
72
+ id: "issue_4",
73
+ identifier: "ENG-4",
74
+ labels: ["prd"],
75
+ stateName: "In Progress",
76
+ stateType: "started",
77
+ }),
78
+ createMockIssue({
79
+ id: "issue_5",
80
+ identifier: "ENG-5",
81
+ labels: ["prd"],
82
+ stateName: "Done",
83
+ stateType: "completed",
84
+ }),
85
+ createMockIssue({
86
+ id: "issue_6",
87
+ identifier: "ENG-6",
88
+ labels: ["prd"],
89
+ stateName: "Canceled",
90
+ stateType: "canceled",
91
+ }),
92
+ ];
93
+
94
+ (adapter as any).fetchIssues = mock(async () => mockIssues);
95
+
96
+ const stats = await adapter.getStats();
97
+
98
+ expect(stats.prds.total).toBe(6);
99
+ expect(stats.prds.draft).toBe(2); // Backlog
100
+ expect(stats.prds.approved).toBe(2); // In Progress
101
+ expect(stats.prds.completed).toBe(1); // Done
102
+ expect(stats.prds.archived).toBe(1); // Canceled
103
+ });
104
+
105
+ test("returns zero counts when no PRDs exist", async () => {
106
+ (adapter as any).fetchIssues = mock(async () => []);
107
+
108
+ const stats = await adapter.getStats();
109
+
110
+ expect(stats.prds.total).toBe(0);
111
+ expect(stats.prds.draft).toBe(0);
112
+ expect(stats.prds.approved).toBe(0);
113
+ expect(stats.prds.completed).toBe(0);
114
+ expect(stats.prds.archived).toBe(0);
115
+ });
116
+ });
117
+
118
+ describe("Epic stats (label-based)", () => {
119
+ test("aggregates epic counts by workflow state type", async () => {
120
+ const mockIssues = [
121
+ createMockIssue({
122
+ id: "issue_1",
123
+ identifier: "ENG-1",
124
+ labels: ["epic"],
125
+ stateType: "backlog",
126
+ }),
127
+ createMockIssue({
128
+ id: "issue_2",
129
+ identifier: "ENG-2",
130
+ labels: ["epic"],
131
+ stateType: "unstarted",
132
+ }),
133
+ createMockIssue({
134
+ id: "issue_3",
135
+ identifier: "ENG-3",
136
+ labels: ["epic"],
137
+ stateType: "started",
138
+ }),
139
+ createMockIssue({
140
+ id: "issue_4",
141
+ identifier: "ENG-4",
142
+ labels: ["epic"],
143
+ stateType: "started",
144
+ }),
145
+ createMockIssue({
146
+ id: "issue_5",
147
+ identifier: "ENG-5",
148
+ labels: ["epic"],
149
+ stateType: "completed",
150
+ }),
151
+ ];
152
+
153
+ (adapter as any).fetchIssues = mock(async () => mockIssues);
154
+
155
+ const stats = await adapter.getStats();
156
+
157
+ expect(stats.epics.total).toBe(5);
158
+ expect(stats.epics.pending).toBe(2); // backlog + unstarted
159
+ expect(stats.epics.inProgress).toBe(2); // started
160
+ expect(stats.epics.completed).toBe(1); // completed
161
+ });
162
+
163
+ test("returns zero counts when no epics exist", async () => {
164
+ const mockIssues = [
165
+ createMockIssue({
166
+ id: "issue_1",
167
+ identifier: "ENG-1",
168
+ labels: ["prd"],
169
+ stateType: "started",
170
+ }),
171
+ createMockIssue({
172
+ id: "issue_2",
173
+ identifier: "ENG-2",
174
+ labels: ["task"],
175
+ stateType: "started",
176
+ }),
177
+ ];
178
+
179
+ (adapter as any).fetchIssues = mock(async () => mockIssues);
180
+
181
+ const stats = await adapter.getStats();
182
+
183
+ expect(stats.epics.total).toBe(0);
184
+ expect(stats.epics.pending).toBe(0);
185
+ expect(stats.epics.inProgress).toBe(0);
186
+ expect(stats.epics.completed).toBe(0);
187
+ });
188
+ });
189
+
190
+ describe("Task stats (label-based)", () => {
191
+ test("aggregates task counts by workflow state type", async () => {
192
+ const mockIssues = [
193
+ createMockIssue({
194
+ id: "issue_1",
195
+ identifier: "ENG-1",
196
+ labels: ["task"],
197
+ stateType: "backlog",
198
+ }),
199
+ createMockIssue({
200
+ id: "issue_2",
201
+ identifier: "ENG-2",
202
+ labels: ["task"],
203
+ stateType: "unstarted",
204
+ }),
205
+ createMockIssue({
206
+ id: "issue_3",
207
+ identifier: "ENG-3",
208
+ labels: ["task"],
209
+ stateType: "started",
210
+ }),
211
+ createMockIssue({
212
+ id: "issue_4",
213
+ identifier: "ENG-4",
214
+ labels: ["task"],
215
+ stateType: "started",
216
+ }),
217
+ createMockIssue({
218
+ id: "issue_5",
219
+ identifier: "ENG-5",
220
+ labels: ["task"],
221
+ stateType: "started",
222
+ }),
223
+ createMockIssue({
224
+ id: "issue_6",
225
+ identifier: "ENG-6",
226
+ labels: ["task"],
227
+ stateType: "completed",
228
+ }),
229
+ ];
230
+
231
+ (adapter as any).fetchIssues = mock(async () => mockIssues);
232
+
233
+ const stats = await adapter.getStats();
234
+
235
+ expect(stats.tasks.total).toBe(6);
236
+ expect(stats.tasks.pending).toBe(2); // backlog + unstarted
237
+ expect(stats.tasks.inProgress).toBe(3); // started
238
+ expect(stats.tasks.completed).toBe(1); // completed
239
+ });
240
+
241
+ test("returns zero counts when no tasks exist", async () => {
242
+ const mockIssues = [
243
+ createMockIssue({
244
+ id: "issue_1",
245
+ identifier: "ENG-1",
246
+ labels: ["prd"],
247
+ stateType: "started",
248
+ }),
249
+ createMockIssue({
250
+ id: "issue_2",
251
+ identifier: "ENG-2",
252
+ labels: ["epic"],
253
+ stateType: "started",
254
+ }),
255
+ ];
256
+
257
+ (adapter as any).fetchIssues = mock(async () => mockIssues);
258
+
259
+ const stats = await adapter.getStats();
260
+
261
+ expect(stats.tasks.total).toBe(0);
262
+ expect(stats.tasks.pending).toBe(0);
263
+ expect(stats.tasks.inProgress).toBe(0);
264
+ expect(stats.tasks.completed).toBe(0);
265
+ });
266
+ });
267
+
268
+ describe("Combined stats", () => {
269
+ test("aggregates all entity types correctly", async () => {
270
+ const mockIssues = [
271
+ // PRDs
272
+ createMockIssue({
273
+ id: "prd_1",
274
+ identifier: "ENG-1",
275
+ labels: ["prd"],
276
+ stateName: "Backlog",
277
+ stateType: "backlog",
278
+ }),
279
+ createMockIssue({
280
+ id: "prd_2",
281
+ identifier: "ENG-2",
282
+ labels: ["prd"],
283
+ stateName: "In Progress",
284
+ stateType: "started",
285
+ }),
286
+ createMockIssue({
287
+ id: "prd_3",
288
+ identifier: "ENG-3",
289
+ labels: ["prd"],
290
+ stateName: "Done",
291
+ stateType: "completed",
292
+ }),
293
+ // Epics
294
+ createMockIssue({
295
+ id: "epic_1",
296
+ identifier: "ENG-4",
297
+ labels: ["epic"],
298
+ stateType: "backlog",
299
+ }),
300
+ createMockIssue({
301
+ id: "epic_2",
302
+ identifier: "ENG-5",
303
+ labels: ["epic"],
304
+ stateType: "started",
305
+ }),
306
+ // Tasks
307
+ createMockIssue({
308
+ id: "task_1",
309
+ identifier: "ENG-6",
310
+ labels: ["task"],
311
+ stateType: "backlog",
312
+ }),
313
+ createMockIssue({
314
+ id: "task_2",
315
+ identifier: "ENG-7",
316
+ labels: ["task"],
317
+ stateType: "started",
318
+ }),
319
+ createMockIssue({
320
+ id: "task_3",
321
+ identifier: "ENG-8",
322
+ labels: ["task"],
323
+ stateType: "completed",
324
+ }),
325
+ ];
326
+
327
+ (adapter as any).fetchIssues = mock(async () => mockIssues);
328
+
329
+ const stats = await adapter.getStats();
330
+
331
+ // PRDs
332
+ expect(stats.prds.total).toBe(3);
333
+ expect(stats.prds.draft).toBe(1);
334
+ expect(stats.prds.approved).toBe(1);
335
+ expect(stats.prds.completed).toBe(1);
336
+
337
+ // Epics
338
+ expect(stats.epics.total).toBe(2);
339
+ expect(stats.epics.pending).toBe(1);
340
+ expect(stats.epics.inProgress).toBe(1);
341
+
342
+ // Tasks
343
+ expect(stats.tasks.total).toBe(3);
344
+ expect(stats.tasks.pending).toBe(1);
345
+ expect(stats.tasks.inProgress).toBe(1);
346
+ expect(stats.tasks.completed).toBe(1);
347
+ });
348
+ });
349
+
350
+ describe("Edge cases", () => {
351
+ test("handles issues without state gracefully", async () => {
352
+ const mockIssues = [
353
+ createMockIssue({
354
+ id: "issue_1",
355
+ identifier: "ENG-1",
356
+ labels: ["epic"],
357
+ stateName: "Backlog", // Default fallback
358
+ stateType: undefined as any,
359
+ }),
360
+ ];
361
+
362
+ (adapter as any).fetchIssues = mock(async () => mockIssues);
363
+
364
+ const stats = await adapter.getStats();
365
+
366
+ // Issue without stateType should still be counted
367
+ expect(stats.epics.total).toBe(1);
368
+ });
369
+
370
+ test("handles unknown workflow states", async () => {
371
+ const mockIssues = [
372
+ createMockIssue({
373
+ id: "issue_1",
374
+ identifier: "ENG-1",
375
+ labels: ["prd"],
376
+ stateName: "Unknown State",
377
+ stateType: "custom" as any,
378
+ }),
379
+ createMockIssue({
380
+ id: "issue_2",
381
+ identifier: "ENG-2",
382
+ labels: ["prd"],
383
+ stateName: "Triage",
384
+ stateType: "triage" as any,
385
+ }),
386
+ ];
387
+
388
+ (adapter as any).fetchIssues = mock(async () => mockIssues);
389
+
390
+ const stats = await adapter.getStats();
391
+
392
+ // Unknown states should still count towards total but go to draft
393
+ expect(stats.prds.total).toBe(2);
394
+ expect(stats.prds.draft).toBe(2);
395
+ });
396
+
397
+ test("uses custom label names from config", async () => {
398
+ const customConfig: LinearConfig = {
399
+ apiKey: "lin_api_test123",
400
+ teamId: "team_123",
401
+ projectId: "proj_container",
402
+ defaultLabels: {
403
+ prd: "product-requirement",
404
+ epic: "feature",
405
+ task: "story",
406
+ },
407
+ };
408
+
409
+ const { LinearAdapter: LA } = await import("../linear/adapter.js");
410
+ const customAdapter = new LA(customConfig);
411
+
412
+ const mockIssues = [
413
+ createMockIssue({
414
+ id: "issue_1",
415
+ identifier: "ENG-1",
416
+ labels: ["product-requirement"],
417
+ stateName: "Backlog",
418
+ stateType: "backlog",
419
+ }),
420
+ createMockIssue({
421
+ id: "issue_2",
422
+ identifier: "ENG-2",
423
+ labels: ["feature"],
424
+ stateType: "started",
425
+ }),
426
+ createMockIssue({
427
+ id: "issue_3",
428
+ identifier: "ENG-3",
429
+ labels: ["story"],
430
+ stateType: "completed",
431
+ }),
432
+ // Standard labels should not match
433
+ createMockIssue({
434
+ id: "issue_4",
435
+ identifier: "ENG-4",
436
+ labels: ["prd"],
437
+ stateType: "started",
438
+ }),
439
+ ];
440
+
441
+ (customAdapter as any).fetchIssues = mock(async () => mockIssues);
442
+
443
+ const stats = await customAdapter.getStats();
444
+
445
+ expect(stats.prds.total).toBe(1);
446
+ expect(stats.epics.total).toBe(1);
447
+ expect(stats.tasks.total).toBe(1);
448
+ });
449
+ });
450
+ });