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.
- package/LICENSE +25 -0
- package/README.md +176 -0
- package/dist/__tests__/schema-validation.test.js +137 -0
- package/dist/__tests__/server.integration.test.js +263 -0
- package/dist/add.js +1 -0
- package/dist/add.test.js +5 -0
- package/dist/bin/auq.js +245 -0
- package/dist/bin/test-session-menu.js +28 -0
- package/dist/bin/test-tabbar.js +42 -0
- package/dist/file-utils.js +59 -0
- package/dist/format/ResponseFormatter.js +206 -0
- package/dist/format/__tests__/ResponseFormatter.test.js +380 -0
- package/dist/package.json +74 -0
- package/dist/server.js +107 -0
- package/dist/session/ResponseFormatter.js +130 -0
- package/dist/session/SessionManager.js +474 -0
- package/dist/session/__tests__/ResponseFormatter.test.js +417 -0
- package/dist/session/__tests__/SessionManager.test.js +553 -0
- package/dist/session/__tests__/atomic-operations.test.js +345 -0
- package/dist/session/__tests__/file-watcher.test.js +311 -0
- package/dist/session/__tests__/workflow.integration.test.js +334 -0
- package/dist/session/atomic-operations.js +307 -0
- package/dist/session/file-watcher.js +218 -0
- package/dist/session/index.js +7 -0
- package/dist/session/types.js +20 -0
- package/dist/session/utils.js +125 -0
- package/dist/session-manager.js +171 -0
- package/dist/session-watcher.js +110 -0
- package/dist/src/__tests__/schema-validation.test.js +170 -0
- package/dist/src/__tests__/server.integration.test.js +274 -0
- package/dist/src/add.js +1 -0
- package/dist/src/add.test.js +5 -0
- package/dist/src/server.js +163 -0
- package/dist/src/session/ResponseFormatter.js +163 -0
- package/dist/src/session/SessionManager.js +572 -0
- package/dist/src/session/__tests__/ResponseFormatter.test.js +741 -0
- package/dist/src/session/__tests__/SessionManager.test.js +593 -0
- package/dist/src/session/__tests__/atomic-operations.test.js +346 -0
- package/dist/src/session/__tests__/file-watcher.test.js +311 -0
- package/dist/src/session/atomic-operations.js +307 -0
- package/dist/src/session/file-watcher.js +227 -0
- package/dist/src/session/index.js +7 -0
- package/dist/src/session/types.js +20 -0
- package/dist/src/session/utils.js +180 -0
- package/dist/src/tui/__tests__/session-watcher.test.js +368 -0
- package/dist/src/tui/components/AnimatedGradient.js +45 -0
- package/dist/src/tui/components/ConfirmationDialog.js +89 -0
- package/dist/src/tui/components/CustomInput.js +14 -0
- package/dist/src/tui/components/Footer.js +55 -0
- package/dist/src/tui/components/Header.js +35 -0
- package/dist/src/tui/components/MultiLineTextInput.js +65 -0
- package/dist/src/tui/components/OptionsList.js +115 -0
- package/dist/src/tui/components/QuestionDisplay.js +36 -0
- package/dist/src/tui/components/ReviewScreen.js +57 -0
- package/dist/src/tui/components/SessionSelectionMenu.js +151 -0
- package/dist/src/tui/components/StepperView.js +166 -0
- package/dist/src/tui/components/TabBar.js +42 -0
- package/dist/src/tui/components/Toast.js +19 -0
- package/dist/src/tui/components/WaitingScreen.js +20 -0
- package/dist/src/tui/session-watcher.js +195 -0
- package/dist/src/tui/theme.js +114 -0
- package/dist/src/tui/utils/gradientText.js +24 -0
- package/dist/tui/__tests__/session-watcher.test.js +368 -0
- package/dist/tui/session-watcher.js +183 -0
- package/package.json +78 -0
- 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
|
+
[](https://www.npmjs.com/package/auq-mcp-server)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](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
|
+

|
|
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;
|