@opsee/mcp-server 0.9.34 → 0.9.35
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 +1 -1
- package/gen/api/v1/doc_space_pb.d.ts +11 -0
- package/gen/api/v1/doc_space_pb.js +1 -1
- package/package.json +2 -1
- package/src/tools/docs.ts +28 -27
- package/src/tools/initiatives.ts +2 -1
- package/src/tools/milestones.ts +5 -3
- package/src/utils/format/blocknote.ts +21 -0
- package/src/utils/format/index.ts +1 -1
- package/src/__tests__/blocknote-fixtures.test.ts +0 -58
- package/src/__tests__/blocknote-markdown.test.ts +0 -92
- package/src/__tests__/comments.test.ts +0 -193
- package/src/__tests__/initiatives.test.ts +0 -188
- package/src/__tests__/kv-store.test.ts +0 -53
- package/src/__tests__/task-dependencies.test.ts +0 -173
- package/src/__tests__/tools.test.ts +0 -913
- package/src/__tests__/work-logs.test.ts +0 -140
- package/src/auth/__tests__/manager.test.ts +0 -67
- package/src/auth/__tests__/oauth-provider.test.ts +0 -272
- package/src/utils/__tests__/format.test.ts +0 -53
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeAll, afterAll, beforeEach } from "vitest";
|
|
2
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
-
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
4
|
-
import { createServer } from "../server.js";
|
|
5
|
-
import type { ApiClients } from "../client/api.js";
|
|
6
|
-
|
|
7
|
-
const mockInitiative = {
|
|
8
|
-
id: 2,
|
|
9
|
-
uuid: "u-2",
|
|
10
|
-
title: "Async brain",
|
|
11
|
-
status: "active",
|
|
12
|
-
anchorType: "none",
|
|
13
|
-
coreIdea: "",
|
|
14
|
-
projectId: 1,
|
|
15
|
-
createdByUserId: 7,
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// Records the last addInitiativeMemoryEntry request so checkpoint tests can assert the mapping.
|
|
19
|
-
export let lastMemoryReq: any = null;
|
|
20
|
-
|
|
21
|
-
// Records the last getInitiatives request so list tests can assert the mapping.
|
|
22
|
-
export let lastListReq: any = null;
|
|
23
|
-
|
|
24
|
-
// Records the last reconcileInitiative request so reconcile tests can assert pass-through.
|
|
25
|
-
export let lastReconcileReq: any = null;
|
|
26
|
-
|
|
27
|
-
const mockListInitiative = {
|
|
28
|
-
id: 2, uuid: "u-2", title: "Auth refactor", status: "active",
|
|
29
|
-
anchorType: "none", coreIdea: "", projectId: 1, createdByUserId: 7,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
function createMockClients(): ApiClients {
|
|
33
|
-
lastMemoryReq = null;
|
|
34
|
-
lastListReq = null;
|
|
35
|
-
lastReconcileReq = null;
|
|
36
|
-
return {
|
|
37
|
-
initiatives: {
|
|
38
|
-
getInitiatives: async (req: any) => {
|
|
39
|
-
lastListReq = req;
|
|
40
|
-
return { initiatives: [mockListInitiative], pagination: {} };
|
|
41
|
-
},
|
|
42
|
-
getInitiativeContext: async () => ({
|
|
43
|
-
initiative: mockInitiative,
|
|
44
|
-
graph: undefined,
|
|
45
|
-
memoryEntries: [],
|
|
46
|
-
pullRequests: [],
|
|
47
|
-
comments: [],
|
|
48
|
-
}),
|
|
49
|
-
reconcileInitiative: async (req: any) => {
|
|
50
|
-
lastReconcileReq = req;
|
|
51
|
-
return {
|
|
52
|
-
changeset: {
|
|
53
|
-
added: [{ taskId: 0, identifier: "", title: "New", reason: "" }],
|
|
54
|
-
removed: [],
|
|
55
|
-
flagged: [{ taskId: 3, identifier: "T-3", title: "InFlight", reason: "has linked PR" }],
|
|
56
|
-
edgesAdded: [],
|
|
57
|
-
edgesRemoved: [],
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
},
|
|
61
|
-
addInitiativeMemoryEntry: async (req: any) => {
|
|
62
|
-
lastMemoryReq = req;
|
|
63
|
-
return {
|
|
64
|
-
memoryEntry: {
|
|
65
|
-
id: 99,
|
|
66
|
-
initiativeId: req.initiativeId,
|
|
67
|
-
kind: req.kind,
|
|
68
|
-
body: req.body,
|
|
69
|
-
contentType: req.contentType,
|
|
70
|
-
sourceType: req.sourceType,
|
|
71
|
-
sourceId: req.sourceId,
|
|
72
|
-
sourceUrl: req.sourceUrl,
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
} as unknown as ApiClients;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
describe("Initiative write-back discipline", () => {
|
|
81
|
-
let client: Client;
|
|
82
|
-
let cleanup: () => Promise<void>;
|
|
83
|
-
|
|
84
|
-
beforeAll(async () => {
|
|
85
|
-
const [ct, st] = InMemoryTransport.createLinkedPair();
|
|
86
|
-
const server = createServer(() => createMockClients());
|
|
87
|
-
await server.connect(st);
|
|
88
|
-
client = new Client({ name: "init-test", version: "1.0.0" });
|
|
89
|
-
await client.connect(ct);
|
|
90
|
-
cleanup = async () => { await client.close(); await server.close(); };
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
afterAll(async () => { await cleanup(); });
|
|
94
|
-
|
|
95
|
-
beforeEach(() => { lastMemoryReq = null; lastListReq = null; lastReconcileReq = null; });
|
|
96
|
-
|
|
97
|
-
test("get_initiative_context appends the working agreement", async () => {
|
|
98
|
-
const result = await client.callTool({
|
|
99
|
-
name: "opsee_get_initiative_context",
|
|
100
|
-
arguments: { initiativeId: 2 },
|
|
101
|
-
});
|
|
102
|
-
const text = (result.content as Array<{ type: string; text: string }>)[0].text;
|
|
103
|
-
expect(text).toContain("Working agreement");
|
|
104
|
-
expect(text).toContain("opsee_checkpoint");
|
|
105
|
-
expect(text).toContain("opsee_add_initiative_memory");
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("checkpoint writes an outcome entry by default with no source", async () => {
|
|
109
|
-
const result = await client.callTool({
|
|
110
|
-
name: "opsee_checkpoint",
|
|
111
|
-
arguments: { initiativeId: 2, summary: "Wired the parser" },
|
|
112
|
-
});
|
|
113
|
-
const text = (result.content as Array<{ type: string; text: string }>)[0].text;
|
|
114
|
-
expect(text).toContain("Checkpoint recorded");
|
|
115
|
-
expect(lastMemoryReq.kind).toBe("outcome");
|
|
116
|
-
expect(lastMemoryReq.body).toBe("Wired the parser");
|
|
117
|
-
expect(lastMemoryReq.sourceType).toBe("none");
|
|
118
|
-
// next-obligations nudge
|
|
119
|
-
expect(text).toContain("Link the PR");
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("checkpoint maps taskId to a task source", async () => {
|
|
123
|
-
await client.callTool({
|
|
124
|
-
name: "opsee_checkpoint",
|
|
125
|
-
arguments: { initiativeId: 2, summary: "done", taskId: 918 },
|
|
126
|
-
});
|
|
127
|
-
expect(lastMemoryReq.sourceType).toBe("task");
|
|
128
|
-
expect(lastMemoryReq.sourceId).toBe(918);
|
|
129
|
-
expect(lastMemoryReq.sourceUrl).toBeUndefined();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test("checkpoint maps prUrl to a pull_request source", async () => {
|
|
133
|
-
await client.callTool({
|
|
134
|
-
name: "opsee_checkpoint",
|
|
135
|
-
arguments: { initiativeId: 2, summary: "shipped", prUrl: "https://example.com/mr/94" },
|
|
136
|
-
});
|
|
137
|
-
expect(lastMemoryReq.sourceType).toBe("pull_request");
|
|
138
|
-
expect(lastMemoryReq.sourceUrl).toBe("https://example.com/mr/94");
|
|
139
|
-
expect(lastMemoryReq.sourceId).toBeUndefined();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("checkpoint with both taskId and prUrl prefers task source and keeps the url", async () => {
|
|
143
|
-
await client.callTool({
|
|
144
|
-
name: "opsee_checkpoint",
|
|
145
|
-
arguments: { initiativeId: 2, summary: "both", taskId: 918, prUrl: "https://example.com/mr/94" },
|
|
146
|
-
});
|
|
147
|
-
expect(lastMemoryReq.sourceType).toBe("task");
|
|
148
|
-
expect(lastMemoryReq.sourceId).toBe(918);
|
|
149
|
-
expect(lastMemoryReq.sourceUrl).toBe("https://example.com/mr/94");
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("checkpoint honors an explicit kind", async () => {
|
|
153
|
-
await client.callTool({
|
|
154
|
-
name: "opsee_checkpoint",
|
|
155
|
-
arguments: { initiativeId: 2, summary: "learned a thing", kind: "learning" },
|
|
156
|
-
});
|
|
157
|
-
expect(lastMemoryReq.kind).toBe("learning");
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
test("list_initiatives passes search through filterOptions", async () => {
|
|
161
|
-
await client.callTool({
|
|
162
|
-
name: "opsee_list_initiatives",
|
|
163
|
-
arguments: { projectId: 1, search: "auth" },
|
|
164
|
-
});
|
|
165
|
-
expect(lastListReq.filterOptions?.search).toBe("auth");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("list_initiatives omits filterOptions when no search", async () => {
|
|
169
|
-
await client.callTool({
|
|
170
|
-
name: "opsee_list_initiatives",
|
|
171
|
-
arguments: { projectId: 1 },
|
|
172
|
-
});
|
|
173
|
-
expect(lastListReq.filterOptions).toBeUndefined();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
test("reconcile passes dryRun + existingTaskId and renders flagged drops", async () => {
|
|
177
|
-
const result = await client.callTool({
|
|
178
|
-
name: "opsee_reconcile_initiative",
|
|
179
|
-
arguments: { initiativeId: 2, dryRun: true, tasks: [{ ref: "a", title: "Keep", existingTaskId: 1 }], edges: [] },
|
|
180
|
-
});
|
|
181
|
-
expect(lastReconcileReq.dryRun).toBe(true);
|
|
182
|
-
expect(lastReconcileReq.tasks[0].existingTaskId).toBe(1);
|
|
183
|
-
const text = (result.content as Array<{ type: string; text: string }>)[0].text;
|
|
184
|
-
expect(text).toContain("[DRY RUN");
|
|
185
|
-
expect(text).toContain("flagged");
|
|
186
|
-
expect(text).toContain("has linked PR");
|
|
187
|
-
});
|
|
188
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "vitest";
|
|
2
|
-
import { InMemoryKVStore } from "../auth/kv-store.js";
|
|
3
|
-
|
|
4
|
-
describe("InMemoryKVStore", () => {
|
|
5
|
-
test("stores and retrieves values", async () => {
|
|
6
|
-
const kv = new InMemoryKVStore();
|
|
7
|
-
await kv.set("a", "1");
|
|
8
|
-
expect(await kv.get("a")).toBe("1");
|
|
9
|
-
expect(await kv.get("missing")).toBeNull();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test("expires entries after their TTL", async () => {
|
|
13
|
-
const kv = new InMemoryKVStore();
|
|
14
|
-
await kv.set("a", "1", -1); // already expired
|
|
15
|
-
expect(await kv.get("a")).toBeNull();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("bounds the store to maxEntries, evicting oldest first", async () => {
|
|
19
|
-
const kv = new InMemoryKVStore(3);
|
|
20
|
-
await kv.set("a", "1");
|
|
21
|
-
await kv.set("b", "2");
|
|
22
|
-
await kv.set("c", "3");
|
|
23
|
-
await kv.set("d", "4"); // exceeds cap -> "a" (oldest) evicted
|
|
24
|
-
|
|
25
|
-
expect(await kv.get("a")).toBeNull();
|
|
26
|
-
expect(await kv.get("b")).toBe("2");
|
|
27
|
-
expect(await kv.get("c")).toBe("3");
|
|
28
|
-
expect(await kv.get("d")).toBe("4");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("re-setting a key refreshes its position so it is not evicted first", async () => {
|
|
32
|
-
const kv = new InMemoryKVStore(3);
|
|
33
|
-
await kv.set("a", "1");
|
|
34
|
-
await kv.set("b", "2");
|
|
35
|
-
await kv.set("a", "1-updated"); // refresh a -> b is now oldest
|
|
36
|
-
await kv.set("c", "3");
|
|
37
|
-
await kv.set("d", "4"); // evicts oldest live entry (b)
|
|
38
|
-
|
|
39
|
-
expect(await kv.get("b")).toBeNull();
|
|
40
|
-
expect(await kv.get("a")).toBe("1-updated");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("evicts expired entries before live ones when over cap", async () => {
|
|
44
|
-
const kv = new InMemoryKVStore(2);
|
|
45
|
-
await kv.set("stale", "x", -1); // expired
|
|
46
|
-
await kv.set("a", "1");
|
|
47
|
-
await kv.set("b", "2"); // over cap -> expired "stale" dropped, a/b kept
|
|
48
|
-
|
|
49
|
-
expect(await kv.get("stale")).toBeNull();
|
|
50
|
-
expect(await kv.get("a")).toBe("1");
|
|
51
|
-
expect(await kv.get("b")).toBe("2");
|
|
52
|
-
});
|
|
53
|
-
});
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeAll, afterAll, beforeEach } from "vitest";
|
|
2
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
-
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
4
|
-
import { createServer } from "../server.js";
|
|
5
|
-
import type { ApiClients } from "../client/api.js";
|
|
6
|
-
import { TaskDependencyType } from "../../gen/api/v1/models_pb.js";
|
|
7
|
-
|
|
8
|
-
const mockDependency = {
|
|
9
|
-
id: 7,
|
|
10
|
-
fromTaskId: 10,
|
|
11
|
-
toTaskId: 11,
|
|
12
|
-
type: TaskDependencyType.BLOCKS,
|
|
13
|
-
fromTask: { id: 10, identifier: "TP-1" },
|
|
14
|
-
toTask: { id: 11, identifier: "TP-2" },
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// Records the last request each mock method received so tests can assert pass-through.
|
|
18
|
-
let lastAddReq: any = null;
|
|
19
|
-
let lastListReq: any = null;
|
|
20
|
-
let lastChainReq: any = null;
|
|
21
|
-
|
|
22
|
-
function createMockClients(): ApiClients {
|
|
23
|
-
lastAddReq = null;
|
|
24
|
-
lastListReq = null;
|
|
25
|
-
lastChainReq = null;
|
|
26
|
-
return {
|
|
27
|
-
taskDependencies: {
|
|
28
|
-
addTaskDependency: async (req: any) => {
|
|
29
|
-
lastAddReq = req;
|
|
30
|
-
return { taskDependency: mockDependency };
|
|
31
|
-
},
|
|
32
|
-
getTaskDependencies: async (req: any) => {
|
|
33
|
-
lastListReq = req;
|
|
34
|
-
return { taskDependencies: [mockDependency] };
|
|
35
|
-
},
|
|
36
|
-
deleteTaskDependency: async () => ({}),
|
|
37
|
-
getDependencyChain: async (req: any) => {
|
|
38
|
-
lastChainReq = req;
|
|
39
|
-
return {
|
|
40
|
-
nodes: [
|
|
41
|
-
{ id: 10, identifier: "TP-1", title: "Fix the bug" },
|
|
42
|
-
{ id: 11, identifier: "TP-2", title: "Write the test" },
|
|
43
|
-
],
|
|
44
|
-
edges: [{ fromTaskId: 10, toTaskId: 11, type: TaskDependencyType.BLOCKS }],
|
|
45
|
-
};
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
} as unknown as ApiClients;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
describe("Task dependency tools", () => {
|
|
52
|
-
let client: Client;
|
|
53
|
-
let cleanup: () => Promise<void>;
|
|
54
|
-
|
|
55
|
-
beforeAll(async () => {
|
|
56
|
-
const [ct, st] = InMemoryTransport.createLinkedPair();
|
|
57
|
-
const server = createServer(() => createMockClients());
|
|
58
|
-
await server.connect(st);
|
|
59
|
-
client = new Client({ name: "task-dependencies-test", version: "1.0.0" });
|
|
60
|
-
await client.connect(ct);
|
|
61
|
-
cleanup = async () => {
|
|
62
|
-
await client.close();
|
|
63
|
-
await server.close();
|
|
64
|
-
};
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
afterAll(async () => {
|
|
68
|
-
await cleanup();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
beforeEach(() => {
|
|
72
|
-
lastAddReq = null;
|
|
73
|
-
lastListReq = null;
|
|
74
|
-
lastChainReq = null;
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("opsee_create_task_dependency maps type and returns the formatted dependency", async () => {
|
|
78
|
-
const result = await client.callTool({
|
|
79
|
-
name: "opsee_create_task_dependency",
|
|
80
|
-
arguments: { fromTaskId: 10, toTaskId: 11, type: "BLOCKS" },
|
|
81
|
-
});
|
|
82
|
-
const text = (result.content as Array<{ type: string; text: string }>)[0].text;
|
|
83
|
-
expect(text).toContain("Task dependency created");
|
|
84
|
-
expect(text).toContain("TP-1 BLOCKS TP-2");
|
|
85
|
-
expect(lastAddReq.type).toBe(TaskDependencyType.BLOCKS);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("opsee_create_task_dependency reports failure when backend returns no dependency", async () => {
|
|
89
|
-
const emptyClients = createMockClients();
|
|
90
|
-
(emptyClients.taskDependencies as any).addTaskDependency = async () => ({});
|
|
91
|
-
|
|
92
|
-
const [ct, st] = InMemoryTransport.createLinkedPair();
|
|
93
|
-
const server = createServer(() => emptyClients);
|
|
94
|
-
await server.connect(st);
|
|
95
|
-
const c = new Client({ name: "empty-add-test", version: "1.0.0" });
|
|
96
|
-
await c.connect(ct);
|
|
97
|
-
|
|
98
|
-
const result = await c.callTool({
|
|
99
|
-
name: "opsee_create_task_dependency",
|
|
100
|
-
arguments: { fromTaskId: 10, toTaskId: 11, type: "RELATES_TO" },
|
|
101
|
-
});
|
|
102
|
-
expect(result.isError).toBe(true);
|
|
103
|
-
const text = (result.content as Array<{ type: string; text: string }>)[0].text;
|
|
104
|
-
expect(text).toContain("Failed to create task dependency");
|
|
105
|
-
|
|
106
|
-
await c.close();
|
|
107
|
-
await server.close();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test("opsee_list_task_dependencies returns the formatted list and default pagination", async () => {
|
|
111
|
-
const result = await client.callTool({
|
|
112
|
-
name: "opsee_list_task_dependencies",
|
|
113
|
-
arguments: { taskId: 10 },
|
|
114
|
-
});
|
|
115
|
-
const text = (result.content as Array<{ type: string; text: string }>)[0].text;
|
|
116
|
-
expect(text).toContain("Found 1 dependency(ies)");
|
|
117
|
-
expect(text).toContain("TP-1 BLOCKS TP-2");
|
|
118
|
-
expect(lastListReq.taskId).toBe(10);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("opsee_list_task_dependencies reports none for an empty result", async () => {
|
|
122
|
-
const emptyClients = createMockClients();
|
|
123
|
-
(emptyClients.taskDependencies as any).getTaskDependencies = async () => ({ taskDependencies: [] });
|
|
124
|
-
|
|
125
|
-
const [ct, st] = InMemoryTransport.createLinkedPair();
|
|
126
|
-
const server = createServer(() => emptyClients);
|
|
127
|
-
await server.connect(st);
|
|
128
|
-
const c = new Client({ name: "empty-list-test", version: "1.0.0" });
|
|
129
|
-
await c.connect(ct);
|
|
130
|
-
|
|
131
|
-
const result = await c.callTool({
|
|
132
|
-
name: "opsee_list_task_dependencies",
|
|
133
|
-
arguments: { taskId: 10 },
|
|
134
|
-
});
|
|
135
|
-
const text = (result.content as Array<{ type: string; text: string }>)[0].text;
|
|
136
|
-
expect(text).toContain("No task dependencies found.");
|
|
137
|
-
|
|
138
|
-
await c.close();
|
|
139
|
-
await server.close();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("opsee_delete_task_dependency succeeds", async () => {
|
|
143
|
-
const result = await client.callTool({
|
|
144
|
-
name: "opsee_delete_task_dependency",
|
|
145
|
-
arguments: { dependencyId: 7 },
|
|
146
|
-
});
|
|
147
|
-
const text = (result.content as Array<{ type: string; text: string }>)[0].text;
|
|
148
|
-
expect(text).toContain("Task dependency deleted.");
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test("opsee_get_dependency_chain renders nodes and edges with defaults", async () => {
|
|
152
|
-
const result = await client.callTool({
|
|
153
|
-
name: "opsee_get_dependency_chain",
|
|
154
|
-
arguments: { taskId: 10 },
|
|
155
|
-
});
|
|
156
|
-
const text = (result.content as Array<{ type: string; text: string }>)[0].text;
|
|
157
|
-
expect(text).toContain("direction: both, depth: 5");
|
|
158
|
-
expect(text).toContain("2 task(s), 1 edge(s)");
|
|
159
|
-
expect(text).toContain("[TP-1] Fix the bug");
|
|
160
|
-
expect(text).toContain("task 10 → task 11 (type");
|
|
161
|
-
expect(lastChainReq.direction).toBe("both");
|
|
162
|
-
expect(lastChainReq.depth).toBe(5);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test("opsee_get_dependency_chain passes through an explicit direction and depth", async () => {
|
|
166
|
-
await client.callTool({
|
|
167
|
-
name: "opsee_get_dependency_chain",
|
|
168
|
-
arguments: { taskId: 10, direction: "blocked_by", depth: 2 },
|
|
169
|
-
});
|
|
170
|
-
expect(lastChainReq.direction).toBe("blocked_by");
|
|
171
|
-
expect(lastChainReq.depth).toBe(2);
|
|
172
|
-
});
|
|
173
|
-
});
|