auq-mcp-server 0.1.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 (66) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +176 -0
  3. package/dist/__tests__/schema-validation.test.js +137 -0
  4. package/dist/__tests__/server.integration.test.js +263 -0
  5. package/dist/add.js +1 -0
  6. package/dist/add.test.js +5 -0
  7. package/dist/bin/auq.js +245 -0
  8. package/dist/bin/test-session-menu.js +28 -0
  9. package/dist/bin/test-tabbar.js +42 -0
  10. package/dist/file-utils.js +59 -0
  11. package/dist/format/ResponseFormatter.js +206 -0
  12. package/dist/format/__tests__/ResponseFormatter.test.js +380 -0
  13. package/dist/package.json +74 -0
  14. package/dist/server.js +107 -0
  15. package/dist/session/ResponseFormatter.js +130 -0
  16. package/dist/session/SessionManager.js +474 -0
  17. package/dist/session/__tests__/ResponseFormatter.test.js +417 -0
  18. package/dist/session/__tests__/SessionManager.test.js +553 -0
  19. package/dist/session/__tests__/atomic-operations.test.js +345 -0
  20. package/dist/session/__tests__/file-watcher.test.js +311 -0
  21. package/dist/session/__tests__/workflow.integration.test.js +334 -0
  22. package/dist/session/atomic-operations.js +307 -0
  23. package/dist/session/file-watcher.js +218 -0
  24. package/dist/session/index.js +7 -0
  25. package/dist/session/types.js +20 -0
  26. package/dist/session/utils.js +125 -0
  27. package/dist/session-manager.js +171 -0
  28. package/dist/session-watcher.js +110 -0
  29. package/dist/src/__tests__/schema-validation.test.js +170 -0
  30. package/dist/src/__tests__/server.integration.test.js +274 -0
  31. package/dist/src/add.js +1 -0
  32. package/dist/src/add.test.js +5 -0
  33. package/dist/src/server.js +163 -0
  34. package/dist/src/session/ResponseFormatter.js +163 -0
  35. package/dist/src/session/SessionManager.js +572 -0
  36. package/dist/src/session/__tests__/ResponseFormatter.test.js +741 -0
  37. package/dist/src/session/__tests__/SessionManager.test.js +593 -0
  38. package/dist/src/session/__tests__/atomic-operations.test.js +346 -0
  39. package/dist/src/session/__tests__/file-watcher.test.js +311 -0
  40. package/dist/src/session/atomic-operations.js +307 -0
  41. package/dist/src/session/file-watcher.js +227 -0
  42. package/dist/src/session/index.js +7 -0
  43. package/dist/src/session/types.js +20 -0
  44. package/dist/src/session/utils.js +180 -0
  45. package/dist/src/tui/__tests__/session-watcher.test.js +368 -0
  46. package/dist/src/tui/components/AnimatedGradient.js +45 -0
  47. package/dist/src/tui/components/ConfirmationDialog.js +89 -0
  48. package/dist/src/tui/components/CustomInput.js +14 -0
  49. package/dist/src/tui/components/Footer.js +55 -0
  50. package/dist/src/tui/components/Header.js +35 -0
  51. package/dist/src/tui/components/MultiLineTextInput.js +65 -0
  52. package/dist/src/tui/components/OptionsList.js +115 -0
  53. package/dist/src/tui/components/QuestionDisplay.js +36 -0
  54. package/dist/src/tui/components/ReviewScreen.js +57 -0
  55. package/dist/src/tui/components/SessionSelectionMenu.js +151 -0
  56. package/dist/src/tui/components/StepperView.js +166 -0
  57. package/dist/src/tui/components/TabBar.js +42 -0
  58. package/dist/src/tui/components/Toast.js +19 -0
  59. package/dist/src/tui/components/WaitingScreen.js +20 -0
  60. package/dist/src/tui/session-watcher.js +195 -0
  61. package/dist/src/tui/theme.js +114 -0
  62. package/dist/src/tui/utils/gradientText.js +24 -0
  63. package/dist/tui/__tests__/session-watcher.test.js +368 -0
  64. package/dist/tui/session-watcher.js +183 -0
  65. package/package.json +78 -0
  66. package/scripts/postinstall.cjs +51 -0
