@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.
- package/README.md +11 -7
- package/agents/coder.md +150 -25
- package/bin/install.cjs +171 -16
- package/commands/breakdown.md +47 -10
- package/commands/dashboard.md +29 -0
- package/commands/flux.md +92 -12
- package/commands/implement.md +166 -17
- package/commands/linear.md +6 -5
- package/commands/prd.md +996 -82
- package/manifest.json +2 -1
- package/package.json +9 -11
- package/skills/flux-orchestrator/SKILL.md +11 -3
- package/skills/prd-writer/SKILL.md +761 -0
- package/skills/ux-ui-design/SKILL.md +346 -0
- package/skills/ux-ui-design/references/design-tokens.md +359 -0
- package/src/__tests__/version.test.ts +37 -0
- package/src/adapters/local/.gitkeep +0 -0
- package/src/dashboard/__tests__/api.test.ts +211 -0
- package/src/dashboard/browser.ts +35 -0
- package/src/dashboard/public/app.js +869 -0
- package/src/dashboard/public/index.html +90 -0
- package/src/dashboard/public/styles.css +807 -0
- package/src/dashboard/public/vendor/highlight.css +10 -0
- package/src/dashboard/public/vendor/highlight.min.js +8422 -0
- package/src/dashboard/public/vendor/marked.min.js +2210 -0
- package/src/dashboard/server.ts +296 -0
- package/src/dashboard/watchers.ts +83 -0
- package/src/server/__tests__/config.test.ts +163 -0
- package/src/server/adapters/__tests__/a-client-linear.test.ts +197 -0
- package/src/server/adapters/__tests__/adapter-factory.test.ts +230 -0
- package/src/server/adapters/__tests__/dependency-ops.test.ts +429 -0
- package/src/server/adapters/__tests__/document-ops.test.ts +306 -0
- package/src/server/adapters/__tests__/linear-adapter.test.ts +91 -0
- package/src/server/adapters/__tests__/linear-config.test.ts +425 -0
- package/src/server/adapters/__tests__/linear-criteria-parser.test.ts +287 -0
- package/src/server/adapters/__tests__/linear-description-test.ts +238 -0
- package/src/server/adapters/__tests__/linear-epic-crud.test.ts +496 -0
- package/src/server/adapters/__tests__/linear-mappers-description.test.ts +276 -0
- package/src/server/adapters/__tests__/linear-mappers-epic.test.ts +294 -0
- package/src/server/adapters/__tests__/linear-mappers-prd.test.ts +300 -0
- package/src/server/adapters/__tests__/linear-mappers-task.test.ts +197 -0
- package/src/server/adapters/__tests__/linear-prd-crud.test.ts +620 -0
- package/src/server/adapters/__tests__/linear-stats.test.ts +450 -0
- package/src/server/adapters/__tests__/linear-task-crud.test.ts +534 -0
- package/src/server/adapters/__tests__/linear-types.test.ts +243 -0
- package/src/server/adapters/__tests__/status-ops.test.ts +441 -0
- package/src/server/adapters/factory.ts +90 -0
- package/src/server/adapters/index.ts +9 -0
- package/src/server/adapters/linear/adapter.ts +1141 -0
- package/src/server/adapters/linear/client.ts +169 -0
- package/src/server/adapters/linear/config.ts +152 -0
- package/src/server/adapters/linear/helpers/criteria-parser.ts +197 -0
- package/src/server/adapters/linear/helpers/index.ts +7 -0
- package/src/server/adapters/linear/index.ts +16 -0
- package/src/server/adapters/linear/mappers/description.ts +136 -0
- package/src/server/adapters/linear/mappers/epic.ts +81 -0
- package/src/server/adapters/linear/mappers/index.ts +27 -0
- package/src/server/adapters/linear/mappers/prd.ts +178 -0
- package/src/server/adapters/linear/mappers/task.ts +82 -0
- package/src/server/adapters/linear/types.ts +264 -0
- package/src/server/adapters/local-adapter.ts +1009 -0
- package/src/server/adapters/types.ts +293 -0
- package/src/server/config.ts +73 -0
- package/src/server/db/__tests__/queries.test.ts +473 -0
- package/src/server/db/ids.ts +17 -0
- package/src/server/db/index.ts +69 -0
- package/src/server/db/queries.ts +142 -0
- package/src/server/db/refs.ts +60 -0
- package/src/server/db/schema.ts +97 -0
- package/src/server/db/sqlite.ts +10 -0
- package/src/server/index.ts +81 -0
- package/src/server/tools/__tests__/crud.test.ts +411 -0
- package/src/server/tools/__tests__/get-version.test.ts +27 -0
- package/src/server/tools/__tests__/mcp-interface.test.ts +479 -0
- package/src/server/tools/__tests__/query.test.ts +405 -0
- package/src/server/tools/__tests__/z-configure-linear.test.ts +511 -0
- package/src/server/tools/__tests__/z-get-linear-url.test.ts +108 -0
- package/src/server/tools/configure-linear.ts +373 -0
- package/src/server/tools/create-epic.ts +44 -0
- package/src/server/tools/create-prd.ts +40 -0
- package/src/server/tools/create-task.ts +47 -0
- package/src/server/tools/criteria.ts +50 -0
- package/src/server/tools/delete-entity.ts +76 -0
- package/src/server/tools/dependencies.ts +55 -0
- package/src/server/tools/get-entity.ts +240 -0
- package/src/server/tools/get-linear-url.ts +28 -0
- package/src/server/tools/get-stats.ts +52 -0
- package/src/server/tools/get-version.ts +20 -0
- package/src/server/tools/index.ts +158 -0
- package/src/server/tools/init-project.ts +108 -0
- package/src/server/tools/query-entities.ts +167 -0
- package/src/server/tools/render-status.ts +219 -0
- package/src/server/tools/update-entity.ts +140 -0
- package/src/server/tools/update-status.ts +166 -0
- package/src/server/utils/__tests__/mcp-response.test.ts +331 -0
- package/src/server/utils/logger.ts +9 -0
- package/src/server/utils/mcp-response.ts +254 -0
- package/src/server/utils/status-transitions.ts +160 -0
- package/src/status-line/__tests__/status-line.test.ts +215 -0
- package/src/status-line/index.ts +147 -0
- package/src/utils/__tests__/chalk-import.test.ts +32 -0
- package/src/utils/__tests__/display.test.ts +97 -0
- package/src/utils/__tests__/status-renderer.test.ts +310 -0
- package/src/utils/display.ts +62 -0
- package/src/utils/status-renderer.ts +214 -0
- package/src/version.ts +5 -0
- package/dist/server/index.js +0 -87063
- package/skills/prd-template/SKILL.md +0 -242
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, 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-mcp-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
7
|
+
const FLUX_DIR = join(TEST_DIR, ".flux");
|
|
8
|
+
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
9
|
+
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { config } from "../../config.js";
|
|
12
|
+
import { closeDb, initDb } from "../../db/index.js";
|
|
13
|
+
import {
|
|
14
|
+
createError,
|
|
15
|
+
createProjectNotInitializedError,
|
|
16
|
+
registerTools,
|
|
17
|
+
type ToolDefinition,
|
|
18
|
+
} from "../index.js";
|
|
19
|
+
|
|
20
|
+
describe("MCP Interface", () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
closeDb();
|
|
23
|
+
config.clearCache();
|
|
24
|
+
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
25
|
+
|
|
26
|
+
mkdirSync(FLUX_DIR, { recursive: true });
|
|
27
|
+
writeFileSync(
|
|
28
|
+
join(FLUX_DIR, "project.json"),
|
|
29
|
+
JSON.stringify({ name: "test-project", ref_prefix: "TEST" }),
|
|
30
|
+
);
|
|
31
|
+
initDb();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
closeDb();
|
|
36
|
+
config.clearCache();
|
|
37
|
+
if (existsSync(TEST_DIR)) {
|
|
38
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("createError", () => {
|
|
43
|
+
test("creates error object with message and code", () => {
|
|
44
|
+
const error = createError("Test error", "TEST_ERROR");
|
|
45
|
+
expect(error).toEqual({
|
|
46
|
+
error: true,
|
|
47
|
+
message: "Test error",
|
|
48
|
+
code: "TEST_ERROR",
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("createProjectNotInitializedError", () => {
|
|
54
|
+
test("creates error with setup instructions", () => {
|
|
55
|
+
const error = createProjectNotInitializedError("/test/cwd", "/test/root");
|
|
56
|
+
|
|
57
|
+
expect(error.error).toBe(true);
|
|
58
|
+
expect(error.code).toBe("PROJECT_NOT_INITIALIZED");
|
|
59
|
+
expect(error.message).toContain("/test/cwd");
|
|
60
|
+
expect(error.message).toContain("/test/root");
|
|
61
|
+
expect(error.setup.instructions).toBeDefined();
|
|
62
|
+
expect(error.setup.options).toHaveLength(2);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("includes command option for interactive setup", () => {
|
|
66
|
+
const error = createProjectNotInitializedError("/cwd", "/root");
|
|
67
|
+
const commandOption = error.setup.options.find(
|
|
68
|
+
(o) => o.method === "command",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(commandOption).toBeDefined();
|
|
72
|
+
expect(commandOption?.name).toBe("/flux");
|
|
73
|
+
expect(commandOption?.description).toContain("Interactive");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("includes tool option with required params", () => {
|
|
77
|
+
const error = createProjectNotInitializedError("/cwd", "/root");
|
|
78
|
+
const toolOption = error.setup.options.find((o) => o.method === "tool");
|
|
79
|
+
|
|
80
|
+
expect(toolOption).toBeDefined();
|
|
81
|
+
expect(toolOption?.name).toBe("init_project");
|
|
82
|
+
expect(toolOption?.params).toBeDefined();
|
|
83
|
+
expect(toolOption?.params?.name).toBeDefined();
|
|
84
|
+
expect(toolOption?.params?.vision).toBeDefined();
|
|
85
|
+
expect(toolOption?.params?.adapter).toBeDefined();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("registerTools", () => {
|
|
90
|
+
test("sets up list tools handler", () => {
|
|
91
|
+
const handlers: Record<string, Function> = {};
|
|
92
|
+
const mockServer = {
|
|
93
|
+
setRequestHandler: mock((schema: any, handler: Function) => {
|
|
94
|
+
handlers[schema.method || "unknown"] = handler;
|
|
95
|
+
}),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const testTool: ToolDefinition = {
|
|
99
|
+
name: "test_tool",
|
|
100
|
+
description: "A test tool",
|
|
101
|
+
inputSchema: z.object({ input: z.string() }),
|
|
102
|
+
handler: async () => ({ success: true }),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
registerTools(mockServer as any, [testTool]);
|
|
106
|
+
|
|
107
|
+
// Should have registered two handlers
|
|
108
|
+
expect(mockServer.setRequestHandler).toHaveBeenCalledTimes(2);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("list tools returns tool definitions with JSON schema", async () => {
|
|
112
|
+
const handlers: Record<string, Function> = {};
|
|
113
|
+
const mockServer = {
|
|
114
|
+
setRequestHandler: mock((schema: any, handler: Function) => {
|
|
115
|
+
// Store handler by a key we can identify
|
|
116
|
+
if (schema.shape?.method?.value === "tools/list") {
|
|
117
|
+
handlers.list = handler;
|
|
118
|
+
} else if (schema.shape?.method?.value === "tools/call") {
|
|
119
|
+
handlers.call = handler;
|
|
120
|
+
}
|
|
121
|
+
}),
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const testTool: ToolDefinition = {
|
|
125
|
+
name: "test_tool",
|
|
126
|
+
description: "A test tool",
|
|
127
|
+
inputSchema: z.object({
|
|
128
|
+
input: z.string(),
|
|
129
|
+
optional: z.number().optional(),
|
|
130
|
+
}),
|
|
131
|
+
handler: async () => ({ success: true }),
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
registerTools(mockServer as any, [testTool]);
|
|
135
|
+
|
|
136
|
+
// Call the list handler
|
|
137
|
+
const listHandler = handlers.list;
|
|
138
|
+
expect(listHandler).toBeDefined();
|
|
139
|
+
|
|
140
|
+
const result = await listHandler();
|
|
141
|
+
expect(result.tools).toBeDefined();
|
|
142
|
+
expect(result.tools.length).toBe(1);
|
|
143
|
+
expect(result.tools[0].name).toBe("test_tool");
|
|
144
|
+
expect(result.tools[0].description).toBe("A test tool");
|
|
145
|
+
expect(result.tools[0].inputSchema).toBeDefined();
|
|
146
|
+
// JSON schema should have properties
|
|
147
|
+
expect(result.tools[0].inputSchema.type).toBe("object");
|
|
148
|
+
expect(result.tools[0].inputSchema.properties).toBeDefined();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("call tool executes handler with arguments", async () => {
|
|
152
|
+
const handlers: Record<string, Function> = {};
|
|
153
|
+
const mockServer = {
|
|
154
|
+
setRequestHandler: mock((schema: any, handler: Function) => {
|
|
155
|
+
if (schema.shape?.method?.value === "tools/list") {
|
|
156
|
+
handlers.list = handler;
|
|
157
|
+
} else if (schema.shape?.method?.value === "tools/call") {
|
|
158
|
+
handlers.call = handler;
|
|
159
|
+
}
|
|
160
|
+
}),
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const handlerMock = mock(async (input: any) => ({
|
|
164
|
+
received: input.input,
|
|
165
|
+
}));
|
|
166
|
+
|
|
167
|
+
const testTool: ToolDefinition = {
|
|
168
|
+
name: "test_tool",
|
|
169
|
+
description: "A test tool",
|
|
170
|
+
inputSchema: z.object({ input: z.string() }),
|
|
171
|
+
handler: handlerMock,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
registerTools(mockServer as any, [testTool]);
|
|
175
|
+
|
|
176
|
+
const callHandler = handlers.call;
|
|
177
|
+
expect(callHandler).toBeDefined();
|
|
178
|
+
|
|
179
|
+
const result = await callHandler({
|
|
180
|
+
params: {
|
|
181
|
+
name: "test_tool",
|
|
182
|
+
arguments: { input: "hello" },
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(handlerMock).toHaveBeenCalledWith({ input: "hello" });
|
|
187
|
+
expect(result.content).toBeDefined();
|
|
188
|
+
expect(result.content[0].type).toBe("text");
|
|
189
|
+
const parsedResult = JSON.parse(result.content[0].text);
|
|
190
|
+
expect(parsedResult.received).toBe("hello");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("call unknown tool returns error", async () => {
|
|
194
|
+
const handlers: Record<string, Function> = {};
|
|
195
|
+
const mockServer = {
|
|
196
|
+
setRequestHandler: mock((schema: any, handler: Function) => {
|
|
197
|
+
if (schema.shape?.method?.value === "tools/list") {
|
|
198
|
+
handlers.list = handler;
|
|
199
|
+
} else if (schema.shape?.method?.value === "tools/call") {
|
|
200
|
+
handlers.call = handler;
|
|
201
|
+
}
|
|
202
|
+
}),
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
registerTools(mockServer as any, []);
|
|
206
|
+
|
|
207
|
+
const callHandler = handlers.call;
|
|
208
|
+
const result = await callHandler({
|
|
209
|
+
params: {
|
|
210
|
+
name: "nonexistent_tool",
|
|
211
|
+
arguments: {},
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(result.isError).toBe(true);
|
|
216
|
+
const parsedError = JSON.parse(result.content[0].text);
|
|
217
|
+
expect(parsedError.error).toBe(true);
|
|
218
|
+
expect(parsedError.code).toBe("UNKNOWN_TOOL");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("call tool with handler error returns error response", async () => {
|
|
222
|
+
const handlers: Record<string, Function> = {};
|
|
223
|
+
const mockServer = {
|
|
224
|
+
setRequestHandler: mock((schema: any, handler: Function) => {
|
|
225
|
+
if (schema.shape?.method?.value === "tools/list") {
|
|
226
|
+
handlers.list = handler;
|
|
227
|
+
} else if (schema.shape?.method?.value === "tools/call") {
|
|
228
|
+
handlers.call = handler;
|
|
229
|
+
}
|
|
230
|
+
}),
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const testTool: ToolDefinition = {
|
|
234
|
+
name: "failing_tool",
|
|
235
|
+
description: "A tool that fails",
|
|
236
|
+
inputSchema: z.object({}),
|
|
237
|
+
handler: async () => {
|
|
238
|
+
throw new Error("Something went wrong");
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
registerTools(mockServer as any, [testTool]);
|
|
243
|
+
|
|
244
|
+
const callHandler = handlers.call;
|
|
245
|
+
const result = await callHandler({
|
|
246
|
+
params: {
|
|
247
|
+
name: "failing_tool",
|
|
248
|
+
arguments: {},
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
expect(result.isError).toBe(true);
|
|
253
|
+
const parsedError = JSON.parse(result.content[0].text);
|
|
254
|
+
expect(parsedError.error).toBe(true);
|
|
255
|
+
expect(parsedError.code).toBe("TOOL_ERROR");
|
|
256
|
+
expect(parsedError.message).toBe("Something went wrong");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("call tool with non-Error throw returns string error", async () => {
|
|
260
|
+
const handlers: Record<string, Function> = {};
|
|
261
|
+
const mockServer = {
|
|
262
|
+
setRequestHandler: mock((schema: any, handler: Function) => {
|
|
263
|
+
if (schema.shape?.method?.value === "tools/list") {
|
|
264
|
+
handlers.list = handler;
|
|
265
|
+
} else if (schema.shape?.method?.value === "tools/call") {
|
|
266
|
+
handlers.call = handler;
|
|
267
|
+
}
|
|
268
|
+
}),
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const testTool: ToolDefinition = {
|
|
272
|
+
name: "string_throw_tool",
|
|
273
|
+
description: "A tool that throws a string",
|
|
274
|
+
inputSchema: z.object({}),
|
|
275
|
+
handler: async () => {
|
|
276
|
+
throw "String error message";
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
registerTools(mockServer as any, [testTool]);
|
|
281
|
+
|
|
282
|
+
const callHandler = handlers.call;
|
|
283
|
+
const result = await callHandler({
|
|
284
|
+
params: {
|
|
285
|
+
name: "string_throw_tool",
|
|
286
|
+
arguments: {},
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
expect(result.isError).toBe(true);
|
|
291
|
+
const parsedError = JSON.parse(result.content[0].text);
|
|
292
|
+
expect(parsedError.message).toBe("String error message");
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe("Tool JSON Schema Generation", () => {
|
|
298
|
+
test("generates correct JSON schema for complex input", () => {
|
|
299
|
+
const complexSchema = z.object({
|
|
300
|
+
required_string: z.string().min(1),
|
|
301
|
+
optional_number: z.number().optional(),
|
|
302
|
+
enum_field: z.enum(["A", "B", "C"]),
|
|
303
|
+
array_field: z.array(z.string()).optional(),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const jsonSchema = z.toJSONSchema(complexSchema);
|
|
307
|
+
|
|
308
|
+
expect(jsonSchema.type).toBe("object");
|
|
309
|
+
expect(jsonSchema.properties).toBeDefined();
|
|
310
|
+
expect(jsonSchema.required).toContain("required_string");
|
|
311
|
+
expect(jsonSchema.required).toContain("enum_field");
|
|
312
|
+
expect(jsonSchema.required).not.toContain("optional_number");
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe("Project Validation", () => {
|
|
317
|
+
const NO_PROJECT_DIR = `/tmp/flux-test-no-project-${Date.now()}`;
|
|
318
|
+
|
|
319
|
+
beforeEach(() => {
|
|
320
|
+
config.clearCache();
|
|
321
|
+
process.env.FLUX_PROJECT_ROOT = NO_PROJECT_DIR;
|
|
322
|
+
mkdirSync(NO_PROJECT_DIR, { recursive: true });
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
afterEach(() => {
|
|
326
|
+
config.clearCache();
|
|
327
|
+
if (existsSync(NO_PROJECT_DIR)) {
|
|
328
|
+
rmSync(NO_PROJECT_DIR, { recursive: true, force: true });
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("returns PROJECT_NOT_INITIALIZED with setup instructions for tools requiring project", async () => {
|
|
333
|
+
const handlers: Record<string, Function> = {};
|
|
334
|
+
const mockServer = {
|
|
335
|
+
setRequestHandler: mock((schema: any, handler: Function) => {
|
|
336
|
+
if (schema.shape?.method?.value === "tools/call") {
|
|
337
|
+
handlers.call = handler;
|
|
338
|
+
}
|
|
339
|
+
}),
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const testTool: ToolDefinition = {
|
|
343
|
+
name: "create_prd",
|
|
344
|
+
description: "Creates a PRD",
|
|
345
|
+
inputSchema: z.object({ title: z.string() }),
|
|
346
|
+
handler: async () => ({ success: true }),
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
registerTools(mockServer as any, [testTool]);
|
|
350
|
+
|
|
351
|
+
const callHandler = handlers.call;
|
|
352
|
+
const result = await callHandler({
|
|
353
|
+
params: {
|
|
354
|
+
name: "create_prd",
|
|
355
|
+
arguments: { title: "Test" },
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
expect(result.isError).toBe(true);
|
|
360
|
+
const parsedError = JSON.parse(result.content[0].text);
|
|
361
|
+
expect(parsedError.error).toBe(true);
|
|
362
|
+
expect(parsedError.code).toBe("PROJECT_NOT_INITIALIZED");
|
|
363
|
+
expect(parsedError.setup).toBeDefined();
|
|
364
|
+
expect(parsedError.setup.instructions).toBeDefined();
|
|
365
|
+
expect(parsedError.setup.options).toBeDefined();
|
|
366
|
+
expect(parsedError.setup.options.length).toBe(2);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("allows init_project without existing project", async () => {
|
|
370
|
+
const handlers: Record<string, Function> = {};
|
|
371
|
+
const mockServer = {
|
|
372
|
+
setRequestHandler: mock((schema: any, handler: Function) => {
|
|
373
|
+
if (schema.shape?.method?.value === "tools/call") {
|
|
374
|
+
handlers.call = handler;
|
|
375
|
+
}
|
|
376
|
+
}),
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const handlerMock = mock(async () => ({ success: true }));
|
|
380
|
+
|
|
381
|
+
const testTool: ToolDefinition = {
|
|
382
|
+
name: "init_project",
|
|
383
|
+
description: "Initialize project",
|
|
384
|
+
inputSchema: z.object({ name: z.string() }),
|
|
385
|
+
handler: handlerMock,
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
registerTools(mockServer as any, [testTool]);
|
|
389
|
+
|
|
390
|
+
const callHandler = handlers.call;
|
|
391
|
+
const result = await callHandler({
|
|
392
|
+
params: {
|
|
393
|
+
name: "init_project",
|
|
394
|
+
arguments: { name: "test" },
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
expect(result.isError).toBeUndefined();
|
|
399
|
+
expect(handlerMock).toHaveBeenCalled();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("allows get_version without existing project", async () => {
|
|
403
|
+
const handlers: Record<string, Function> = {};
|
|
404
|
+
const mockServer = {
|
|
405
|
+
setRequestHandler: mock((schema: any, handler: Function) => {
|
|
406
|
+
if (schema.shape?.method?.value === "tools/call") {
|
|
407
|
+
handlers.call = handler;
|
|
408
|
+
}
|
|
409
|
+
}),
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const handlerMock = mock(async () => ({ version: "1.0.0" }));
|
|
413
|
+
|
|
414
|
+
const testTool: ToolDefinition = {
|
|
415
|
+
name: "get_version",
|
|
416
|
+
description: "Get version",
|
|
417
|
+
inputSchema: z.object({}),
|
|
418
|
+
handler: handlerMock,
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
registerTools(mockServer as any, [testTool]);
|
|
422
|
+
|
|
423
|
+
const callHandler = handlers.call;
|
|
424
|
+
const result = await callHandler({
|
|
425
|
+
params: {
|
|
426
|
+
name: "get_version",
|
|
427
|
+
arguments: {},
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
expect(result.isError).toBeUndefined();
|
|
432
|
+
expect(handlerMock).toHaveBeenCalled();
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test("setup options have correct structure", async () => {
|
|
436
|
+
const handlers: Record<string, Function> = {};
|
|
437
|
+
const mockServer = {
|
|
438
|
+
setRequestHandler: mock((schema: any, handler: Function) => {
|
|
439
|
+
if (schema.shape?.method?.value === "tools/call") {
|
|
440
|
+
handlers.call = handler;
|
|
441
|
+
}
|
|
442
|
+
}),
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const testTool: ToolDefinition = {
|
|
446
|
+
name: "query_entities",
|
|
447
|
+
description: "Query entities",
|
|
448
|
+
inputSchema: z.object({ type: z.string() }),
|
|
449
|
+
handler: async () => ({ items: [] }),
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
registerTools(mockServer as any, [testTool]);
|
|
453
|
+
|
|
454
|
+
const callHandler = handlers.call;
|
|
455
|
+
const result = await callHandler({
|
|
456
|
+
params: {
|
|
457
|
+
name: "query_entities",
|
|
458
|
+
arguments: { type: "prd" },
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
expect(result.isError).toBe(true);
|
|
463
|
+
const parsedError = JSON.parse(result.content[0].text);
|
|
464
|
+
|
|
465
|
+
const commandOption = parsedError.setup.options.find(
|
|
466
|
+
(o: any) => o.method === "command",
|
|
467
|
+
);
|
|
468
|
+
expect(commandOption.name).toBe("/flux");
|
|
469
|
+
expect(commandOption.description).toBeTruthy();
|
|
470
|
+
|
|
471
|
+
const toolOption = parsedError.setup.options.find(
|
|
472
|
+
(o: any) => o.method === "tool",
|
|
473
|
+
);
|
|
474
|
+
expect(toolOption.name).toBe("init_project");
|
|
475
|
+
expect(toolOption.params.name).toBeTruthy();
|
|
476
|
+
expect(toolOption.params.vision).toBeTruthy();
|
|
477
|
+
expect(toolOption.params.adapter).toBeTruthy();
|
|
478
|
+
});
|
|
479
|
+
});
|