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
package/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ The MIT License (MIT)
2
+ =====================
3
+
4
+ Copyright © 2025 Frank Fiegel (frank@glama.ai)
5
+
6
+ Permission is hereby granted, free of charge, to any person
7
+ obtaining a copy of this software and associated documentation
8
+ files (the “Software”), to deal in the Software without
9
+ restriction, including without limitation the rights to use,
10
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the
12
+ Software is furnished to do so, subject to the following
13
+ conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25
+ OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # AUQ - ask-user-questions MCP
2
+
3
+ [![npm version](https://img.shields.io/npm/v/auq-mcp-server.svg)](https://www.npmjs.com/package/auq-mcp-server)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/paulp-o/ask-user-question-mcp/test.yml)](https://github.com/paulp-o/ask-user-question-mcp/actions)
6
+
7
+ **A lightweight MCP server & CLI tool that allows your LLMs to ask questions to you in a clean, separate space with great terminal UX.**
8
+
9
+ ![AUQ Demo](docs/screenshot.png)
10
+
11
+ ---
12
+
13
+ ## What does it do?
14
+
15
+ Through the MCP server, your LLM can generate question sets consisting of multiple-choice, single-choice, and free-text questions (with an "Other" option for custom input) while coding or working, and wait for your answers.
16
+
17
+ You can keep the `auq` CLI running in advance, or start it when questions are pending. With simple arrow key navigation, you can select answers and send them back to the AI—all within a clean terminal interface.
18
+
19
+ ## Why?
20
+
21
+ In AI-assisted coding, **clarifying questions** have always been recognized as a powerful prompt engineering technique to overcome LLM hallucination and generate more contextually appropriate code ([research paper](https://arxiv.org/abs/2308.13507)).
22
+
23
+ On October 18th, Claude Code 2.0.21 introduced an internal `ask-user-question` tool, and I loved this feature. Inspired by it, I created this project to overcome what I saw as a few limitations:
24
+
25
+ - **Tool-agnostic** - Works with any MCP client (Claude Desktop, Cursor, etc.)
26
+ - **Non-invasive** - Doesn't heavily integrate with your coding CLI workflow or occupy UI space
27
+ - **Multi-agent friendly** - Supports receiving questions from multiple agents simultaneously in parallel workflows
28
+
29
+ This is AUQ—a human-AI question-answer loop tool designed for modern AI coding workflows.
30
+
31
+ ---
32
+
33
+ ## 🚀 Quick Start
34
+
35
+ ### Global Installation (Recommended)
36
+
37
+ ```bash
38
+ # Install globally
39
+ npm install -g auq-mcp-server
40
+
41
+ # Start the TUI
42
+ auq
43
+ ```
44
+
45
+ ### Local Installation (Project-specific)
46
+
47
+ ```bash
48
+ # Install in your project
49
+ npm install auq-mcp-server
50
+
51
+ # Start the TUI from project directory
52
+ npx auq
53
+ ```
54
+
55
+ **Session Storage:**
56
+ - **Global install**: `~/Library/Application Support/auq/sessions` (macOS), `~/.local/share/auq/sessions` (Linux)
57
+ - **Local install**: `.auq/sessions/` in your project root
58
+ - **Override**: Set `AUQ_SESSION_DIR` environment variable
59
+
60
+ ---
61
+
62
+ ## 🔌 MCP Server Configuration
63
+
64
+ ### Claude Desktop
65
+
66
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
67
+
68
+ ```json
69
+ {
70
+ "mcpServers": {
71
+ "auq": {
72
+ "command": "npx",
73
+ "args": ["-y", "auq-mcp-server", "server"]
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ **Restart Claude Desktop** after saving.
80
+
81
+ ### Cursor / Other MCP Clients
82
+
83
+ Add to `.mcp.json` in your project root:
84
+
85
+ ```json
86
+ {
87
+ "mcpServers": {
88
+ "auq": {
89
+ "command": "npx",
90
+ "args": ["-y", "auq-mcp-server", "server"],
91
+ "env": {}
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ Restart your client after configuration.
98
+
99
+ ---
100
+
101
+ ## ✨ Features
102
+
103
+ ### 🖥️ CLI-Based
104
+ - **Lightweight**: Adds only ~150 tokens to your context per question
105
+ - **SSH-compatible**: Use over remote connections
106
+ - **Fast**: Instant startup, minimal resource usage
107
+
108
+ ### 📦 100% Local
109
+ All information operates based on your local file system. No data leaves your machine.
110
+
111
+ ### 🔄 Resumable & Stateless
112
+ The CLI app doesn't need to be running in advance. Whether the model calls the MCP first and you start the CLI later, or you keep it running—you can immediately answer pending questions in FIFO order.
113
+
114
+ ### ❌ Question Set Rejection with Feedback Loop
115
+ When the LLM asks about the wrong domain entirely, you can reject the question set, optionally providing the reason to the LLM. The rejection feedback is sent back to the LLM, allowing it to ask more helpful questions or align on what's important for the project.
116
+
117
+ ### 📋 Question Set Queuing
118
+ Recent AI workflows often use parallel sub-agents for concurrent coding. AUQ handles multiple simultaneous LLM calls gracefully—when a new question set arrives while you're answering another, it's queued and processed sequentially. Perfect for multi-agent parallel coding workflows.
119
+
120
+ ---
121
+
122
+ ## 💻 Usage
123
+
124
+ ### Starting the CLI tool
125
+
126
+ ```bash
127
+ auq
128
+ ```
129
+
130
+ Then just start working with your coding agent or AI assistant. You may prompt to ask questions with the tool the agent got; it will mostly just get what you mean.
131
+
132
+ ### Commands
133
+
134
+ ```bash
135
+ # you won't likely need these at all
136
+ auq server # Start MCP server
137
+ auq --version # Show version
138
+ auq --help # Show help
139
+ ```
140
+
141
+ ---
142
+
143
+ ### Manual session cleanup
144
+
145
+ Sessions auto-clean after completion or timeout. However, you can manually clean them up if you want to.
146
+
147
+ ```bash
148
+ # Global install
149
+ rm -rf ~/Library/Application\ Support/auq/sessions/* # macOS
150
+ rm -rf ~/.local/share/auq/sessions/* # Linux
151
+
152
+ # Local install
153
+ rm -rf .auq/sessions/*
154
+ ```
155
+
156
+ ---
157
+
158
+ ## 🚀 Roadmap
159
+
160
+ - [ ] Light & dark mode themes
161
+ - [ ] MCP prompt mode switch (Anthropic style / minimal)
162
+ - [ ] Custom color themes
163
+ - [ ] Question history/recall
164
+ - [ ] Multi-language support
165
+ - [ ] Audio notifications (optional)
166
+ - [ ] Export answers to file
167
+
168
+ ---
169
+
170
+
171
+ ## 📄 License
172
+
173
+ MIT License - see [LICENSE](LICENSE) file for details.
174
+
175
+ ---
176
+
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Minimal schema validation tests for Question/Option types
3
+ * Tests the most common edge cases to catch obvious bugs
4
+ */
5
+ import { describe, expect, it } from "vitest";
6
+ import { z } from "zod";
7
+ // Import schemas from server (in real implementation, might extract to validation module)
8
+ const OptionSchema = z.object({
9
+ description: z.string().optional(),
10
+ label: z.string(),
11
+ });
12
+ const QuestionSchema = z.object({
13
+ title: z.string(),
14
+ options: z.array(OptionSchema).min(1),
15
+ prompt: z.string(),
16
+ });
17
+ const QuestionsArraySchema = z.array(QuestionSchema).min(1);
18
+ describe("Schema Validation - Edge Cases", () => {
19
+ describe("Invalid Input (should reject)", () => {
20
+ it("should reject missing title field", () => {
21
+ const invalidQuestion = {
22
+ // title missing
23
+ options: [{ label: "Option 1" }],
24
+ prompt: "Test question?",
25
+ };
26
+ expect(() => QuestionSchema.parse(invalidQuestion)).toThrow();
27
+ });
28
+ it("should reject empty options array", () => {
29
+ const invalidQuestion = {
30
+ title: "Test",
31
+ options: [], // Empty array
32
+ prompt: "Test question?",
33
+ };
34
+ expect(() => QuestionSchema.parse(invalidQuestion)).toThrow();
35
+ });
36
+ it("should reject missing prompt", () => {
37
+ const invalidQuestion = {
38
+ title: "Test",
39
+ options: [{ label: "Option 1" }],
40
+ // prompt missing
41
+ };
42
+ expect(() => QuestionSchema.parse(invalidQuestion)).toThrow();
43
+ });
44
+ it("should reject missing options field", () => {
45
+ const invalidQuestion = {
46
+ title: "Test",
47
+ // options missing
48
+ prompt: "Test question?",
49
+ };
50
+ expect(() => QuestionSchema.parse(invalidQuestion)).toThrow();
51
+ });
52
+ it("should reject option with missing label", () => {
53
+ const invalidQuestion = {
54
+ title: "Test",
55
+ options: [
56
+ {
57
+ description: "A description",
58
+ // label missing
59
+ },
60
+ ],
61
+ prompt: "Test question?",
62
+ };
63
+ expect(() => QuestionSchema.parse(invalidQuestion)).toThrow();
64
+ });
65
+ it("should reject empty questions array", () => {
66
+ const invalidQuestions = [];
67
+ expect(() => QuestionsArraySchema.parse(invalidQuestions)).toThrow();
68
+ });
69
+ });
70
+ describe("Valid Input (should accept)", () => {
71
+ it("should accept valid question with title", () => {
72
+ const validQuestion = {
73
+ title: "Language",
74
+ options: [
75
+ {
76
+ description: "A helpful description",
77
+ label: "Option 1",
78
+ },
79
+ ],
80
+ prompt: "What is your choice?",
81
+ };
82
+ expect(() => QuestionSchema.parse(validQuestion)).not.toThrow();
83
+ const parsed = QuestionSchema.parse(validQuestion);
84
+ expect(parsed.title).toBe("Language");
85
+ expect(parsed.prompt).toBe("What is your choice?");
86
+ expect(parsed.options).toHaveLength(1);
87
+ });
88
+ it("should accept valid question with all fields", () => {
89
+ const validQuestion = {
90
+ title: "Framework",
91
+ options: [
92
+ {
93
+ description: "A helpful description",
94
+ label: "Option 1",
95
+ },
96
+ ],
97
+ prompt: "What is your choice?",
98
+ };
99
+ expect(() => QuestionSchema.parse(validQuestion)).not.toThrow();
100
+ const parsed = QuestionSchema.parse(validQuestion);
101
+ expect(parsed.prompt).toBe("What is your choice?");
102
+ expect(parsed.options).toHaveLength(1);
103
+ });
104
+ it("should accept valid question with description omitted", () => {
105
+ const validQuestion = {
106
+ title: "Choice",
107
+ options: [
108
+ {
109
+ label: "Option 1",
110
+ // description omitted (optional)
111
+ },
112
+ ],
113
+ prompt: "What is your choice?",
114
+ };
115
+ expect(() => QuestionSchema.parse(validQuestion)).not.toThrow();
116
+ const parsed = QuestionSchema.parse(validQuestion);
117
+ expect(parsed.options[0].description).toBeUndefined();
118
+ });
119
+ it("should accept multiple valid questions", () => {
120
+ const validQuestions = [
121
+ {
122
+ title: "First",
123
+ options: [{ label: "A" }],
124
+ prompt: "Question 1?",
125
+ },
126
+ {
127
+ title: "Second",
128
+ options: [{ label: "B" }, { label: "C" }],
129
+ prompt: "Question 2?",
130
+ },
131
+ ];
132
+ expect(() => QuestionsArraySchema.parse(validQuestions)).not.toThrow();
133
+ const parsed = QuestionsArraySchema.parse(validQuestions);
134
+ expect(parsed).toHaveLength(2);
135
+ });
136
+ });
137
+ });
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Integration tests for FastMCP server and SessionManager interaction
3
+ */
4
+ import { promises as fs } from "fs";
5
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
+ import { SessionManager } from "../session/index.js";
7
+ describe("Server Integration", () => {
8
+ let sessionManager;
9
+ const testBaseDir = "/tmp/auq-test-integration";
10
+ beforeEach(async () => {
11
+ // Clean up test directory before each test
12
+ await fs.rm(testBaseDir, { force: true, recursive: true }).catch(() => { });
13
+ sessionManager = new SessionManager({
14
+ baseDir: testBaseDir,
15
+ maxSessions: 10,
16
+ sessionTimeout: 5000, // 5 seconds for integration tests
17
+ });
18
+ await sessionManager.initialize();
19
+ });
20
+ afterEach(async () => {
21
+ // Clean up test directory after each test
22
+ await fs.rm(testBaseDir, { force: true, recursive: true }).catch(() => { });
23
+ });
24
+ describe("Session Creation Integration", () => {
25
+ it("should create session when ask_user_questions tool is called", async () => {
26
+ const questions = [
27
+ {
28
+ options: [
29
+ { description: "Dynamic language", label: "JavaScript" },
30
+ { description: "Static typing", label: "TypeScript" },
31
+ ],
32
+ prompt: "Which programming language do you prefer?",
33
+ },
34
+ ];
35
+ const sessionId = await sessionManager.createSession(questions);
36
+ expect(sessionId).toBeDefined();
37
+ expect(typeof sessionId).toBe("string");
38
+ // Verify session data integrity
39
+ const request = await sessionManager.getSessionRequest(sessionId);
40
+ const status = await sessionManager.getSessionStatus(sessionId);
41
+ expect(request?.sessionId).toBe(sessionId);
42
+ expect(request?.questions).toEqual(questions);
43
+ expect(request?.status).toBe("pending");
44
+ expect(status?.sessionId).toBe(sessionId);
45
+ expect(status?.status).toBe("pending");
46
+ expect(status?.totalQuestions).toBe(questions.length);
47
+ });
48
+ it("should handle multiple questions correctly", async () => {
49
+ const questions = [
50
+ {
51
+ options: [
52
+ { description: "Dynamic language", label: "JavaScript" },
53
+ { description: "Static typing", label: "TypeScript" },
54
+ ],
55
+ prompt: "Which programming language?",
56
+ },
57
+ {
58
+ options: [
59
+ { description: "Web application", label: "Web" },
60
+ { description: "Command-line tool", label: "CLI" },
61
+ { description: "Desktop application", label: "Desktop" },
62
+ ],
63
+ prompt: "What type of application?",
64
+ },
65
+ ];
66
+ const sessionId = await sessionManager.createSession(questions);
67
+ const request = await sessionManager.getSessionRequest(sessionId);
68
+ const status = await sessionManager.getSessionStatus(sessionId);
69
+ expect(request?.questions).toHaveLength(2);
70
+ expect(request?.questions[0].prompt).toBe("Which programming language?");
71
+ expect(request?.questions[1].prompt).toBe("What type of application?");
72
+ expect(status?.totalQuestions).toBe(2);
73
+ });
74
+ it("should create unique sessions for multiple calls", async () => {
75
+ const questions = [
76
+ {
77
+ options: [{ label: "Test option" }],
78
+ prompt: "Test question",
79
+ },
80
+ ];
81
+ const sessionId1 = await sessionManager.createSession(questions);
82
+ const sessionId2 = await sessionManager.createSession(questions);
83
+ expect(sessionId1).not.toBe(sessionId2);
84
+ // Verify both sessions exist and are independent
85
+ expect(await sessionManager.sessionExists(sessionId1)).toBe(true);
86
+ expect(await sessionManager.sessionExists(sessionId2)).toBe(true);
87
+ const request1 = await sessionManager.getSessionRequest(sessionId1);
88
+ const request2 = await sessionManager.getSessionRequest(sessionId2);
89
+ expect(request1?.sessionId).toBe(sessionId1);
90
+ expect(request2?.sessionId).toBe(sessionId2);
91
+ });
92
+ });
93
+ describe("Session Data Persistence", () => {
94
+ it("should persist session data across manager instances", async () => {
95
+ const questions = [
96
+ {
97
+ options: [{ description: "Test description", label: "Test option" }],
98
+ prompt: "Test question for persistence",
99
+ },
100
+ ];
101
+ // Create session with first manager instance
102
+ const sessionId = await sessionManager.createSession(questions);
103
+ // Create new manager instance with same directory
104
+ const newManager = new SessionManager({ baseDir: testBaseDir });
105
+ await newManager.initialize();
106
+ // Verify session data is accessible through new manager
107
+ const request = await newManager.getSessionRequest(sessionId);
108
+ const status = await newManager.getSessionStatus(sessionId);
109
+ expect(request?.sessionId).toBe(sessionId);
110
+ expect(request?.questions).toEqual(questions);
111
+ expect(status?.sessionId).toBe(sessionId);
112
+ expect(status?.totalQuestions).toBe(1);
113
+ });
114
+ it("should store session files with correct structure", async () => {
115
+ const questions = [
116
+ {
117
+ options: [{ description: "Description", label: "Option" }],
118
+ prompt: "File structure test",
119
+ },
120
+ ];
121
+ const sessionId = await sessionManager.createSession(questions);
122
+ const sessionDir = `${testBaseDir}/${sessionId}`;
123
+ // Verify request.json structure
124
+ const requestContent = await fs.readFile(`${sessionDir}/request.json`, "utf-8");
125
+ const requestData = JSON.parse(requestContent);
126
+ expect(requestData).toHaveProperty("sessionId", sessionId);
127
+ expect(requestData).toHaveProperty("questions");
128
+ expect(requestData).toHaveProperty("timestamp");
129
+ expect(requestData).toHaveProperty("status", "pending");
130
+ expect(requestData.questions).toEqual(questions);
131
+ // Verify status.json structure
132
+ const statusContent = await fs.readFile(`${sessionDir}/status.json`, "utf-8");
133
+ const statusData = JSON.parse(statusContent);
134
+ expect(statusData).toHaveProperty("sessionId", sessionId);
135
+ expect(statusData).toHaveProperty("status", "pending");
136
+ expect(statusData).toHaveProperty("createdAt");
137
+ expect(statusData).toHaveProperty("lastModified");
138
+ expect(statusData).toHaveProperty("totalQuestions", 1);
139
+ });
140
+ });
141
+ describe("Error Handling Integration", () => {
142
+ it("should handle invalid session directory gracefully", async () => {
143
+ const invalidManager = new SessionManager({
144
+ baseDir: "/root/invalid/path",
145
+ });
146
+ await expect(invalidManager.initialize()).rejects.toThrow();
147
+ });
148
+ it("should handle concurrent session creation", async () => {
149
+ const questions = [
150
+ {
151
+ options: [{ label: "Option" }],
152
+ prompt: "Concurrent test",
153
+ },
154
+ ];
155
+ // Create multiple sessions concurrently
156
+ const sessionPromises = Array.from({ length: 5 }, () => sessionManager.createSession(questions));
157
+ const sessionIds = await Promise.all(sessionPromises);
158
+ // Verify all sessions were created with unique IDs
159
+ const uniqueIds = new Set(sessionIds);
160
+ expect(uniqueIds.size).toBe(5);
161
+ // Verify all sessions exist and are valid
162
+ for (const sessionId of sessionIds) {
163
+ expect(await sessionManager.sessionExists(sessionId)).toBe(true);
164
+ const validation = await sessionManager.validateSession(sessionId);
165
+ expect(validation.isValid).toBe(true);
166
+ }
167
+ });
168
+ });
169
+ describe("Session Lifecycle Integration", () => {
170
+ it("should support complete session lifecycle", async () => {
171
+ const questions = [
172
+ {
173
+ options: [{ label: "Option" }],
174
+ prompt: "Lifecycle test",
175
+ },
176
+ ];
177
+ // Create session
178
+ const sessionId = await sessionManager.createSession(questions);
179
+ expect(await sessionManager.sessionExists(sessionId)).toBe(true);
180
+ // Update status to in-progress
181
+ await sessionManager.updateSessionStatus(sessionId, "in-progress", {
182
+ currentQuestionIndex: 0,
183
+ });
184
+ let status = await sessionManager.getSessionStatus(sessionId);
185
+ expect(status?.status).toBe("in-progress");
186
+ expect(status?.currentQuestionIndex).toBe(0);
187
+ // Save answers
188
+ const answers = {
189
+ answers: [
190
+ {
191
+ questionIndex: 0,
192
+ selectedOption: "Option",
193
+ timestamp: new Date().toISOString(),
194
+ },
195
+ ],
196
+ sessionId,
197
+ timestamp: new Date().toISOString(),
198
+ };
199
+ await sessionManager.saveSessionAnswers(sessionId, answers);
200
+ // Verify final state
201
+ status = await sessionManager.getSessionStatus(sessionId);
202
+ expect(status?.status).toBe("completed");
203
+ const savedAnswers = await sessionManager.getSessionAnswers(sessionId);
204
+ expect(savedAnswers?.answers).toHaveLength(1);
205
+ expect(savedAnswers?.answers[0].selectedOption).toBe("Option");
206
+ // Cleanup
207
+ await sessionManager.deleteSession(sessionId);
208
+ expect(await sessionManager.sessionExists(sessionId)).toBe(false);
209
+ });
210
+ it("should handle session validation correctly", async () => {
211
+ const questions = [
212
+ {
213
+ options: [{ label: "Option" }],
214
+ prompt: "Validation test",
215
+ },
216
+ ];
217
+ const sessionId = await sessionManager.createSession(questions);
218
+ const validation = await sessionManager.validateSession(sessionId);
219
+ expect(validation.isValid).toBe(true);
220
+ expect(validation.issues).toEqual([]);
221
+ // Corrupt the session by removing a required file
222
+ const sessionDir = `${testBaseDir}/${sessionId}`;
223
+ await fs.rm(`${sessionDir}/status.json`);
224
+ const validation2 = await sessionManager.validateSession(sessionId);
225
+ expect(validation2.isValid).toBe(false);
226
+ expect(validation2.issues).toContain("Required file missing: status.json");
227
+ });
228
+ });
229
+ describe("Performance Integration", () => {
230
+ it("should handle session creation under time limits", async () => {
231
+ const questions = [
232
+ {
233
+ options: [{ label: "Option" }],
234
+ prompt: "Performance test",
235
+ },
236
+ ];
237
+ const startTime = Date.now();
238
+ const sessionId = await sessionManager.createSession(questions);
239
+ const endTime = Date.now();
240
+ // Session creation should be fast (under 100ms)
241
+ expect(endTime - startTime).toBeLessThan(100);
242
+ expect(sessionId).toBeDefined();
243
+ });
244
+ it("should handle multiple sessions efficiently", async () => {
245
+ const questions = [
246
+ {
247
+ options: [{ label: "Option" }],
248
+ prompt: "Efficiency test",
249
+ },
250
+ ];
251
+ const startTime = Date.now();
252
+ const sessionIds = await Promise.all(Array.from({ length: 10 }, () => sessionManager.createSession(questions)));
253
+ const endTime = Date.now();
254
+ // Creating 10 sessions should be fast (under 500ms)
255
+ expect(endTime - startTime).toBeLessThan(500);
256
+ expect(sessionIds).toHaveLength(10);
257
+ // Verify all sessions were created correctly
258
+ for (const sessionId of sessionIds) {
259
+ expect(await sessionManager.sessionExists(sessionId)).toBe(true);
260
+ }
261
+ });
262
+ });
263
+ });
package/dist/add.js ADDED
@@ -0,0 +1 @@
1
+ export const add = (a, b) => a + b;
@@ -0,0 +1,5 @@
1
+ import { expect, it } from "vitest";
2
+ import { add } from "./add.js";
3
+ it("should add two numbers", () => {
4
+ expect(add(1, 2)).toBe(3);
5
+ });