@@ -0,0 +1,417 @@
1
+ /**
2
+ * Unit tests for Response Formatter
3
+ */
4
+ import { describe, expect, it } from "vitest";
5
+ import { ResponseFormatter } from "../ResponseFormatter.js";
6
+ describe("ResponseFormatter", () => {
7
+ describe("formatUserResponse", () => {
8
+ it("should format a single question with selected option", () => {
9
+ const questions = [
10
+ {
11
+ options: [
12
+ {
13
+ description: "Dynamic web language",
14
+ label: "JavaScript",
15
+ },
16
+ {
17
+ description: "Type-safe JavaScript",
18
+ label: "TypeScript",
19
+ },
20
+ ],
21
+ prompt: "What is your favorite programming language?",
22
+ },
23
+ ];
24
+ const answers = {
25
+ answers: [
26
+ {
27
+ questionIndex: 0,
28
+ selectedOption: "TypeScript",
29
+ timestamp: "2025-01-01T00:00:00Z",
30
+ },
31
+ ],
32
+ sessionId: "test-123",
33
+ timestamp: "2025-01-01T00:00:00Z",
34
+ };
35
+ const result = ResponseFormatter.formatUserResponse(answers, questions);
36
+ expect(result).toBe("Here are the user's answers:\n\n" +
37
+ "1. What is your favorite programming language?\n" +
38
+ "→ TypeScript — Type-safe JavaScript");
39
+ });
40
+ it("should format multiple questions with selected options", () => {
41
+ const questions = [
42
+ {
43
+ options: [
44
+ {
45
+ description: "Dynamic web language",
46
+ label: "JavaScript",
47
+ },
48
+ {
49
+ description: "Type-safe JavaScript",
50
+ label: "TypeScript",
51
+ },
52
+ ],
53
+ prompt: "What is your favorite programming language?",
54
+ },
55
+ {
56
+ options: [
57
+ {
58
+ description: "Frontend or backend web application",
59
+ label: "Web",
60
+ },
61
+ {
62
+ description: "Command-line tool",
63
+ label: "CLI",
64
+ },
65
+ ],
66
+ prompt: "What type of application are you building?",
67
+ },
68
+ ];
69
+ const answers = {
70
+ answers: [
71
+ {
72
+ questionIndex: 0,
73
+ selectedOption: "TypeScript",
74
+ timestamp: "2025-01-01T00:00:00Z",
75
+ },
76
+ {
77
+ questionIndex: 1,
78
+ selectedOption: "Web",
79
+ timestamp: "2025-01-01T00:00:00Z",
80
+ },
81
+ ],
82
+ sessionId: "test-123",
83
+ timestamp: "2025-01-01T00:00:00Z",
84
+ };
85
+ const result = ResponseFormatter.formatUserResponse(answers, questions);
86
+ expect(result).toBe("Here are the user's answers:\n\n" +
87
+ "1. What is your favorite programming language?\n" +
88
+ "→ TypeScript — Type-safe JavaScript\n\n" +
89
+ "2. What type of application are you building?\n" +
90
+ "→ Web — Frontend or backend web application");
91
+ });
92
+ it("should format custom text answer", () => {
93
+ const questions = [
94
+ {
95
+ options: [
96
+ {
97
+ description: "Frontend or backend web application",
98
+ label: "Web",
99
+ },
100
+ {
101
+ description: "Command-line tool",
102
+ label: "CLI",
103
+ },
104
+ ],
105
+ prompt: "What type of application are you building?",
106
+ },
107
+ ];
108
+ const answers = {
109
+ answers: [
110
+ {
111
+ customText: "Desktop app with Electron",
112
+ questionIndex: 0,
113
+ timestamp: "2025-01-01T00:00:00Z",
114
+ },
115
+ ],
116
+ sessionId: "test-123",
117
+ timestamp: "2025-01-01T00:00:00Z",
118
+ };
119
+ const result = ResponseFormatter.formatUserResponse(answers, questions);
120
+ expect(result).toBe("Here are the user's answers:\n\n" +
121
+ "1. What type of application are you building?\n" +
122
+ "→ Other: 'Desktop app with Electron'");
123
+ });
124
+ it("should format mix of selected and custom answers", () => {
125
+ const questions = [
126
+ {
127
+ options: [
128
+ {
129
+ description: "Dynamic web language",
130
+ label: "JavaScript",
131
+ },
132
+ {
133
+ description: "Type-safe JavaScript",
134
+ label: "TypeScript",
135
+ },
136
+ ],
137
+ prompt: "What is your favorite programming language?",
138
+ },
139
+ {
140
+ options: [
141
+ {
142
+ description: "Frontend or backend web application",
143
+ label: "Web",
144
+ },
145
+ {
146
+ description: "Command-line tool",
147
+ label: "CLI",
148
+ },
149
+ ],
150
+ prompt: "What type of application are you building?",
151
+ },
152
+ ];
153
+ const answers = {
154
+ answers: [
155
+ {
156
+ questionIndex: 0,
157
+ selectedOption: "TypeScript",
158
+ timestamp: "2025-01-01T00:00:00Z",
159
+ },
160
+ {
161
+ customText: "Desktop app with Electron",
162
+ questionIndex: 1,
163
+ timestamp: "2025-01-01T00:00:00Z",
164
+ },
165
+ ],
166
+ sessionId: "test-123",
167
+ timestamp: "2025-01-01T00:00:00Z",
168
+ };
169
+ const result = ResponseFormatter.formatUserResponse(answers, questions);
170
+ expect(result).toBe("Here are the user's answers:\n\n" +
171
+ "1. What is your favorite programming language?\n" +
172
+ "→ TypeScript — Type-safe JavaScript\n\n" +
173
+ "2. What type of application are you building?\n" +
174
+ "→ Other: 'Desktop app with Electron'");
175
+ });
176
+ it("should handle option without description", () => {
177
+ const questions = [
178
+ {
179
+ options: [
180
+ {
181
+ label: "Red",
182
+ },
183
+ {
184
+ label: "Blue",
185
+ },
186
+ ],
187
+ prompt: "Choose a color",
188
+ },
189
+ ];
190
+ const answers = {
191
+ answers: [
192
+ {
193
+ questionIndex: 0,
194
+ selectedOption: "Red",
195
+ timestamp: "2025-01-01T00:00:00Z",
196
+ },
197
+ ],
198
+ sessionId: "test-123",
199
+ timestamp: "2025-01-01T00:00:00Z",
200
+ };
201
+ const result = ResponseFormatter.formatUserResponse(answers, questions);
202
+ expect(result).toBe("Here are the user's answers:\n\n" + "1. Choose a color\n" + "→ Red");
203
+ });
204
+ it("should escape single quotes in custom text", () => {
205
+ const questions = [
206
+ {
207
+ options: [
208
+ {
209
+ label: "Good",
210
+ },
211
+ ],
212
+ prompt: "What is your feedback?",
213
+ },
214
+ ];
215
+ const answers = {
216
+ answers: [
217
+ {
218
+ customText: "It's great! I'm loving it!",
219
+ questionIndex: 0,
220
+ timestamp: "2025-01-01T00:00:00Z",
221
+ },
222
+ ],
223
+ sessionId: "test-123",
224
+ timestamp: "2025-01-01T00:00:00Z",
225
+ };
226
+ const result = ResponseFormatter.formatUserResponse(answers, questions);
227
+ expect(result).toContain("It\\'s great! I\\'m loving it!");
228
+ });
229
+ it("should throw error for empty answers", () => {
230
+ const questions = [
231
+ {
232
+ options: [{ label: "Option 1" }],
233
+ prompt: "Test question",
234
+ },
235
+ ];
236
+ const answers = {
237
+ answers: [],
238
+ sessionId: "test-123",
239
+ timestamp: "2025-01-01T00:00:00Z",
240
+ };
241
+ expect(() => ResponseFormatter.formatUserResponse(answers, questions)).toThrow("No answers provided in session");
242
+ });
243
+ it("should throw error for empty questions", () => {
244
+ const questions = [];
245
+ const answers = {
246
+ answers: [
247
+ {
248
+ questionIndex: 0,
249
+ selectedOption: "Option 1",
250
+ timestamp: "2025-01-01T00:00:00Z",
251
+ },
252
+ ],
253
+ sessionId: "test-123",
254
+ timestamp: "2025-01-01T00:00:00Z",
255
+ };
256
+ expect(() => ResponseFormatter.formatUserResponse(answers, questions)).toThrow("No questions provided");
257
+ });
258
+ it("should handle missing answer for a question gracefully", () => {
259
+ const questions = [
260
+ {
261
+ options: [{ label: "Option 1" }],
262
+ prompt: "Question 1",
263
+ },
264
+ {
265
+ options: [{ label: "Option 2" }],
266
+ prompt: "Question 2",
267
+ },
268
+ ];
269
+ const answers = {
270
+ answers: [
271
+ // Only answer for question 0, missing answer for question 1
272
+ {
273
+ questionIndex: 0,
274
+ selectedOption: "Option 1",
275
+ timestamp: "2025-01-01T00:00:00Z",
276
+ },
277
+ ],
278
+ sessionId: "test-123",
279
+ timestamp: "2025-01-01T00:00:00Z",
280
+ };
281
+ const result = ResponseFormatter.formatUserResponse(answers, questions);
282
+ // Should only include the answered question
283
+ expect(result).toBe("Here are the user's answers:\n\n" + "1. Question 1\n" + "→ Option 1");
284
+ });
285
+ });
286
+ describe("validateAnswers", () => {
287
+ it("should validate correct answers", () => {
288
+ const questions = [
289
+ {
290
+ options: [{ label: "Option 1" }, { label: "Option 2" }],
291
+ prompt: "Test question",
292
+ },
293
+ ];
294
+ const answers = {
295
+ answers: [
296
+ {
297
+ questionIndex: 0,
298
+ selectedOption: "Option 1",
299
+ timestamp: "2025-01-01T00:00:00Z",
300
+ },
301
+ ],
302
+ sessionId: "test-123",
303
+ timestamp: "2025-01-01T00:00:00Z",
304
+ };
305
+ expect(() => ResponseFormatter.validateAnswers(answers, questions)).not.toThrow();
306
+ });
307
+ it("should throw error for invalid question index", () => {
308
+ const questions = [
309
+ {
310
+ options: [{ label: "Option 1" }],
311
+ prompt: "Test question",
312
+ },
313
+ ];
314
+ const answers = {
315
+ answers: [
316
+ {
317
+ questionIndex: 99, // Invalid index
318
+ selectedOption: "Option 1",
319
+ timestamp: "2025-01-01T00:00:00Z",
320
+ },
321
+ ],
322
+ sessionId: "test-123",
323
+ timestamp: "2025-01-01T00:00:00Z",
324
+ };
325
+ expect(() => ResponseFormatter.validateAnswers(answers, questions)).toThrow("Answer references invalid question index: 99");
326
+ });
327
+ it("should throw error for answer with neither selectedOption nor customText", () => {
328
+ const questions = [
329
+ {
330
+ options: [{ label: "Option 1" }],
331
+ prompt: "Test question",
332
+ },
333
+ ];
334
+ const answers = {
335
+ answers: [
336
+ {
337
+ questionIndex: 0,
338
+ // Neither selectedOption nor customText provided
339
+ timestamp: "2025-01-01T00:00:00Z",
340
+ },
341
+ ],
342
+ sessionId: "test-123",
343
+ timestamp: "2025-01-01T00:00:00Z",
344
+ };
345
+ expect(() => ResponseFormatter.validateAnswers(answers, questions)).toThrow("has neither selectedOption nor customText");
346
+ });
347
+ it("should throw error for non-existent option", () => {
348
+ const questions = [
349
+ {
350
+ options: [{ label: "Option 1" }, { label: "Option 2" }],
351
+ prompt: "Test question",
352
+ },
353
+ ];
354
+ const answers = {
355
+ answers: [
356
+ {
357
+ questionIndex: 0,
358
+ selectedOption: "Option 3", // Non-existent option
359
+ timestamp: "2025-01-01T00:00:00Z",
360
+ },
361
+ ],
362
+ sessionId: "test-123",
363
+ timestamp: "2025-01-01T00:00:00Z",
364
+ };
365
+ expect(() => ResponseFormatter.validateAnswers(answers, questions)).toThrow("references non-existent option: Option 3");
366
+ });
367
+ it("should validate custom text answers", () => {
368
+ const questions = [
369
+ {
370
+ options: [{ label: "Option 1" }],
371
+ prompt: "Test question",
372
+ },
373
+ ];
374
+ const answers = {
375
+ answers: [
376
+ {
377
+ customText: "My custom answer",
378
+ questionIndex: 0,
379
+ timestamp: "2025-01-01T00:00:00Z",
380
+ },
381
+ ],
382
+ sessionId: "test-123",
383
+ timestamp: "2025-01-01T00:00:00Z",
384
+ };
385
+ expect(() => ResponseFormatter.validateAnswers(answers, questions)).not.toThrow();
386
+ });
387
+ it("should throw error for empty answers array", () => {
388
+ const questions = [
389
+ {
390
+ options: [{ label: "Option 1" }],
391
+ prompt: "Test question",
392
+ },
393
+ ];
394
+ const answers = {
395
+ answers: [],
396
+ sessionId: "test-123",
397
+ timestamp: "2025-01-01T00:00:00Z",
398
+ };
399
+ expect(() => ResponseFormatter.validateAnswers(answers, questions)).toThrow("No answers provided");
400
+ });
401
+ it("should throw error for empty questions array", () => {
402
+ const questions = [];
403
+ const answers = {
404
+ answers: [
405
+ {
406
+ questionIndex: 0,
407
+ selectedOption: "Option 1",
408
+ timestamp: "2025-01-01T00:00:00Z",
409
+ },
410
+ ],
411
+ sessionId: "test-123",
412
+ timestamp: "2025-01-01T00:00:00Z",
413
+ };
414
+ expect(() => ResponseFormatter.validateAnswers(answers, questions)).toThrow("No questions provided");
415
+ });
416
+ });
417
+ });