@cliangdev/flux-plugin 0.0.0-dev.cbdf207 → 0.0.0-dev.df3e9bb

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 (86) hide show
  1. package/README.md +8 -4
  2. package/bin/install.cjs +150 -16
  3. package/package.json +7 -11
  4. package/src/__tests__/version.test.ts +37 -0
  5. package/src/adapters/local/.gitkeep +0 -0
  6. package/src/server/__tests__/config.test.ts +163 -0
  7. package/src/server/adapters/__tests__/a-client-linear.test.ts +197 -0
  8. package/src/server/adapters/__tests__/adapter-factory.test.ts +230 -0
  9. package/src/server/adapters/__tests__/dependency-ops.test.ts +395 -0
  10. package/src/server/adapters/__tests__/document-ops.test.ts +306 -0
  11. package/src/server/adapters/__tests__/linear-adapter.test.ts +91 -0
  12. package/src/server/adapters/__tests__/linear-config.test.ts +425 -0
  13. package/src/server/adapters/__tests__/linear-criteria-parser.test.ts +287 -0
  14. package/src/server/adapters/__tests__/linear-description-test.ts +238 -0
  15. package/src/server/adapters/__tests__/linear-epic-crud.test.ts +496 -0
  16. package/src/server/adapters/__tests__/linear-mappers-description.test.ts +276 -0
  17. package/src/server/adapters/__tests__/linear-mappers-epic.test.ts +294 -0
  18. package/src/server/adapters/__tests__/linear-mappers-prd.test.ts +300 -0
  19. package/src/server/adapters/__tests__/linear-mappers-task.test.ts +197 -0
  20. package/src/server/adapters/__tests__/linear-prd-crud.test.ts +620 -0
  21. package/src/server/adapters/__tests__/linear-stats.test.ts +450 -0
  22. package/src/server/adapters/__tests__/linear-task-crud.test.ts +534 -0
  23. package/src/server/adapters/__tests__/linear-types.test.ts +243 -0
  24. package/src/server/adapters/__tests__/status-ops.test.ts +441 -0
  25. package/src/server/adapters/factory.ts +90 -0
  26. package/src/server/adapters/index.ts +9 -0
  27. package/src/server/adapters/linear/adapter.ts +1136 -0
  28. package/src/server/adapters/linear/client.ts +169 -0
  29. package/src/server/adapters/linear/config.ts +152 -0
  30. package/src/server/adapters/linear/helpers/criteria-parser.ts +197 -0
  31. package/src/server/adapters/linear/helpers/index.ts +7 -0
  32. package/src/server/adapters/linear/index.ts +16 -0
  33. package/src/server/adapters/linear/mappers/description.ts +136 -0
  34. package/src/server/adapters/linear/mappers/epic.ts +81 -0
  35. package/src/server/adapters/linear/mappers/index.ts +27 -0
  36. package/src/server/adapters/linear/mappers/prd.ts +178 -0
  37. package/src/server/adapters/linear/mappers/task.ts +82 -0
  38. package/src/server/adapters/linear/types.ts +264 -0
  39. package/src/server/adapters/local-adapter.ts +968 -0
  40. package/src/server/adapters/types.ts +293 -0
  41. package/src/server/config.ts +73 -0
  42. package/src/server/db/__tests__/queries.test.ts +472 -0
  43. package/src/server/db/ids.ts +17 -0
  44. package/src/server/db/index.ts +69 -0
  45. package/src/server/db/queries.ts +142 -0
  46. package/src/server/db/refs.ts +60 -0
  47. package/src/server/db/schema.ts +88 -0
  48. package/src/server/db/sqlite.ts +10 -0
  49. package/src/server/index.ts +83 -0
  50. package/src/server/tools/__tests__/crud.test.ts +301 -0
  51. package/src/server/tools/__tests__/get-version.test.ts +27 -0
  52. package/src/server/tools/__tests__/mcp-interface.test.ts +388 -0
  53. package/src/server/tools/__tests__/query.test.ts +353 -0
  54. package/src/server/tools/__tests__/z-configure-linear.test.ts +511 -0
  55. package/src/server/tools/__tests__/z-get-linear-url.test.ts +108 -0
  56. package/src/server/tools/configure-linear.ts +373 -0
  57. package/src/server/tools/create-epic.ts +35 -0
  58. package/src/server/tools/create-prd.ts +31 -0
  59. package/src/server/tools/create-task.ts +38 -0
  60. package/src/server/tools/criteria.ts +50 -0
  61. package/src/server/tools/delete-entity.ts +76 -0
  62. package/src/server/tools/dependencies.ts +55 -0
  63. package/src/server/tools/get-entity.ts +238 -0
  64. package/src/server/tools/get-linear-url.ts +28 -0
  65. package/src/server/tools/get-project-context.ts +33 -0
  66. package/src/server/tools/get-stats.ts +52 -0
  67. package/src/server/tools/get-version.ts +20 -0
  68. package/src/server/tools/index.ts +114 -0
  69. package/src/server/tools/init-project.ts +108 -0
  70. package/src/server/tools/query-entities.ts +167 -0
  71. package/src/server/tools/render-status.ts +201 -0
  72. package/src/server/tools/update-entity.ts +140 -0
  73. package/src/server/tools/update-status.ts +166 -0
  74. package/src/server/utils/__tests__/mcp-response.test.ts +331 -0
  75. package/src/server/utils/logger.ts +9 -0
  76. package/src/server/utils/mcp-response.ts +254 -0
  77. package/src/server/utils/status-transitions.ts +160 -0
  78. package/src/status-line/__tests__/status-line.test.ts +215 -0
  79. package/src/status-line/index.ts +147 -0
  80. package/src/utils/__tests__/chalk-import.test.ts +32 -0
  81. package/src/utils/__tests__/display.test.ts +97 -0
  82. package/src/utils/__tests__/status-renderer.test.ts +310 -0
  83. package/src/utils/display.ts +62 -0
  84. package/src/utils/status-renderer.ts +188 -0
  85. package/src/version.ts +5 -0
  86. package/dist/server/index.js +0 -87063
