@dpesch/mantisbt-mcp-server 1.10.3 → 1.11.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/CHANGELOG.md +41 -0
- package/README.de.md +11 -8
- package/README.md +11 -8
- package/dist/config.js +13 -0
- package/dist/index.js +12 -12
- package/dist/tools/files.js +11 -2
- package/dist/tools/issues.js +47 -15
- package/dist/tools/notes.js +15 -10
- package/docs/cookbook.de.md +67 -0
- package/docs/cookbook.md +67 -0
- package/docs/examples.de.md +8 -0
- package/docs/examples.md +8 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/.gitea/PULL_REQUEST_TEMPLATE.md +0 -17
- package/.gitea/workflows/ci.yml +0 -95
- package/.github/workflows/ci.yml +0 -32
- package/scripts/hooks/pre-push.mjs +0 -220
- package/scripts/init.mjs +0 -95
- package/scripts/record-fixtures.ts +0 -160
- package/tests/cache.test.ts +0 -265
- package/tests/client.test.ts +0 -372
- package/tests/config.test.ts +0 -263
- package/tests/fixtures/get_current_user.json +0 -40
- package/tests/fixtures/get_issue.json +0 -133
- package/tests/fixtures/get_issue_enums.json +0 -67
- package/tests/fixtures/get_issue_fields_sample.json +0 -76
- package/tests/fixtures/get_project_categories.json +0 -157
- package/tests/fixtures/get_project_versions.json +0 -3
- package/tests/fixtures/get_project_versions_with_data.json +0 -52
- package/tests/fixtures/list_issues.json +0 -95
- package/tests/fixtures/list_projects.json +0 -65
- package/tests/helpers/mock-server.ts +0 -166
- package/tests/helpers/search-mocks.ts +0 -84
- package/tests/prompts/prompts.test.ts +0 -242
- package/tests/resources/resources.test.ts +0 -309
- package/tests/search/embedder.test.ts +0 -81
- package/tests/search/highlight.test.ts +0 -129
- package/tests/search/store.test.ts +0 -193
- package/tests/search/sync.test.ts +0 -249
- package/tests/search/tools.test.ts +0 -661
- package/tests/tools/config.test.ts +0 -212
- package/tests/tools/files.test.ts +0 -343
- package/tests/tools/issues.test.ts +0 -1180
- package/tests/tools/metadata.test.ts +0 -509
- package/tests/tools/monitors.test.ts +0 -101
- package/tests/tools/projects.test.ts +0 -338
- package/tests/tools/relationships.test.ts +0 -177
- package/tests/tools/string-coercion.test.ts +0 -317
- package/tests/tools/users.test.ts +0 -62
- package/tests/utils/date-filter.test.ts +0 -169
- package/tests/version-hint.test.ts +0 -230
- package/tsconfig.build.json +0 -8
- package/vitest.config.ts +0 -8
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
export function makeResponse(status: number, body: string): Response {
|
|
2
|
-
return {
|
|
3
|
-
ok: status >= 200 && status < 300,
|
|
4
|
-
status,
|
|
5
|
-
statusText: `Status ${status}`,
|
|
6
|
-
text: () => Promise.resolve(body),
|
|
7
|
-
headers: { get: (_key: string) => null },
|
|
8
|
-
} as unknown as Response;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Typ für das Result-Objekt das die Tools zurückgeben
|
|
12
|
-
export interface ToolResult {
|
|
13
|
-
content: Array<{ type: string; text: string }>;
|
|
14
|
-
isError?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
import { z } from 'zod';
|
|
18
|
-
|
|
19
|
-
// Handler-Typ (args ist der Zod-geparste Input)
|
|
20
|
-
type ToolHandler = (args: Record<string, unknown>) => Promise<ToolResult>;
|
|
21
|
-
|
|
22
|
-
interface ToolDefinition {
|
|
23
|
-
inputSchema?: z.ZodTypeAny;
|
|
24
|
-
[key: string]: unknown;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface PromptMessage {
|
|
28
|
-
role: string;
|
|
29
|
-
content: { type: string; text: string };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface PromptResult {
|
|
33
|
-
messages: PromptMessage[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface ResourceResult {
|
|
37
|
-
contents: Array<{ uri: string; mimeType?: string; text: string }>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
type PromptHandler = (args: Record<string, unknown>) => PromptResult;
|
|
41
|
-
|
|
42
|
-
type ResourceHandler = (uri: URL, variables?: Record<string, string>) => Promise<ResourceResult>;
|
|
43
|
-
|
|
44
|
-
interface ResourceEntry {
|
|
45
|
-
handler: ResourceHandler;
|
|
46
|
-
/** URI template pattern, e.g. 'mantis://projects/{id}'. Present for template resources only. */
|
|
47
|
-
template?: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function matchTemplate(template: string, uri: string): Record<string, string> | null {
|
|
51
|
-
const names = [...template.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]!);
|
|
52
|
-
const pattern = template.replace(/\{[^}]+\}/g, '([^/]+)');
|
|
53
|
-
const match = uri.match(new RegExp(`^${pattern}$`));
|
|
54
|
-
if (!match) return null;
|
|
55
|
-
return Object.fromEntries(names.map((name, i) => [name, match[i + 1]!]));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface PromptDefinition {
|
|
59
|
-
argsSchema?: Record<string, z.ZodTypeAny>;
|
|
60
|
-
[key: string]: unknown;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export class MockMcpServer {
|
|
64
|
-
private readonly handlers = new Map<string, ToolHandler>();
|
|
65
|
-
private readonly schemas = new Map<string, z.ZodTypeAny>();
|
|
66
|
-
private readonly promptHandlers = new Map<string, PromptHandler>();
|
|
67
|
-
private readonly resourceEntries = new Map<string, ResourceEntry>();
|
|
68
|
-
|
|
69
|
-
// Nachahmt McpServer.registerTool – fängt Handler und Schema ein
|
|
70
|
-
registerTool(name: string, definition: ToolDefinition, handler: ToolHandler): void {
|
|
71
|
-
this.handlers.set(name, handler);
|
|
72
|
-
if (definition.inputSchema) {
|
|
73
|
-
this.schemas.set(name, definition.inputSchema);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Nachahmt McpServer.registerPrompt
|
|
78
|
-
registerPrompt(name: string, _definition: PromptDefinition, handler: PromptHandler): void {
|
|
79
|
-
this.promptHandlers.set(name, handler);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Ruft den Handler auf. Wenn `validate: true`, wird der Input zuerst
|
|
84
|
-
* durch das Zod-Schema geparst (wie der echte MCP-Server es tut).
|
|
85
|
-
* Das ermöglicht Tests für Coercion und Validierungsfehler.
|
|
86
|
-
*/
|
|
87
|
-
async callTool(
|
|
88
|
-
name: string,
|
|
89
|
-
args: Record<string, unknown> = {},
|
|
90
|
-
options: { validate?: boolean } = {},
|
|
91
|
-
): Promise<ToolResult> {
|
|
92
|
-
const handler = this.handlers.get(name);
|
|
93
|
-
if (!handler) throw new Error(`Tool not registered: ${name}`);
|
|
94
|
-
|
|
95
|
-
const schema = this.schemas.get(name);
|
|
96
|
-
if (schema) {
|
|
97
|
-
const parsed = schema.safeParse(args);
|
|
98
|
-
if (!parsed.success) {
|
|
99
|
-
if (options.validate) {
|
|
100
|
-
return {
|
|
101
|
-
content: [{ type: 'text', text: `Validation error: ${parsed.error.message}` }],
|
|
102
|
-
isError: true,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
} else {
|
|
106
|
-
return handler(parsed.data as Record<string, unknown>);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return handler(args);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
callPrompt(name: string, args: Record<string, unknown> = {}): PromptResult {
|
|
114
|
-
const handler = this.promptHandlers.get(name);
|
|
115
|
-
if (!handler) throw new Error(`Prompt not registered: ${name}`);
|
|
116
|
-
return handler(args);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
hasToolRegistered(name: string): boolean {
|
|
120
|
-
return this.handlers.has(name);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
hasPromptRegistered(name: string): boolean {
|
|
124
|
-
return this.promptHandlers.has(name);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
registeredToolNames(): string[] {
|
|
128
|
-
return [...this.handlers.keys()];
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
registeredPromptNames(): string[] {
|
|
132
|
-
return [...this.promptHandlers.keys()];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
136
|
-
registerResource(name: string, uriOrTemplate: any, _config: unknown, handler: ResourceHandler): void {
|
|
137
|
-
if (typeof uriOrTemplate === 'string') {
|
|
138
|
-
this.resourceEntries.set(uriOrTemplate, { handler });
|
|
139
|
-
} else {
|
|
140
|
-
// ResourceTemplate — uriTemplate getter returns a UriTemplate object; .toString() gives the pattern string
|
|
141
|
-
const templateStr: string = uriOrTemplate.uriTemplate?.toString() ?? name;
|
|
142
|
-
this.resourceEntries.set(templateStr, { handler, template: templateStr });
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async callResource(uri: string): Promise<ResourceResult> {
|
|
147
|
-
const exact = this.resourceEntries.get(uri);
|
|
148
|
-
if (exact) return exact.handler(new URL(uri), {});
|
|
149
|
-
|
|
150
|
-
for (const entry of this.resourceEntries.values()) {
|
|
151
|
-
if (!entry.template) continue;
|
|
152
|
-
const variables = matchTemplate(entry.template, uri);
|
|
153
|
-
if (variables) return entry.handler(new URL(uri), variables);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
throw new Error(`Resource not registered: ${uri}`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
hasResourceRegistered(uri: string): boolean {
|
|
160
|
-
return this.resourceEntries.has(uri);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
registeredResourceUris(): string[] {
|
|
164
|
-
return [...this.resourceEntries.keys()];
|
|
165
|
-
}
|
|
166
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { vi } from 'vitest';
|
|
2
|
-
import type { VectorStore, VectorStoreItem } from '../../src/search/store.js';
|
|
3
|
-
import type { Embedder } from '../../src/search/embedder.js';
|
|
4
|
-
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// Shared constants
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
export const MOCK_VECTOR = Array(384).fill(0.1) as number[];
|
|
10
|
-
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
// makeMockStore
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
|
|
15
|
-
export interface MockStoreItem {
|
|
16
|
-
id: number;
|
|
17
|
-
score?: number;
|
|
18
|
-
updated_at?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function makeMockStore(options?: {
|
|
22
|
-
lastSyncedAt?: string | null;
|
|
23
|
-
itemCount?: number;
|
|
24
|
-
lastKnownTotal?: number | null;
|
|
25
|
-
items?: MockStoreItem[];
|
|
26
|
-
}): VectorStore {
|
|
27
|
-
const lastSyncedAt = options?.lastSyncedAt ?? null;
|
|
28
|
-
const seedItems = options?.items ?? null;
|
|
29
|
-
const addedItems: VectorStoreItem[] = [];
|
|
30
|
-
const itemMap = new Map<number, VectorStoreItem>(
|
|
31
|
-
(seedItems ?? []).map(i => [i.id, {
|
|
32
|
-
id: i.id,
|
|
33
|
-
vector: MOCK_VECTOR,
|
|
34
|
-
metadata: { summary: `Issue ${i.id}`, updated_at: i.updated_at },
|
|
35
|
-
}])
|
|
36
|
-
);
|
|
37
|
-
let count = options?.itemCount ?? seedItems?.length ?? 0;
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
add: vi.fn(async (item: VectorStoreItem) => {
|
|
41
|
-
addedItems.push(item);
|
|
42
|
-
count++;
|
|
43
|
-
}),
|
|
44
|
-
addBatch: vi.fn(async (items: VectorStoreItem[]) => {
|
|
45
|
-
for (const item of items) {
|
|
46
|
-
addedItems.push(item);
|
|
47
|
-
}
|
|
48
|
-
count += items.length;
|
|
49
|
-
}),
|
|
50
|
-
search: vi.fn(async (_vec: number[], topN: number) => {
|
|
51
|
-
if (seedItems) {
|
|
52
|
-
return seedItems.slice(0, topN).map(i => ({ id: i.id, score: i.score ?? 0.9 }));
|
|
53
|
-
}
|
|
54
|
-
return Array.from({ length: Math.min(topN, count) }, (_, i) => ({
|
|
55
|
-
id: i + 1,
|
|
56
|
-
score: 1 - i * 0.1,
|
|
57
|
-
}));
|
|
58
|
-
}),
|
|
59
|
-
getItem: vi.fn(async (id: number) => itemMap.get(id) ?? null),
|
|
60
|
-
delete: vi.fn(async () => {}),
|
|
61
|
-
count: vi.fn(async () => count),
|
|
62
|
-
clear: vi.fn(async () => {
|
|
63
|
-
addedItems.splice(0);
|
|
64
|
-
count = 0;
|
|
65
|
-
}),
|
|
66
|
-
getLastSyncedAt: vi.fn(async () => lastSyncedAt),
|
|
67
|
-
setLastSyncedAt: vi.fn(async () => {}),
|
|
68
|
-
resetLastSyncedAt: vi.fn(async () => {}),
|
|
69
|
-
getLastKnownTotal: vi.fn(async () => options?.lastKnownTotal ?? null),
|
|
70
|
-
setLastKnownTotal: vi.fn(async () => {}),
|
|
71
|
-
flush: vi.fn(async () => {}),
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
|
-
// makeMockEmbedder
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
|
|
79
|
-
export function makeMockEmbedder(): Embedder {
|
|
80
|
-
return {
|
|
81
|
-
embed: vi.fn(async () => MOCK_VECTOR),
|
|
82
|
-
embedBatch: vi.fn(async (texts: string[]) => texts.map(() => MOCK_VECTOR)),
|
|
83
|
-
} as unknown as Embedder;
|
|
84
|
-
}
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { registerPrompts } from '../../src/prompts/index.js';
|
|
3
|
-
import { MockMcpServer } from '../helpers/mock-server.js';
|
|
4
|
-
|
|
5
|
-
let mockServer: MockMcpServer;
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
mockServer = new MockMcpServer();
|
|
9
|
-
registerPrompts(mockServer as never);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
// Registration
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
describe('prompt registration', () => {
|
|
17
|
-
it('registers create-bug-report', () => {
|
|
18
|
-
expect(mockServer.hasPromptRegistered('create-bug-report')).toBe(true);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('registers create-feature-request', () => {
|
|
22
|
-
expect(mockServer.hasPromptRegistered('create-feature-request')).toBe(true);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('registers summarize-issue', () => {
|
|
26
|
-
expect(mockServer.hasPromptRegistered('summarize-issue')).toBe(true);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('registers project-status', () => {
|
|
30
|
-
expect(mockServer.hasPromptRegistered('project-status')).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
// create-bug-report
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
|
|
38
|
-
describe('create-bug-report', () => {
|
|
39
|
-
it('returns a single user message', () => {
|
|
40
|
-
const result = mockServer.callPrompt('create-bug-report', {
|
|
41
|
-
project_id: 1,
|
|
42
|
-
category: 'General',
|
|
43
|
-
summary: 'Login fails',
|
|
44
|
-
description: 'Cannot log in after password reset',
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
expect(result.messages).toHaveLength(1);
|
|
48
|
-
expect(result.messages[0]!.role).toBe('user');
|
|
49
|
-
expect(result.messages[0]!.content.type).toBe('text');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('includes project_id, category, summary, and description in the text', () => {
|
|
53
|
-
const result = mockServer.callPrompt('create-bug-report', {
|
|
54
|
-
project_id: 7,
|
|
55
|
-
category: 'Authentication',
|
|
56
|
-
summary: 'Session expires too early',
|
|
57
|
-
description: 'Session is invalidated after 5 minutes of inactivity',
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const text = result.messages[0]!.content.text;
|
|
61
|
-
expect(text).toContain('project 7');
|
|
62
|
-
expect(text).toContain('Authentication');
|
|
63
|
-
expect(text).toContain('Session expires too early');
|
|
64
|
-
expect(text).toContain('Session is invalidated after 5 minutes');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('includes optional steps_to_reproduce when provided', () => {
|
|
68
|
-
const result = mockServer.callPrompt('create-bug-report', {
|
|
69
|
-
project_id: 1,
|
|
70
|
-
category: 'General',
|
|
71
|
-
summary: 'Bug',
|
|
72
|
-
description: 'Desc',
|
|
73
|
-
steps_to_reproduce: '1. Open app\n2. Click login',
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
expect(result.messages[0]!.content.text).toContain('Steps to reproduce');
|
|
77
|
-
expect(result.messages[0]!.content.text).toContain('1. Open app');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('omits optional sections when not provided', () => {
|
|
81
|
-
const result = mockServer.callPrompt('create-bug-report', {
|
|
82
|
-
project_id: 1,
|
|
83
|
-
category: 'General',
|
|
84
|
-
summary: 'Bug',
|
|
85
|
-
description: 'Desc',
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const text = result.messages[0]!.content.text;
|
|
89
|
-
expect(text).not.toContain('Steps to reproduce');
|
|
90
|
-
expect(text).not.toContain('Expected behavior');
|
|
91
|
-
expect(text).not.toContain('Actual behavior');
|
|
92
|
-
expect(text).not.toContain('Environment');
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('includes all optional fields when provided', () => {
|
|
96
|
-
const result = mockServer.callPrompt('create-bug-report', {
|
|
97
|
-
project_id: 1,
|
|
98
|
-
category: 'UI',
|
|
99
|
-
summary: 'Button broken',
|
|
100
|
-
description: 'Save button does nothing',
|
|
101
|
-
steps_to_reproduce: 'Click Save',
|
|
102
|
-
expected: 'Form is saved',
|
|
103
|
-
actual: 'Nothing happens',
|
|
104
|
-
environment: 'Chrome 120, Windows 11',
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const text = result.messages[0]!.content.text;
|
|
108
|
-
expect(text).toContain('Steps to reproduce');
|
|
109
|
-
expect(text).toContain('Expected behavior');
|
|
110
|
-
expect(text).toContain('Actual behavior');
|
|
111
|
-
expect(text).toContain('Environment');
|
|
112
|
-
expect(text).toContain('Chrome 120');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('mentions create_issue tool', () => {
|
|
116
|
-
const result = mockServer.callPrompt('create-bug-report', {
|
|
117
|
-
project_id: 1,
|
|
118
|
-
category: 'General',
|
|
119
|
-
summary: 'Bug',
|
|
120
|
-
description: 'Desc',
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
expect(result.messages[0]!.content.text).toContain('create_issue');
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// ---------------------------------------------------------------------------
|
|
128
|
-
// create-feature-request
|
|
129
|
-
// ---------------------------------------------------------------------------
|
|
130
|
-
|
|
131
|
-
describe('create-feature-request', () => {
|
|
132
|
-
it('returns a single user message', () => {
|
|
133
|
-
const result = mockServer.callPrompt('create-feature-request', {
|
|
134
|
-
project_id: 2,
|
|
135
|
-
category: 'General',
|
|
136
|
-
summary: 'Dark mode',
|
|
137
|
-
description: 'Add a dark mode toggle to the UI',
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
expect(result.messages).toHaveLength(1);
|
|
141
|
-
expect(result.messages[0]!.role).toBe('user');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('includes project_id, category, summary, and description', () => {
|
|
145
|
-
const result = mockServer.callPrompt('create-feature-request', {
|
|
146
|
-
project_id: 5,
|
|
147
|
-
category: 'UX',
|
|
148
|
-
summary: 'Export to CSV',
|
|
149
|
-
description: 'Allow exporting issue lists as CSV',
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
const text = result.messages[0]!.content.text;
|
|
153
|
-
expect(text).toContain('project 5');
|
|
154
|
-
expect(text).toContain('UX');
|
|
155
|
-
expect(text).toContain('Export to CSV');
|
|
156
|
-
expect(text).toContain('Allow exporting issue lists');
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('includes use_case when provided', () => {
|
|
160
|
-
const result = mockServer.callPrompt('create-feature-request', {
|
|
161
|
-
project_id: 1,
|
|
162
|
-
category: 'General',
|
|
163
|
-
summary: 'Feature',
|
|
164
|
-
description: 'Desc',
|
|
165
|
-
use_case: 'Needed for monthly reporting',
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
expect(result.messages[0]!.content.text).toContain('Needed for monthly reporting');
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('omits use_case section when not provided', () => {
|
|
172
|
-
const result = mockServer.callPrompt('create-feature-request', {
|
|
173
|
-
project_id: 1,
|
|
174
|
-
category: 'General',
|
|
175
|
-
summary: 'Feature',
|
|
176
|
-
description: 'Desc',
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
expect(result.messages[0]!.content.text).not.toContain('Use case');
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('mentions create_issue tool', () => {
|
|
183
|
-
const result = mockServer.callPrompt('create-feature-request', {
|
|
184
|
-
project_id: 1,
|
|
185
|
-
category: 'General',
|
|
186
|
-
summary: 'Feature',
|
|
187
|
-
description: 'Desc',
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
expect(result.messages[0]!.content.text).toContain('create_issue');
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// ---------------------------------------------------------------------------
|
|
195
|
-
// summarize-issue
|
|
196
|
-
// ---------------------------------------------------------------------------
|
|
197
|
-
|
|
198
|
-
describe('summarize-issue', () => {
|
|
199
|
-
it('returns a single user message', () => {
|
|
200
|
-
const result = mockServer.callPrompt('summarize-issue', { issue_id: 42 });
|
|
201
|
-
|
|
202
|
-
expect(result.messages).toHaveLength(1);
|
|
203
|
-
expect(result.messages[0]!.role).toBe('user');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('includes the issue ID in the text', () => {
|
|
207
|
-
const result = mockServer.callPrompt('summarize-issue', { issue_id: 1234 });
|
|
208
|
-
|
|
209
|
-
expect(result.messages[0]!.content.text).toContain('1234');
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('mentions get_issue tool', () => {
|
|
213
|
-
const result = mockServer.callPrompt('summarize-issue', { issue_id: 1 });
|
|
214
|
-
|
|
215
|
-
expect(result.messages[0]!.content.text).toContain('get_issue');
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
// ---------------------------------------------------------------------------
|
|
220
|
-
// project-status
|
|
221
|
-
// ---------------------------------------------------------------------------
|
|
222
|
-
|
|
223
|
-
describe('project-status', () => {
|
|
224
|
-
it('returns a single user message', () => {
|
|
225
|
-
const result = mockServer.callPrompt('project-status', { project_id: 3 });
|
|
226
|
-
|
|
227
|
-
expect(result.messages).toHaveLength(1);
|
|
228
|
-
expect(result.messages[0]!.role).toBe('user');
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('includes the project ID in the text', () => {
|
|
232
|
-
const result = mockServer.callPrompt('project-status', { project_id: 99 });
|
|
233
|
-
|
|
234
|
-
expect(result.messages[0]!.content.text).toContain('99');
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it('mentions list_issues tool', () => {
|
|
238
|
-
const result = mockServer.callPrompt('project-status', { project_id: 1 });
|
|
239
|
-
|
|
240
|
-
expect(result.messages[0]!.content.text).toContain('list_issues');
|
|
241
|
-
});
|
|
242
|
-
});
|