@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,306 @@
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
+ import type { Document } from "../types.js";
5
+
6
+ /**
7
+ * Helper to create a mock HydratedIssue
8
+ */
9
+ function createMockIssue(
10
+ overrides: Partial<HydratedIssue> = {},
11
+ ): HydratedIssue {
12
+ return {
13
+ id: "issue_abc123",
14
+ identifier: "ENG-42",
15
+ title: "Test Issue",
16
+ description: "Test description",
17
+ stateName: "Backlog",
18
+ stateType: "backlog",
19
+ labels: ["prd"],
20
+ parentIdentifier: undefined,
21
+ priority: 3,
22
+ createdAt: new Date("2024-01-01T00:00:00Z"),
23
+ updatedAt: new Date("2024-01-01T00:00:00Z"),
24
+ _raw: {},
25
+ ...overrides,
26
+ };
27
+ }
28
+
29
+ describe("LinearAdapter - Document Operations", () => {
30
+ const mockConfig: LinearConfig = {
31
+ apiKey: "lin_api_test123",
32
+ teamId: "team_123",
33
+ projectId: "proj_container",
34
+ defaultLabels: {
35
+ prd: "prd",
36
+ epic: "epic",
37
+ task: "task",
38
+ },
39
+ };
40
+
41
+ let adapter: LinearAdapter;
42
+
43
+ beforeEach(async () => {
44
+ const { LinearAdapter: LA } = await import("../linear/adapter.js");
45
+ adapter = new LA(mockConfig);
46
+ });
47
+
48
+ describe("saveDocument", () => {
49
+ test("creates attachment for PRD and returns document with URL", async () => {
50
+ const mockPrdIssue = createMockIssue({
51
+ id: "issue_prd_123",
52
+ identifier: "ENG-1",
53
+ title: "Test PRD",
54
+ labels: ["prd"],
55
+ _raw: {
56
+ attachments: mock(async () => ({
57
+ nodes: [],
58
+ })),
59
+ },
60
+ });
61
+
62
+ const mockAttachment = {
63
+ id: "att_123",
64
+ title: "prd.md",
65
+ url:
66
+ "data:text/markdown;base64," +
67
+ Buffer.from(
68
+ "# Product Requirements Document\n\nContent here...",
69
+ ).toString("base64"),
70
+ createdAt: "2024-01-01T00:00:00Z",
71
+ };
72
+
73
+ (adapter as any).fetchIssue = mock(async () => mockPrdIssue);
74
+ (adapter as any).client = {
75
+ execute: mock(async (fn: () => Promise<any>) => fn()),
76
+ client: {
77
+ attachmentCreate: mock(async () => ({
78
+ attachment: mockAttachment,
79
+ success: true,
80
+ })),
81
+ },
82
+ };
83
+
84
+ const doc: Document = {
85
+ prdRef: "ENG-1",
86
+ filename: "prd.md",
87
+ content: "# Product Requirements Document\n\nContent here...",
88
+ };
89
+
90
+ const savedDoc = await adapter.saveDocument(doc);
91
+
92
+ expect(savedDoc.prdRef).toBe("ENG-1");
93
+ expect(savedDoc.filename).toBe("prd.md");
94
+ expect(savedDoc.content).toBe(doc.content);
95
+ expect(savedDoc.url).toContain("data:text/markdown;base64,");
96
+ });
97
+
98
+ test("updates existing document if filename already exists", async () => {
99
+ const mockArchive = mock(async () => ({ success: true }));
100
+ const existingAttachment = {
101
+ id: "att_existing",
102
+ title: "prd.md",
103
+ url: "data:text/markdown;base64,b2xkIGNvbnRlbnQ=",
104
+ archive: mockArchive,
105
+ };
106
+
107
+ const mockPrdIssue = createMockIssue({
108
+ id: "issue_prd_123",
109
+ identifier: "ENG-1",
110
+ title: "Test PRD",
111
+ labels: ["prd"],
112
+ _raw: {
113
+ attachments: mock(async () => ({
114
+ nodes: [existingAttachment],
115
+ })),
116
+ },
117
+ });
118
+
119
+ const newAttachment = {
120
+ id: "att_new",
121
+ title: "prd.md",
122
+ url:
123
+ "data:text/markdown;base64," +
124
+ Buffer.from("# Updated Content").toString("base64"),
125
+ createdAt: "2024-01-02T00:00:00Z",
126
+ };
127
+
128
+ (adapter as any).fetchIssue = mock(async () => mockPrdIssue);
129
+ (adapter as any).client = {
130
+ execute: mock(async (fn: () => Promise<any>) => fn()),
131
+ client: {
132
+ attachmentCreate: mock(async () => ({
133
+ attachment: newAttachment,
134
+ success: true,
135
+ })),
136
+ },
137
+ };
138
+
139
+ const doc: Document = {
140
+ prdRef: "ENG-1",
141
+ filename: "prd.md",
142
+ content: "# Updated Content",
143
+ };
144
+
145
+ const savedDoc = await adapter.saveDocument(doc);
146
+
147
+ expect(savedDoc.url).toContain("data:text/markdown;base64,");
148
+ expect(mockArchive).toHaveBeenCalled();
149
+ });
150
+
151
+ test("throws error when PRD does not exist", async () => {
152
+ (adapter as any).fetchIssue = mock(async () => null);
153
+
154
+ const doc: Document = {
155
+ prdRef: "ENG-999",
156
+ filename: "prd.md",
157
+ content: "Content",
158
+ };
159
+
160
+ await expect(adapter.saveDocument(doc)).rejects.toThrow(
161
+ "PRD ENG-999 not found",
162
+ );
163
+ });
164
+ });
165
+
166
+ describe("getDocuments", () => {
167
+ test("returns all documents for a PRD", async () => {
168
+ const mockPrdIssue = createMockIssue({
169
+ id: "issue_prd_123",
170
+ identifier: "ENG-1",
171
+ title: "Test PRD",
172
+ labels: ["prd"],
173
+ _raw: {
174
+ attachments: mock(async () => ({
175
+ nodes: [
176
+ {
177
+ id: "att_1",
178
+ title: "prd.md",
179
+ url:
180
+ "data:text/markdown;base64," +
181
+ Buffer.from("# PRD Content").toString("base64"),
182
+ },
183
+ {
184
+ id: "att_2",
185
+ title: "design.md",
186
+ url:
187
+ "data:text/markdown;base64," +
188
+ Buffer.from("# Design Doc").toString("base64"),
189
+ },
190
+ ],
191
+ })),
192
+ },
193
+ });
194
+
195
+ (adapter as any).fetchIssue = mock(async () => mockPrdIssue);
196
+ (adapter as any).client = {
197
+ execute: mock(async (fn: () => Promise<any>) => fn()),
198
+ };
199
+
200
+ const documents = await adapter.getDocuments("ENG-1");
201
+
202
+ expect(documents.length).toBe(2);
203
+ expect(documents[0].prdRef).toBe("ENG-1");
204
+ expect(documents[0].filename).toBe("prd.md");
205
+ expect(documents[0].content).toBe("# PRD Content");
206
+ expect(documents[0].url).toContain("data:text/markdown;base64,");
207
+ expect(documents[1].filename).toBe("design.md");
208
+ expect(documents[1].content).toBe("# Design Doc");
209
+ });
210
+
211
+ test("returns empty array when PRD has no documents", async () => {
212
+ const mockPrdIssue = createMockIssue({
213
+ id: "issue_prd_123",
214
+ identifier: "ENG-1",
215
+ title: "Test PRD",
216
+ labels: ["prd"],
217
+ _raw: {
218
+ attachments: mock(async () => ({
219
+ nodes: [],
220
+ })),
221
+ },
222
+ });
223
+
224
+ (adapter as any).fetchIssue = mock(async () => mockPrdIssue);
225
+ (adapter as any).client = {
226
+ execute: mock(async (fn: () => Promise<any>) => fn()),
227
+ };
228
+
229
+ const documents = await adapter.getDocuments("ENG-1");
230
+
231
+ expect(documents.length).toBe(0);
232
+ });
233
+
234
+ test("throws error when PRD does not exist", async () => {
235
+ (adapter as any).fetchIssue = mock(async () => null);
236
+
237
+ await expect(adapter.getDocuments("ENG-999")).rejects.toThrow(
238
+ "PRD ENG-999 not found",
239
+ );
240
+ });
241
+ });
242
+
243
+ describe("deleteDocument", () => {
244
+ test("archives attachment matching filename", async () => {
245
+ const mockArchive = mock(async () => ({ success: true }));
246
+ const mockAttachment = {
247
+ id: "att_123",
248
+ title: "prd.md",
249
+ url: "data:text/markdown;base64,Y29udGVudA==",
250
+ archive: mockArchive,
251
+ };
252
+
253
+ const mockPrdIssue = createMockIssue({
254
+ id: "issue_prd_123",
255
+ identifier: "ENG-1",
256
+ title: "Test PRD",
257
+ labels: ["prd"],
258
+ _raw: {
259
+ attachments: mock(async () => ({
260
+ nodes: [mockAttachment],
261
+ })),
262
+ },
263
+ });
264
+
265
+ (adapter as any).fetchIssue = mock(async () => mockPrdIssue);
266
+ (adapter as any).client = {
267
+ execute: mock(async (fn: () => Promise<any>) => fn()),
268
+ };
269
+
270
+ await adapter.deleteDocument("ENG-1", "prd.md");
271
+
272
+ expect(mockArchive).toHaveBeenCalled();
273
+ });
274
+
275
+ test("throws error when document not found", async () => {
276
+ const mockPrdIssue = createMockIssue({
277
+ id: "issue_prd_123",
278
+ identifier: "ENG-1",
279
+ title: "Test PRD",
280
+ labels: ["prd"],
281
+ _raw: {
282
+ attachments: mock(async () => ({
283
+ nodes: [],
284
+ })),
285
+ },
286
+ });
287
+
288
+ (adapter as any).fetchIssue = mock(async () => mockPrdIssue);
289
+ (adapter as any).client = {
290
+ execute: mock(async (fn: () => Promise<any>) => fn()),
291
+ };
292
+
293
+ await expect(
294
+ adapter.deleteDocument("ENG-1", "nonexistent.md"),
295
+ ).rejects.toThrow("Document nonexistent.md not found");
296
+ });
297
+
298
+ test("throws error when PRD does not exist", async () => {
299
+ (adapter as any).fetchIssue = mock(async () => null);
300
+
301
+ await expect(adapter.deleteDocument("ENG-999", "prd.md")).rejects.toThrow(
302
+ "PRD ENG-999 not found",
303
+ );
304
+ });
305
+ });
306
+ });
@@ -0,0 +1,91 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+ import type { HydratedIssue } from "../linear/adapter.js";
3
+ import type { LinearConfig } from "../linear/index.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", () => {
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
+ describe("Instantiation", () => {
41
+ test("can be instantiated with valid config", async () => {
42
+ const { LinearAdapter: LA } = await import("../linear/adapter.js");
43
+ const adapter = new LA(mockConfig);
44
+ expect(adapter).toBeInstanceOf(LA);
45
+ });
46
+ });
47
+
48
+ describe("addCriterion", () => {
49
+ test("adds criterion to epic description", async () => {
50
+ const { LinearAdapter: LA } = await import("../linear/adapter.js");
51
+ const adapter = new LA(mockConfig);
52
+
53
+ const mockEpic = createMockIssue({
54
+ id: "issue_epic_123",
55
+ identifier: "ENG-42",
56
+ title: "Test Epic",
57
+ description: "Epic description",
58
+ labels: ["epic"],
59
+ parentIdentifier: "ENG-1",
60
+ _raw: {
61
+ update: mock(async () => ({})),
62
+ },
63
+ });
64
+
65
+ (adapter as any).fetchIssue = mock(async () => mockEpic);
66
+ (adapter as any).client = {
67
+ execute: mock(async (fn: () => Promise<any>) => fn()),
68
+ };
69
+
70
+ const criterion = await adapter.addCriterion({
71
+ parentRef: "ENG-42",
72
+ criteria: "User can login",
73
+ });
74
+
75
+ expect(criterion.parentId).toBe("ENG-42");
76
+ expect(criterion.criteria).toBe("User can login");
77
+ expect(criterion.isMet).toBe(false);
78
+ });
79
+
80
+ test("throws error if parent not found", async () => {
81
+ const { LinearAdapter: LA } = await import("../linear/adapter.js");
82
+ const adapter = new LA(mockConfig);
83
+
84
+ (adapter as any).fetchIssue = mock(async () => null);
85
+
86
+ await expect(
87
+ adapter.addCriterion({ parentRef: "ENG-999", criteria: "test" }),
88
+ ).rejects.toThrow("Entity not found: ENG-999");
89
+ });
90
+ });
91
+ });