@@ -0,0 +1,353 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ // Set up test environment BEFORE any imports
6
+ const TEST_DIR = `/tmp/flux-test-query-${Date.now()}`;
7
+ const FLUX_DIR = join(TEST_DIR, ".flux");
8
+ process.env.FLUX_PROJECT_ROOT = TEST_DIR;
9
+
10
+ // Now import modules
11
+ import { clearAdapterCache } from "../../adapters/index.js";
12
+ import { config } from "../../config.js";
13
+ import { closeDb, initDb } from "../../db/index.js";
14
+ import { createEpicTool } from "../create-epic.js";
15
+ import { createPrdTool } from "../create-prd.js";
16
+ import { createTaskTool } from "../create-task.js";
17
+ import { addDependencyTool } from "../dependencies.js";
18
+ import { getEntityTool } from "../get-entity.js";
19
+ import { getProjectContextTool } from "../get-project-context.js";
20
+ import { getStatsTool } from "../get-stats.js";
21
+ import { initProjectTool } from "../init-project.js";
22
+ import { queryEntitiesTool } from "../query-entities.js";
23
+
24
+ describe("Query MCP Tools", () => {
25
+ beforeEach(() => {
26
+ config.clearCache();
27
+ clearAdapterCache();
28
+ process.env.FLUX_PROJECT_ROOT = TEST_DIR;
29
+
30
+ mkdirSync(FLUX_DIR, { recursive: true });
31
+ writeFileSync(
32
+ join(FLUX_DIR, "project.json"),
33
+ JSON.stringify({ name: "test-project", ref_prefix: "TEST" }),
34
+ );
35
+ initDb();
36
+ });
37
+
38
+ afterEach(() => {
39
+ closeDb();
40
+ clearAdapterCache();
41
+ config.clearCache();
42
+ if (existsSync(TEST_DIR)) {
43
+ rmSync(TEST_DIR, { recursive: true, force: true });
44
+ }
45
+ });
46
+
47
+ describe("get_entity", () => {
48
+ test("gets PRD by ref", async () => {
49
+ const prd = (await createPrdTool.handler({ title: "Test PRD" })) as any;
50
+ const result = (await getEntityTool.handler({ ref: prd.ref })) as any;
51
+
52
+ expect(result.id).toBe(prd.id);
53
+ expect(result.title).toBe("Test PRD");
54
+ expect(result.ref).toBe(prd.ref);
55
+ });
56
+
57
+ test("gets epic with tasks include", async () => {
58
+ const prd = (await createPrdTool.handler({ title: "Test PRD" })) as any;
59
+ const epic = (await createEpicTool.handler({
60
+ prd_ref: prd.ref,
61
+ title: "Test Epic",
62
+ })) as any;
63
+ await createTaskTool.handler({ epic_ref: epic.ref, title: "Task 1" });
64
+ await createTaskTool.handler({ epic_ref: epic.ref, title: "Task 2" });
65
+
66
+ const result = (await getEntityTool.handler({
67
+ ref: epic.ref,
68
+ include: ["tasks"],
69
+ })) as any;
70
+
71
+ expect(result.tasks).toBeDefined();
72
+ expect(result.tasks.length).toBe(2);
73
+ });
74
+
75
+ test("gets entity with criteria include", async () => {
76
+ const prd = (await createPrdTool.handler({ title: "Test PRD" })) as any;
77
+ const epic = (await createEpicTool.handler({
78
+ prd_ref: prd.ref,
79
+ title: "Test Epic",
80
+ acceptance_criteria: ["Criterion 1", "Criterion 2"],
81
+ })) as any;
82
+
83
+ const result = (await getEntityTool.handler({
84
+ ref: epic.ref,
85
+ include: ["criteria"],
86
+ })) as any;
87
+
88
+ expect(result.criteria).toBeDefined();
89
+ expect(result.criteria.length).toBe(2);
90
+ });
91
+
92
+ test("gets entity with dependencies include", async () => {
93
+ const prd = (await createPrdTool.handler({ title: "Test PRD" })) as any;
94
+ const epic1 = (await createEpicTool.handler({
95
+ prd_ref: prd.ref,
96
+ title: "Epic 1",
97
+ })) as any;
98
+ const epic2 = (await createEpicTool.handler({
99
+ prd_ref: prd.ref,
100
+ title: "Epic 2",
101
+ })) as any;
102
+ await addDependencyTool.handler({
103
+ ref: epic2.ref,
104
+ depends_on_ref: epic1.ref,
105
+ });
106
+
107
+ const result = (await getEntityTool.handler({
108
+ ref: epic2.ref,
109
+ include: ["dependencies"],
110
+ })) as any;
111
+
112
+ expect(result.dependencies).toBeDefined();
113
+ expect(result.dependencies.length).toBe(1);
114
+ // Dependencies now return refs directly, not objects
115
+ expect(result.dependencies[0]).toBe(epic1.ref);
116
+ });
117
+
118
+ test("throws error for invalid ref", async () => {
119
+ await expect(
120
+ getEntityTool.handler({ ref: "INVALID-P999" }),
121
+ ).rejects.toThrow("Entity not found");
122
+ });
123
+ });
124
+
125
+ describe("query_entities", () => {
126
+ test("queries all PRDs", async () => {
127
+ await createPrdTool.handler({ title: "PRD 1" });
128
+ await createPrdTool.handler({ title: "PRD 2" });
129
+
130
+ const result = (await queryEntitiesTool.handler({
131
+ type: "prd",
132
+ })) as any;
133
+ expect(result.items.length).toBe(2);
134
+ expect(result.total).toBe(2);
135
+ expect(result.has_more).toBe(false);
136
+ });
137
+
138
+ test("filters PRDs by status", async () => {
139
+ const prd1 = (await createPrdTool.handler({ title: "PRD 1" })) as any;
140
+ await createPrdTool.handler({ title: "PRD 2" });
141
+
142
+ // Update first PRD to APPROVED
143
+ const { updateEntityTool } = await import("../update-entity.js");
144
+ await updateEntityTool.handler({
145
+ ref: prd1.ref,
146
+ fields: { status: "APPROVED" },
147
+ });
148
+
149
+ const result = (await queryEntitiesTool.handler({
150
+ type: "prd",
151
+ status: "DRAFT",
152
+ })) as any;
153
+ expect(result.items.length).toBe(1);
154
+ expect(result.total).toBe(1);
155
+ });
156
+
157
+ test("queries epics by PRD ref", async () => {
158
+ const prd1 = (await createPrdTool.handler({ title: "PRD 1" })) as any;
159
+ const prd2 = (await createPrdTool.handler({ title: "PRD 2" })) as any;
160
+ await createEpicTool.handler({ prd_ref: prd1.ref, title: "Epic 1" });
161
+ await createEpicTool.handler({ prd_ref: prd1.ref, title: "Epic 2" });
162
+ await createEpicTool.handler({ prd_ref: prd2.ref, title: "Epic 3" });
163
+
164
+ const result = (await queryEntitiesTool.handler({
165
+ type: "epic",
166
+ prd_ref: prd1.ref,
167
+ })) as any;
168
+ expect(result.items.length).toBe(2);
169
+ expect(result.total).toBe(2);
170
+ });
171
+
172
+ test("queries tasks by epic ref", async () => {
173
+ const prd = (await createPrdTool.handler({ title: "Test PRD" })) as any;
174
+ const epic1 = (await createEpicTool.handler({
175
+ prd_ref: prd.ref,
176
+ title: "Epic 1",
177
+ })) as any;
178
+ const epic2 = (await createEpicTool.handler({
179
+ prd_ref: prd.ref,
180
+ title: "Epic 2",
181
+ })) as any;
182
+ await createTaskTool.handler({ epic_ref: epic1.ref, title: "Task 1" });
183
+ await createTaskTool.handler({ epic_ref: epic1.ref, title: "Task 2" });
184
+ await createTaskTool.handler({ epic_ref: epic2.ref, title: "Task 3" });
185
+
186
+ const result = (await queryEntitiesTool.handler({
187
+ type: "task",
188
+ epic_ref: epic1.ref,
189
+ })) as any;
190
+ expect(result.items.length).toBe(2);
191
+ expect(result.total).toBe(2);
192
+ });
193
+
194
+ test("includes criteria counts with entities", async () => {
195
+ const prd = (await createPrdTool.handler({ title: "Test PRD" })) as any;
196
+ const _epic = (await createEpicTool.handler({
197
+ prd_ref: prd.ref,
198
+ title: "Test Epic",
199
+ acceptance_criteria: ["Criterion 1"],
200
+ })) as any;
201
+
202
+ const result = (await queryEntitiesTool.handler({
203
+ type: "epic",
204
+ include: ["criteria"],
205
+ })) as any;
206
+
207
+ // Now returns counts instead of full criteria array
208
+ expect(result.items[0].criteria_count).toBe(1);
209
+ expect(result.items[0].criteria_met).toBe(0);
210
+ });
211
+
212
+ test("supports pagination with limit and offset", async () => {
213
+ const prd = (await createPrdTool.handler({ title: "Test PRD" })) as any;
214
+ for (let i = 1; i <= 5; i++) {
215
+ await createEpicTool.handler({ prd_ref: prd.ref, title: `Epic ${i}` });
216
+ }
217
+
218
+ const page1 = (await queryEntitiesTool.handler({
219
+ type: "epic",
220
+ limit: 2,
221
+ offset: 0,
222
+ })) as any;
223
+ expect(page1.items.length).toBe(2);
224
+ expect(page1.total).toBe(5);
225
+ expect(page1.has_more).toBe(true);
226
+
227
+ const page2 = (await queryEntitiesTool.handler({
228
+ type: "epic",
229
+ limit: 2,
230
+ offset: 2,
231
+ })) as any;
232
+ expect(page2.items.length).toBe(2);
233
+ expect(page2.has_more).toBe(true);
234
+
235
+ const page3 = (await queryEntitiesTool.handler({
236
+ type: "epic",
237
+ limit: 2,
238
+ offset: 4,
239
+ })) as any;
240
+ expect(page3.items.length).toBe(1);
241
+ expect(page3.has_more).toBe(false);
242
+ });
243
+ });
244
+
245
+ describe("get_project_context", () => {
246
+ test("returns project context when initialized", async () => {
247
+ const result = (await getProjectContextTool.handler({})) as any;
248
+
249
+ expect(result.initialized).toBe(true);
250
+ expect(result.name).toBe("test-project");
251
+ expect(result.ref_prefix).toBe("TEST");
252
+ });
253
+
254
+ test("returns initialized false when no project", async () => {
255
+ // Remove the project.json
256
+ rmSync(join(FLUX_DIR, "project.json"));
257
+
258
+ const result = (await getProjectContextTool.handler({})) as any;
259
+ expect(result.initialized).toBe(false);
260
+ });
261
+ });
262
+
263
+ describe("get_stats", () => {
264
+ test("returns zeroes for empty project", async () => {
265
+ const result = (await getStatsTool.handler({})) as any;
266
+
267
+ expect(result.prds.total).toBe(0);
268
+ expect(result.epics.total).toBe(0);
269
+ expect(result.tasks.total).toBe(0);
270
+ });
271
+
272
+ test("counts entities by status", async () => {
273
+ const prd = (await createPrdTool.handler({ title: "PRD 1" })) as any;
274
+ const epic = (await createEpicTool.handler({
275
+ prd_ref: prd.ref,
276
+ title: "Epic 1",
277
+ })) as any;
278
+ await createTaskTool.handler({ epic_ref: epic.ref, title: "Task 1" });
279
+ await createTaskTool.handler({ epic_ref: epic.ref, title: "Task 2" });
280
+
281
+ const result = (await getStatsTool.handler({})) as any;
282
+
283
+ expect(result.prds.total).toBe(1);
284
+ expect(result.prds.draft).toBe(1);
285
+ expect(result.epics.total).toBe(1);
286
+ expect(result.epics.pending).toBe(1);
287
+ expect(result.tasks.total).toBe(2);
288
+ expect(result.tasks.pending).toBe(2);
289
+ });
290
+ });
291
+ });
292
+
293
+ describe("init_project", () => {
294
+ const INIT_TEST_DIR = `/tmp/flux-init-test-${Date.now()}`;
295
+
296
+ beforeEach(() => {
297
+ config.clearCache();
298
+ clearAdapterCache();
299
+ process.env.FLUX_PROJECT_ROOT = INIT_TEST_DIR;
300
+ mkdirSync(INIT_TEST_DIR, { recursive: true });
301
+ });
302
+
303
+ afterEach(() => {
304
+ closeDb();
305
+ clearAdapterCache();
306
+ config.clearCache();
307
+ if (existsSync(INIT_TEST_DIR)) {
308
+ rmSync(INIT_TEST_DIR, { recursive: true, force: true });
309
+ }
310
+ });
311
+
312
+ test("initializes new project", async () => {
313
+ const result = (await initProjectTool.handler({
314
+ name: "My Project",
315
+ vision: "Build something great",
316
+ })) as any;
317
+
318
+ expect(result.success).toBe(true);
319
+ expect(result.project.name).toBe("My Project");
320
+ expect(result.project.vision).toBe("Build something great");
321
+ expect(result.project.ref_prefix).toBeDefined();
322
+ expect(existsSync(join(INIT_TEST_DIR, ".flux"))).toBe(true);
323
+ expect(existsSync(join(INIT_TEST_DIR, ".flux", "project.json"))).toBe(true);
324
+ expect(existsSync(join(INIT_TEST_DIR, ".flux", "flux.db"))).toBe(true);
325
+ });
326
+
327
+ test("generates ref prefix from project name", async () => {
328
+ const result = (await initProjectTool.handler({
329
+ name: "My Awesome App",
330
+ vision: "Test vision",
331
+ })) as any;
332
+
333
+ // Should generate prefix like "MAA" or "MYAWE"
334
+ expect(result.project.ref_prefix).toBeDefined();
335
+ expect(result.project.ref_prefix.length).toBeGreaterThan(0);
336
+ });
337
+
338
+ test("fails if already initialized", async () => {
339
+ // First init
340
+ await initProjectTool.handler({
341
+ name: "First Project",
342
+ vision: "First vision",
343
+ });
344
+
345
+ // Second init should fail
346
+ await expect(
347
+ initProjectTool.handler({
348
+ name: "Second Project",
349
+ vision: "Second vision",
350
+ }),
351
+ ).rejects.toThrow("already initialized");
352
+ });
353
+ });