agent-issues 0.0.121 → 0.0.127

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/dist/build-info.json +3 -0
  2. package/dist/cli.js +97746 -26
  3. package/kanban/dist/assets/index-CIAg6qj5.css +1 -0
  4. package/kanban/dist/assets/index-DaYQjJS7.js +920 -0
  5. package/kanban/dist/index.html +13 -0
  6. package/package.json +12 -11
  7. package/site/dist/assets/{index-Cx-fLU3e.js → index-BRvRYF7I.js} +195 -146
  8. package/site/dist/index.html +1 -1
  9. package/dist/agent-installer.d.ts +0 -49
  10. package/dist/agent-installer.js +0 -171
  11. package/dist/agent-installer.js.map +0 -1
  12. package/dist/auth-session.d.ts +0 -40
  13. package/dist/auth-session.js +0 -107
  14. package/dist/auth-session.js.map +0 -1
  15. package/dist/body-backfill.d.ts +0 -26
  16. package/dist/body-backfill.js +0 -219
  17. package/dist/body-backfill.js.map +0 -1
  18. package/dist/build-mode.d.ts +0 -1
  19. package/dist/build-mode.js +0 -1
  20. package/dist/build-mode.js.map +0 -1
  21. package/dist/cli/commands/auth.d.ts +0 -61
  22. package/dist/cli/commands/auth.js +0 -134
  23. package/dist/cli/commands/auth.js.map +0 -1
  24. package/dist/cli/commands/backfill.d.ts +0 -8
  25. package/dist/cli/commands/backfill.js +0 -36
  26. package/dist/cli/commands/backfill.js.map +0 -1
  27. package/dist/cli/commands/comments.d.ts +0 -34
  28. package/dist/cli/commands/comments.js +0 -124
  29. package/dist/cli/commands/comments.js.map +0 -1
  30. package/dist/cli/commands/context.d.ts +0 -13
  31. package/dist/cli/commands/context.js +0 -156
  32. package/dist/cli/commands/context.js.map +0 -1
  33. package/dist/cli/commands/entities.d.ts +0 -91
  34. package/dist/cli/commands/entities.js +0 -420
  35. package/dist/cli/commands/entities.js.map +0 -1
  36. package/dist/cli/commands/export.d.ts +0 -20
  37. package/dist/cli/commands/export.js +0 -98
  38. package/dist/cli/commands/export.js.map +0 -1
  39. package/dist/cli/commands/fallback.d.ts +0 -6
  40. package/dist/cli/commands/fallback.js +0 -10
  41. package/dist/cli/commands/fallback.js.map +0 -1
  42. package/dist/cli/commands/handoff.d.ts +0 -1
  43. package/dist/cli/commands/handoff.js +0 -2
  44. package/dist/cli/commands/handoff.js.map +0 -1
  45. package/dist/cli/commands/installers.d.ts +0 -37
  46. package/dist/cli/commands/installers.js +0 -78
  47. package/dist/cli/commands/installers.js.map +0 -1
  48. package/dist/cli/commands/kanban.d.ts +0 -8
  49. package/dist/cli/commands/kanban.js +0 -83
  50. package/dist/cli/commands/kanban.js.map +0 -1
  51. package/dist/cli/commands/meta.d.ts +0 -14
  52. package/dist/cli/commands/meta.js +0 -33
  53. package/dist/cli/commands/meta.js.map +0 -1
  54. package/dist/cli/commands/plan-entries.d.ts +0 -30
  55. package/dist/cli/commands/plan-entries.js +0 -94
  56. package/dist/cli/commands/plan-entries.js.map +0 -1
  57. package/dist/cli/commands/site.d.ts +0 -29
  58. package/dist/cli/commands/site.js +0 -89
  59. package/dist/cli/commands/site.js.map +0 -1
  60. package/dist/cli/commands/sql.d.ts +0 -6
  61. package/dist/cli/commands/sql.js +0 -21
  62. package/dist/cli/commands/sql.js.map +0 -1
  63. package/dist/cli/commands/synchronize.d.ts +0 -12
  64. package/dist/cli/commands/synchronize.js +0 -31
  65. package/dist/cli/commands/synchronize.js.map +0 -1
  66. package/dist/cli/commands/tenants.d.ts +0 -29
  67. package/dist/cli/commands/tenants.js +0 -101
  68. package/dist/cli/commands/tenants.js.map +0 -1
  69. package/dist/cli/index.d.ts +0 -4
  70. package/dist/cli/index.js +0 -120
  71. package/dist/cli/index.js.map +0 -1
  72. package/dist/cli/renderers.d.ts +0 -250
  73. package/dist/cli/renderers.js +0 -286
  74. package/dist/cli/renderers.js.map +0 -1
  75. package/dist/cli/shared.d.ts +0 -97
  76. package/dist/cli/shared.js +0 -192
  77. package/dist/cli/shared.js.map +0 -1
  78. package/dist/cli.d.ts +0 -11
  79. package/dist/cli.js.map +0 -1
  80. package/dist/context-cli.d.ts +0 -16
  81. package/dist/context-cli.js +0 -131
  82. package/dist/context-cli.js.map +0 -1
  83. package/dist/daemon/daemon-lifecycle.d.ts +0 -1
  84. package/dist/daemon/daemon-lifecycle.js +0 -2
  85. package/dist/daemon/daemon-lifecycle.js.map +0 -1
  86. package/dist/daemon/daemon-main.d.ts +0 -14
  87. package/dist/daemon/daemon-main.js +0 -30
  88. package/dist/daemon/daemon-main.js.map +0 -1
  89. package/dist/daemon/local-daemon-store.d.ts +0 -21
  90. package/dist/daemon/local-daemon-store.js +0 -31
  91. package/dist/daemon/local-daemon-store.js.map +0 -1
  92. package/dist/entity-projection.d.ts +0 -141
  93. package/dist/entity-projection.js +0 -147
  94. package/dist/entity-projection.js.map +0 -1
  95. package/dist/entra-device-login.d.ts +0 -12
  96. package/dist/entra-device-login.js +0 -40
  97. package/dist/entra-device-login.js.map +0 -1
  98. package/dist/export-files.d.ts +0 -24
  99. package/dist/export-files.js +0 -230
  100. package/dist/export-files.js.map +0 -1
  101. package/dist/export-markdown.d.ts +0 -24
  102. package/dist/export-markdown.js +0 -343
  103. package/dist/export-markdown.js.map +0 -1
  104. package/dist/help.d.ts +0 -112
  105. package/dist/help.js +0 -1430
  106. package/dist/help.js.map +0 -1
  107. package/dist/kanban/assets.d.ts +0 -3
  108. package/dist/kanban/assets.js +0 -37
  109. package/dist/kanban/assets.js.map +0 -1
  110. package/dist/kanban/index.d.ts +0 -1
  111. package/dist/kanban/index.js +0 -2
  112. package/dist/kanban/index.js.map +0 -1
  113. package/dist/kanban/server.d.ts +0 -28
  114. package/dist/kanban/server.js +0 -118
  115. package/dist/kanban/server.js.map +0 -1
  116. package/dist/mcp-installer.d.ts +0 -32
  117. package/dist/mcp-installer.js +0 -118
  118. package/dist/mcp-installer.js.map +0 -1
  119. package/dist/mcp-server/index.d.ts +0 -11
  120. package/dist/mcp-server/index.js +0 -504
  121. package/dist/mcp-server/index.js.map +0 -1
  122. package/dist/mcp-server/index.test.d.ts +0 -1
  123. package/dist/mcp-server/index.test.js +0 -746
  124. package/dist/mcp-server/index.test.js.map +0 -1
  125. package/dist/mcp-server/mcp-tool-audit.d.ts +0 -6
  126. package/dist/mcp-server/mcp-tool-audit.js +0 -47
  127. package/dist/mcp-server/mcp-tool-audit.js.map +0 -1
  128. package/dist/mcp-server/stdio.d.ts +0 -2
  129. package/dist/mcp-server/stdio.js +0 -7
  130. package/dist/mcp-server/stdio.js.map +0 -1
  131. package/dist/mcp.d.ts +0 -2
  132. package/dist/mcp.js +0 -19
  133. package/dist/mcp.js.map +0 -1
  134. package/dist/open-storage-driver.d.ts +0 -55
  135. package/dist/open-storage-driver.js +0 -73
  136. package/dist/open-storage-driver.js.map +0 -1
  137. package/dist/open-synchronize-stores.d.ts +0 -24
  138. package/dist/open-synchronize-stores.js +0 -34
  139. package/dist/open-synchronize-stores.js.map +0 -1
  140. package/dist/project-identity.d.ts +0 -23
  141. package/dist/project-identity.js +0 -121
  142. package/dist/project-identity.js.map +0 -1
  143. package/dist/runtime-access-boundary.d.ts +0 -7
  144. package/dist/runtime-access-boundary.js +0 -182
  145. package/dist/runtime-access-boundary.js.map +0 -1
  146. package/dist/service-discovery.d.ts +0 -10
  147. package/dist/service-discovery.js +0 -64
  148. package/dist/service-discovery.js.map +0 -1
  149. package/dist/site/assets.d.ts +0 -4
  150. package/dist/site/assets.js +0 -46
  151. package/dist/site/assets.js.map +0 -1
  152. package/dist/site/cloud-events-relay.d.ts +0 -16
  153. package/dist/site/cloud-events-relay.js +0 -78
  154. package/dist/site/cloud-events-relay.js.map +0 -1
  155. package/dist/site/index.d.ts +0 -1
  156. package/dist/site/index.js +0 -2
  157. package/dist/site/index.js.map +0 -1
  158. package/dist/site/server.d.ts +0 -42
  159. package/dist/site/server.js +0 -632
  160. package/dist/site/server.js.map +0 -1
  161. package/dist/skill-installer.d.ts +0 -35
  162. package/dist/skill-installer.js +0 -180
  163. package/dist/skill-installer.js.map +0 -1
  164. package/dist/vitest-setup.d.ts +0 -1
  165. package/dist/vitest-setup.js +0 -7
  166. package/dist/vitest-setup.js.map +0 -1
@@ -1,746 +0,0 @@
1
- import { mkdtempSync, rmSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import path from "node:path";
4
- import { afterEach, describe, expect, it } from "vitest";
5
- import { createLocalDaemonServer, openSqliteStore } from "@agent-issues/api-local";
6
- import { resolveWellKnownLocalTenantId } from "@agent-issues/core";
7
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
8
- import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
9
- import { auditMcpToolRegistrations } from "./mcp-tool-audit.js";
10
- import { createLocalMcpServer, createMcpServer } from "./index.js";
11
- function fakeCredentialStore() {
12
- const store = new Map();
13
- return {
14
- platform: "darwin",
15
- runCommand: async (command) => {
16
- const [action, , account, , service] = command.args;
17
- const key = `${service}:${account}`;
18
- if (action === "add-generic-password") {
19
- store.set(key, command.args[6]);
20
- return { stdout: "", exitCode: 0 };
21
- }
22
- if (action === "find-generic-password") {
23
- const value = store.get(key);
24
- return value === undefined ? { stdout: "", exitCode: 44 } : { stdout: `${value}\n`, exitCode: 0 };
25
- }
26
- const existed = store.delete(key);
27
- return { stdout: "", exitCode: existed ? 0 : 44 };
28
- }
29
- };
30
- }
31
- describe("agent-issues MCP server", () => {
32
- const directories = [];
33
- afterEach(() => {
34
- for (const directory of directories.splice(0)) {
35
- rmSync(directory, { recursive: true, force: true });
36
- }
37
- });
38
- it("registers a tool for every included tracker data command", async () => {
39
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
40
- directories.push(directory);
41
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
42
- const server = createMcpServer({ openStore: async () => store });
43
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
44
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
45
- await server.connect(serverTransport);
46
- await client.connect(clientTransport);
47
- const tools = await client.listTools();
48
- expect(auditMcpToolRegistrations(tools.tools.map((tool) => tool.name))).toEqual({ missing: [] });
49
- await client.close();
50
- await server.close();
51
- await store.close();
52
- });
53
- it("reports the resolved project identity", async () => {
54
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
55
- directories.push(directory);
56
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
57
- const server = createMcpServer({ openStore: async () => store, projectIdentity: "shared-product" });
58
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
59
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
60
- await server.connect(serverTransport);
61
- await client.connect(clientTransport);
62
- const result = await client.callTool({ name: "project_identity", arguments: {} });
63
- expect(result).toMatchObject({ structuredContent: { projectIdentity: "shared-product" } });
64
- await client.close();
65
- await server.close();
66
- await store.close();
67
- });
68
- it("reports a null project identity when no identity is configured", async () => {
69
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
70
- directories.push(directory);
71
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
72
- const server = createMcpServer({ openStore: async () => store });
73
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
74
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
75
- await server.connect(serverTransport);
76
- await client.connect(clientTransport);
77
- const result = await client.callTool({ name: "project_identity", arguments: {} });
78
- expect(result).toMatchObject({ structuredContent: { projectIdentity: null } });
79
- await client.close();
80
- await server.close();
81
- await store.close();
82
- });
83
- it("creates an entity through entity_create", async () => {
84
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
85
- directories.push(directory);
86
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
87
- const server = createMcpServer({ openStore: async () => store });
88
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
89
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
90
- await server.connect(serverTransport);
91
- await client.connect(clientTransport);
92
- const result = await client.callTool({ name: "entity_create", arguments: { kind: "issue", title: "Create through MCP", body: "Authored body" } });
93
- expect(result).toMatchObject({ structuredContent: { entity: { kind: "issue", title: "Create through MCP" } } });
94
- expect(result.structuredContent.entity).not.toHaveProperty("body");
95
- expect(result.structuredContent.entity).not.toHaveProperty("bodySource");
96
- await client.close();
97
- await server.close();
98
- await store.close();
99
- });
100
- it("edits an entity through entity_edit with its expected revision", async () => {
101
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
102
- directories.push(directory);
103
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
104
- const entity = await store.createEntity({ kind: "issue", title: "Original title", body: "Original body" });
105
- const server = createMcpServer({ openStore: async () => store });
106
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
107
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
108
- await server.connect(serverTransport);
109
- await client.connect(clientTransport);
110
- const result = await client.callTool({
111
- name: "entity_edit",
112
- arguments: {
113
- entityId: entity.reference,
114
- title: "Edited title",
115
- expectedRevision: entity.revision,
116
- expectedContentHash: entity.contentHash
117
- }
118
- });
119
- expect(result).toMatchObject({ structuredContent: { entity: { reference: entity.reference, title: "Edited title", revision: 2 } } });
120
- await client.close();
121
- await server.close();
122
- await store.close();
123
- });
124
- it("discovers the entity and graph command tools", async () => {
125
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
126
- directories.push(directory);
127
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
128
- const server = createMcpServer({ openStore: async () => store });
129
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
130
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
131
- await server.connect(serverTransport);
132
- await client.connect(clientTransport);
133
- const tools = await client.listTools();
134
- expect(tools.tools.map((tool) => tool.name)).toEqual(expect.arrayContaining([
135
- "entity_create",
136
- "entity_edit",
137
- "entity_archive",
138
- "entity_delete",
139
- "entity_history",
140
- "entity_list",
141
- "entity_move",
142
- "entity_restore",
143
- "entity_show",
144
- "entity_status",
145
- "relation_link",
146
- "relation_unlink",
147
- "relation_query",
148
- "initiative_bundle",
149
- "entity_next_work",
150
- "entity_orphans"
151
- ]));
152
- await client.close();
153
- await server.close();
154
- await store.close();
155
- });
156
- it("runs entity lifecycle queries and mutations", async () => {
157
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
158
- directories.push(directory);
159
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
160
- const firstParent = await store.createEntity({ kind: "initiative", title: "First parent" });
161
- const secondParent = await store.createEntity({ kind: "initiative", title: "Second parent" });
162
- const issue = await store.createEntity({ kind: "issue", title: "Lifecycle issue", body: "Lifecycle body", parentId: firstParent.id });
163
- const server = createMcpServer({ openStore: async () => store });
164
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
165
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
166
- await server.connect(serverTransport);
167
- await client.connect(clientTransport);
168
- const status = await client.callTool({ name: "entity_status", arguments: { entityId: issue.reference, status: "in-progress" } });
169
- const move = await client.callTool({ name: "entity_move", arguments: { entityId: issue.reference, newParentId: secondParent.reference } });
170
- const history = await client.callTool({ name: "entity_history", arguments: { entityId: issue.reference, revision: 1 } });
171
- const list = await client.callTool({ name: "entity_list", arguments: { kind: "issue", parentId: secondParent.reference } });
172
- const relations = await client.callTool({ name: "relation_query", arguments: { entityId: issue.reference } });
173
- const shown = await client.callTool({ name: "entity_show", arguments: { reference: issue.reference } });
174
- const archive = await client.callTool({ name: "entity_archive", arguments: { entityId: issue.reference } });
175
- expect(status).toMatchObject({ structuredContent: { entity: { reference: issue.reference, status: "in-progress" } } });
176
- expect(status.structuredContent.entity).not.toHaveProperty("body");
177
- expect(status.structuredContent.entity).not.toHaveProperty("bodySource");
178
- expect(move).toMatchObject({ structuredContent: { entity: { reference: issue.reference }, newParentId: secondParent.id } });
179
- expect(move.structuredContent.entity).not.toHaveProperty("body");
180
- expect(move.structuredContent.entity).not.toHaveProperty("bodySource");
181
- expect(history).toMatchObject({ structuredContent: { entityId: issue.reference, targetRevision: 1 } });
182
- expect(list).toMatchObject({ structuredContent: { entities: [expect.objectContaining({ reference: issue.reference })] } });
183
- expect(list.structuredContent.entities[0]).not.toHaveProperty("body");
184
- expect(list.structuredContent.entities[0]).not.toHaveProperty("bodySource");
185
- expect(relations.structuredContent.entity).not.toHaveProperty("body");
186
- expect(relations.structuredContent.incoming[0]?.entity).not.toHaveProperty("body");
187
- expect(shown).toMatchObject({ structuredContent: { entity: { reference: issue.reference, body: "Lifecycle body", bodySource: "authored" } } });
188
- expect(archive).toMatchObject({ structuredContent: { entity: { reference: issue.reference, status: "done" } } });
189
- expect(archive.structuredContent.entity).not.toHaveProperty("body");
190
- expect(archive.structuredContent.entity).not.toHaveProperty("bodySource");
191
- await client.close();
192
- await server.close();
193
- await store.close();
194
- });
195
- it("requires inspection before entity_delete", async () => {
196
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
197
- directories.push(directory);
198
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
199
- const issue = await store.createEntity({ kind: "issue", title: "Delete through MCP" });
200
- const server = createMcpServer({ openStore: async () => store });
201
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
202
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
203
- await server.connect(serverTransport);
204
- await client.connect(clientTransport);
205
- const inspection = await client.callTool({ name: "entity_delete_inspect", arguments: { entityId: issue.reference } });
206
- const confirmationToken = inspection.structuredContent.confirmationToken;
207
- const result = await client.callTool({ name: "entity_delete", arguments: { entityId: issue.reference, confirmationToken } });
208
- expect(inspection).toMatchObject({ structuredContent: { impact: { entity: { reference: issue.reference } }, confirmationToken: expect.any(String) } });
209
- expect(result).toMatchObject({ structuredContent: { entity: { reference: issue.reference }, removed: true } });
210
- expect(result.structuredContent.entity).not.toHaveProperty("body");
211
- expect(result.structuredContent.entity).not.toHaveProperty("bodySource");
212
- await expect(store.getEntityDetails(issue.reference)).rejects.toThrow();
213
- await client.close();
214
- await server.close();
215
- await store.close();
216
- });
217
- it("runs relation and initiative graph queries", async () => {
218
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
219
- directories.push(directory);
220
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
221
- const initiative = await store.createEntity({ kind: "initiative", title: "Graph initiative" });
222
- const source = await store.createEntity({ kind: "issue", title: "Source", parentId: initiative.id });
223
- const target = await store.createEntity({ kind: "issue", title: "Target", parentId: initiative.id });
224
- const orphan = await store.createEntity({ kind: "issue", title: "Orphan" });
225
- const server = createMcpServer({ openStore: async () => store });
226
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
227
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
228
- await server.connect(serverTransport);
229
- await client.connect(clientTransport);
230
- const linked = await client.callTool({ name: "relation_link", arguments: { fromId: source.reference, relationType: "blocks", toId: target.reference } });
231
- const relations = await client.callTool({ name: "relation_query", arguments: { entityId: source.reference, direction: "outgoing", types: ["blocks"] } });
232
- const bundle = await client.callTool({ name: "initiative_bundle", arguments: { initiativeId: initiative.reference } });
233
- const shown = await client.callTool({ name: "entity_show", arguments: { reference: initiative.reference } });
234
- const orphans = await client.callTool({ name: "entity_orphans", arguments: { kind: "issue" } });
235
- const unlinked = await client.callTool({ name: "relation_unlink", arguments: { fromId: source.reference, relationType: "blocks", toId: target.reference } });
236
- expect(linked).toMatchObject({ structuredContent: { created: true } });
237
- expect(relations).toMatchObject({ structuredContent: { outgoing: [expect.objectContaining({ entity: expect.objectContaining({ reference: target.reference }) })] } });
238
- expect(bundle).toMatchObject({ structuredContent: { initiative: { reference: initiative.reference }, issues: expect.arrayContaining([expect.objectContaining({ reference: source.reference })]) } });
239
- expect(shown).toMatchObject({ structuredContent: { initiative: { reference: initiative.reference }, issues: expect.arrayContaining([expect.objectContaining({ reference: source.reference })]) } });
240
- expect(orphans).toMatchObject({ structuredContent: { entities: expect.arrayContaining([expect.objectContaining({ reference: orphan.reference })]) } });
241
- expect(unlinked).toMatchObject({ structuredContent: { removed: true } });
242
- await client.close();
243
- await server.close();
244
- await store.close();
245
- });
246
- it("ranks available and blocked work through entity_next_work", async () => {
247
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
248
- directories.push(directory);
249
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
250
- const initiative = await store.createEntity({ kind: "initiative", title: "Work initiative" });
251
- const blocker = await store.createEntity({ kind: "issue", title: "Blocker", parentId: initiative.id });
252
- const blocked = await store.createEntity({ kind: "issue", title: "Blocked", parentId: initiative.id });
253
- await store.linkEntities({ fromId: blocker.id, relationType: "blocks", toId: blocked.id });
254
- const server = createMcpServer({ openStore: async () => store });
255
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
256
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
257
- await server.connect(serverTransport);
258
- await client.connect(clientTransport);
259
- const result = await client.callTool({ name: "entity_next_work", arguments: { scopeId: blocked.reference } });
260
- expect(result).toMatchObject({
261
- structuredContent: {
262
- available: [expect.objectContaining({ issue: expect.objectContaining({ reference: blocker.reference }) })],
263
- blocked: [expect.objectContaining({ issue: expect.objectContaining({ reference: blocked.reference }), blockers: [blocker.reference] })]
264
- }
265
- });
266
- await client.close();
267
- await server.close();
268
- await store.close();
269
- });
270
- it("excludes every issue with an unfinished blocker from available entity_next_work", async () => {
271
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
272
- directories.push(directory);
273
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
274
- const initiative = await store.createEntity({ kind: "initiative", title: "Work initiative" });
275
- const blocker = await store.createEntity({ kind: "issue", title: "Shared blocker", parentId: initiative.id });
276
- const firstBlocked = await store.createEntity({ kind: "issue", title: "First blocked", parentId: initiative.id });
277
- const secondBlocked = await store.createEntity({ kind: "issue", title: "Second blocked", parentId: initiative.id });
278
- const selected = await store.createEntity({ kind: "issue", title: "Available selection", parentId: initiative.id });
279
- await store.linkEntities({ fromId: blocker.id, relationType: "blocks", toId: firstBlocked.id });
280
- await store.linkEntities({ fromId: blocker.id, relationType: "blocks", toId: secondBlocked.id });
281
- const server = createMcpServer({ openStore: async () => store });
282
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
283
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
284
- await server.connect(serverTransport);
285
- await client.connect(clientTransport);
286
- const result = await client.callTool({ name: "entity_next_work", arguments: { scopeId: selected.reference } });
287
- const content = result.structuredContent;
288
- expect(content.available.map((item) => item.issue.reference).sort()).toEqual([blocker.reference, selected.reference].sort());
289
- expect(content.blocked).toEqual(expect.arrayContaining([
290
- expect.objectContaining({ issue: expect.objectContaining({ reference: firstBlocked.reference }), blockers: [blocker.reference] }),
291
- expect.objectContaining({ issue: expect.objectContaining({ reference: secondBlocked.reference }), blockers: [blocker.reference] })
292
- ]));
293
- await client.close();
294
- await server.close();
295
- await store.close();
296
- });
297
- it("returns entity details from entity_show", async () => {
298
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
299
- directories.push(directory);
300
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
301
- const issue = await store.createEntity({ kind: "issue", title: "Read through MCP" });
302
- const server = createMcpServer({ openStore: async () => store });
303
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
304
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
305
- await server.connect(serverTransport);
306
- await client.connect(clientTransport);
307
- const result = await client.callTool({ name: "entity_show", arguments: { reference: issue.reference } });
308
- expect(result).toMatchObject({ structuredContent: { entity: { reference: issue.reference, title: "Read through MCP" } } });
309
- await client.close();
310
- await server.close();
311
- await store.close();
312
- });
313
- it("creates, changes, links, and deletes Plan entries through MCP", async () => {
314
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
315
- directories.push(directory);
316
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
317
- const initiative = await store.createEntity({ kind: "initiative", title: "Plan initiative" });
318
- const plan = await store.createEntity({ kind: "plan", title: "MCP Plan", parentId: initiative.id });
319
- const issue = await store.createEntity({ kind: "issue", title: "Plan issue" });
320
- const relatedIssue = await store.createEntity({ kind: "issue", title: "Related Plan issue" });
321
- const server = createMcpServer({ openStore: async () => store });
322
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
323
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
324
- await server.connect(serverTransport);
325
- await client.connect(clientTransport);
326
- const created = await client.callTool({
327
- name: "plan_entry_create",
328
- arguments: { planId: plan.reference, role: "question", body: "What must the tool return?", referencedEntityIds: [relatedIssue.reference] }
329
- });
330
- expect(created).toMatchObject({ structuredContent: { entry: { referencedEntityIds: [relatedIssue.id] } } });
331
- expect(created.structuredContent.entry).not.toHaveProperty("body");
332
- const entry = created.structuredContent.entry;
333
- const list = await client.callTool({ name: "plan_entry_list", arguments: { planId: plan.reference } });
334
- const edited = await client.callTool({
335
- name: "plan_entry_edit",
336
- arguments: { entryId: entry.reference, body: "The tool returns structured data.", expectedRevision: entry.revision, expectedContentHash: entry.contentHash }
337
- });
338
- const updatedEntry = edited.structuredContent.entry;
339
- const history = await client.callTool({ name: "plan_entry_history", arguments: { entryId: updatedEntry.reference } });
340
- const linked = await client.callTool({ name: "plan_entry_issue_link", arguments: { entryId: updatedEntry.reference, issueId: issue.reference } });
341
- const linkedEntry = await store.getPlanEntry({ entryId: updatedEntry.reference });
342
- const unlinked = await client.callTool({ name: "plan_entry_issue_unlink", arguments: { entryId: updatedEntry.reference, issueId: issue.reference } });
343
- const unlinkedEntry = await store.getPlanEntry({ entryId: updatedEntry.reference });
344
- const deleted = await client.callTool({
345
- name: "plan_entry_delete",
346
- arguments: { entryId: updatedEntry.reference, expectedRevision: unlinkedEntry.revision, expectedContentHash: unlinkedEntry.contentHash }
347
- });
348
- expect(list).toMatchObject({ structuredContent: { entries: [expect.objectContaining({ reference: entry.reference, body: "What must the tool return?" })] } });
349
- expect(edited).toMatchObject({ structuredContent: { entry: { reference: entry.reference, body: "The tool returns structured data.", revision: 2 } } });
350
- expect(history).toMatchObject({ structuredContent: { history: expect.arrayContaining([expect.objectContaining({ entryId: expect.any(String), targetRevision: 1, body: "What must the tool return?" })]) } });
351
- expect(linked).toMatchObject({ structuredContent: { created: true } });
352
- expect(linkedEntry.referencedEntityIds).toContain(issue.id);
353
- expect(unlinked).toMatchObject({ structuredContent: { removed: true } });
354
- expect(deleted).toMatchObject({ structuredContent: { entry: { reference: entry.reference, tombstone: true } } });
355
- await client.close();
356
- await server.close();
357
- await store.close();
358
- });
359
- it("creates, changes, lists, and deletes issue comments through MCP", async () => {
360
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
361
- directories.push(directory);
362
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
363
- const issue = await store.createEntity({ kind: "issue", title: "Comment issue" });
364
- const relatedIssue = await store.createEntity({ kind: "issue", title: "Related comment issue" });
365
- const server = createMcpServer({ openStore: async () => store });
366
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
367
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
368
- await server.connect(serverTransport);
369
- await client.connect(clientTransport);
370
- const created = await client.callTool({
371
- name: "comment_create",
372
- arguments: { issueId: issue.reference, body: "Authored comment", referencedIssueIds: [relatedIssue.reference] }
373
- });
374
- expect(created).toMatchObject({ structuredContent: { comment: { referencedIssueIds: [relatedIssue.id] } } });
375
- expect(created.structuredContent.comment).not.toHaveProperty("body");
376
- const comment = created.structuredContent.comment;
377
- const list = await client.callTool({ name: "comment_list", arguments: { issueId: issue.reference, all: true } });
378
- const edited = await client.callTool({
379
- name: "comment_edit",
380
- arguments: { commentId: comment.reference, body: "Edited comment", expectedRevision: comment.revision, expectedContentHash: comment.contentHash }
381
- });
382
- const updatedComment = edited.structuredContent.comment;
383
- const history = await client.callTool({ name: "comment_history", arguments: { commentId: updatedComment.reference } });
384
- const deleted = await client.callTool({
385
- name: "comment_delete",
386
- arguments: { commentId: updatedComment.reference, expectedRevision: updatedComment.revision, expectedContentHash: updatedComment.contentHash }
387
- });
388
- expect(list).toMatchObject({ structuredContent: { comments: [expect.objectContaining({ reference: comment.reference, body: "Authored comment" })] } });
389
- expect(edited).toMatchObject({ structuredContent: { comment: { reference: comment.reference, body: "Edited comment", revision: 2 } } });
390
- expect(history).toMatchObject({
391
- structuredContent: {
392
- history: expect.arrayContaining([expect.objectContaining({ commentId: expect.any(String), targetRevision: 1, body: "Authored comment" })])
393
- }
394
- });
395
- expect(deleted).toMatchObject({ structuredContent: { comment: { reference: comment.reference, tombstone: true, revision: 3 } } });
396
- await client.close();
397
- await server.close();
398
- await store.close();
399
- });
400
- it("returns context details and terms through context_show", async () => {
401
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
402
- directories.push(directory);
403
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
404
- const initiative = await store.createEntity({ kind: "initiative", title: "Glossary initiative" });
405
- await store.upsertContext({ scopeRef: initiative.reference, title: "Glossary", summary: "Initiative terms." });
406
- await store.defineContextTerm({ scopeRef: initiative.reference, term: "MCP", definition: "A protocol." });
407
- const server = createMcpServer({ openStore: async () => store });
408
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
409
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
410
- await server.connect(serverTransport);
411
- await client.connect(clientTransport);
412
- const result = await client.callTool({ name: "context_show", arguments: { scopeRef: initiative.reference } });
413
- expect(result).toMatchObject({
414
- structuredContent: {
415
- context: { title: "Glossary", summary: "Initiative terms." },
416
- terms: [expect.objectContaining({ term: "MCP", definition: "A protocol." })]
417
- }
418
- });
419
- await client.close();
420
- await server.close();
421
- await store.close();
422
- });
423
- it("queries, changes, and reads context revisions through context tools", async () => {
424
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
425
- directories.push(directory);
426
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
427
- const firstInitiative = await store.createEntity({ kind: "initiative", title: "First context initiative" });
428
- const secondInitiative = await store.createEntity({ kind: "initiative", title: "Second context initiative" });
429
- await store.upsertContext({ scopeRef: firstInitiative.reference, title: "First context", summary: "First summary." });
430
- await store.upsertContext({ scopeRef: secondInitiative.reference, title: "Second context", summary: "Second summary." });
431
- await store.defineContextTerm({ scopeRef: firstInitiative.reference, term: "parity", definition: "Equivalent behavior." });
432
- await store.defineContextTerm({ scopeRef: secondInitiative.reference, term: "parity", definition: "A different definition." });
433
- const firstContext = await store.getContextDetails({ scopeRef: firstInitiative.reference });
434
- const server = createMcpServer({ openStore: async () => store });
435
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
436
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
437
- await server.connect(serverTransport);
438
- await client.connect(clientTransport);
439
- const directoryResult = await client.callTool({ name: "context_directory", arguments: {} });
440
- const search = await client.callTool({ name: "context_search", arguments: { query: "parity", view: "initiatives" } });
441
- const conflicts = await client.callTool({ name: "context_conflicts", arguments: { query: "parity" } });
442
- const set = await client.callTool({
443
- name: "context_set",
444
- arguments: {
445
- scopeRef: firstInitiative.reference,
446
- title: "First context revised",
447
- summary: "Revised summary.",
448
- expectedRevision: firstContext.context.revision,
449
- expectedContentHash: firstContext.context.contentHash
450
- }
451
- });
452
- const define = await client.callTool({
453
- name: "context_term_define",
454
- arguments: { scopeRef: firstInitiative.reference, term: "token", definition: "A temporary confirmation value." }
455
- });
456
- const token = (await store.getContextDetails({ scopeRef: firstInitiative.reference })).terms.find((term) => term.term === "token");
457
- const contextRevision = await client.callTool({ name: "context_revision", arguments: { scopeRef: firstInitiative.reference, revision: 1 } });
458
- const termRevision = await client.callTool({ name: "context_term_revision", arguments: { scopeRef: firstInitiative.reference, term: "parity", revision: 1 } });
459
- const forget = await client.callTool({
460
- name: "context_term_forget",
461
- arguments: {
462
- scopeRef: firstInitiative.reference,
463
- term: "token",
464
- expectedRevision: token?.revision,
465
- expectedContentHash: token?.contentHash
466
- }
467
- });
468
- expect(directoryResult).toMatchObject({
469
- structuredContent: {
470
- initiatives: expect.arrayContaining([expect.objectContaining({ context: expect.objectContaining({ title: "First context" }) })])
471
- }
472
- });
473
- expect(search).toMatchObject({ structuredContent: { query: "parity", view: "initiatives", terms: [expect.objectContaining({ term: "parity" })] } });
474
- expect(conflicts).toMatchObject({ structuredContent: { conflictsOnly: true, terms: [expect.objectContaining({ term: "parity", hasConflictingDefinitions: true })] } });
475
- expect(set).toMatchObject({ structuredContent: { context: { title: "First context revised" } } });
476
- expect(set.structuredContent.context).not.toHaveProperty("summary");
477
- expect(define).toMatchObject({ structuredContent: { term: { term: "token" }, created: true } });
478
- expect(define.structuredContent.term).not.toHaveProperty("definition");
479
- expect(contextRevision).toMatchObject({ structuredContent: { targetRevision: 1, title: "First context" } });
480
- expect(termRevision).toMatchObject({ structuredContent: { targetRevision: 1, term: "parity", definition: "Equivalent behavior." } });
481
- expect(forget).toMatchObject({ structuredContent: { term: "token", removed: true } });
482
- await client.close();
483
- await server.close();
484
- await store.close();
485
- });
486
- it("lists tracker contexts through context_list", async () => {
487
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
488
- directories.push(directory);
489
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
490
- const initiative = await store.createEntity({ kind: "initiative", title: "Context initiative" });
491
- await store.upsertContext({ scopeRef: initiative.reference, title: "Initiative context", summary: "Tracker terms." });
492
- const server = createMcpServer({ openStore: async () => store });
493
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
494
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
495
- await server.connect(serverTransport);
496
- await client.connect(clientTransport);
497
- const result = await client.callTool({ name: "context_list", arguments: {} });
498
- expect(result).toMatchObject({
499
- structuredContent: {
500
- contexts: expect.arrayContaining([expect.objectContaining({ context: expect.objectContaining({ title: "Initiative context" }) })])
501
- }
502
- });
503
- await client.close();
504
- await server.close();
505
- await store.close();
506
- });
507
- it("lists tenants through tenant_list", async () => {
508
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
509
- directories.push(directory);
510
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
511
- const server = createMcpServer({ openStore: async () => store });
512
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
513
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
514
- await server.connect(serverTransport);
515
- await client.connect(clientTransport);
516
- const result = await client.callTool({ name: "tenant_list", arguments: {} });
517
- expect(result).toMatchObject({ structuredContent: { tenants: [{ id: resolveWellKnownLocalTenantId() }] } });
518
- await client.close();
519
- await server.close();
520
- await store.close();
521
- });
522
- it("renames a tenant through tenant_rename", async () => {
523
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
524
- directories.push(directory);
525
- const dbPath = path.join(directory, "agent-issues.db");
526
- const { store } = await openSqliteStore(dbPath);
527
- const { store: tenantStore } = await openSqliteStore(dbPath, { tenant: "tenant-source" });
528
- const server = createMcpServer({ openStore: async () => store });
529
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
530
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
531
- await server.connect(serverTransport);
532
- await client.connect(clientTransport);
533
- const result = await client.callTool({
534
- name: "tenant_rename",
535
- arguments: { previousTenantId: "tenant-source", newTenantId: "tenant-renamed" }
536
- });
537
- expect(result).toMatchObject({ structuredContent: { previousTenantId: "tenant-source", newTenantId: "tenant-renamed", renamed: true } });
538
- await client.close();
539
- await server.close();
540
- await tenantStore.close();
541
- await store.close();
542
- });
543
- it("requires inspection before tenant_delete", async () => {
544
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
545
- directories.push(directory);
546
- const dbPath = path.join(directory, "agent-issues.db");
547
- const { store } = await openSqliteStore(dbPath);
548
- const { store: tenantStore } = await openSqliteStore(dbPath, { tenant: "tenant-delete" });
549
- const server = createMcpServer({ openStore: async () => store });
550
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
551
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
552
- await server.connect(serverTransport);
553
- await client.connect(clientTransport);
554
- const inspection = await client.callTool({ name: "tenant_delete_inspect", arguments: { tenantId: "tenant-delete" } });
555
- const confirmationToken = inspection.structuredContent.confirmationToken;
556
- const result = await client.callTool({ name: "tenant_delete", arguments: { tenantId: "tenant-delete", confirmationToken } });
557
- expect(inspection).toMatchObject({ structuredContent: { impact: { id: "tenant-delete" }, confirmationToken: expect.any(String) } });
558
- expect(result).toMatchObject({ structuredContent: { tenantId: "tenant-delete", removed: true } });
559
- await client.close();
560
- await server.close();
561
- await tenantStore.close();
562
- await store.close();
563
- });
564
- it("rejects expired confirmation tokens", async () => {
565
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
566
- directories.push(directory);
567
- const dbPath = path.join(directory, "agent-issues.db");
568
- const { store } = await openSqliteStore(dbPath);
569
- const { store: tenantStore } = await openSqliteStore(dbPath, { tenant: "tenant-expired" });
570
- let now = Date.UTC(2026, 0, 1);
571
- const server = createMcpServer({ openStore: async () => store, now: () => now });
572
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
573
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
574
- await server.connect(serverTransport);
575
- await client.connect(clientTransport);
576
- const inspection = await client.callTool({ name: "tenant_delete_inspect", arguments: { tenantId: "tenant-expired" } });
577
- const confirmationToken = inspection.structuredContent.confirmationToken;
578
- now += 5 * 60 * 1000;
579
- const result = await client.callTool({ name: "tenant_delete", arguments: { tenantId: "tenant-expired", confirmationToken } });
580
- expect(result).toMatchObject({ isError: true, content: [{ text: expect.stringContaining("has expired") }] });
581
- expect(await store.listTenants()).toEqual(expect.arrayContaining([expect.objectContaining({ id: "tenant-expired" })]));
582
- await client.close();
583
- await server.close();
584
- await tenantStore.close();
585
- await store.close();
586
- });
587
- it("rejects a replayed confirmation token", async () => {
588
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
589
- directories.push(directory);
590
- const dbPath = path.join(directory, "agent-issues.db");
591
- const { store } = await openSqliteStore(dbPath);
592
- const { store: tenantStore } = await openSqliteStore(dbPath, { tenant: "tenant-replay" });
593
- const server = createMcpServer({ openStore: async () => store });
594
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
595
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
596
- await server.connect(serverTransport);
597
- await client.connect(clientTransport);
598
- const inspection = await client.callTool({ name: "tenant_delete_inspect", arguments: { tenantId: "tenant-replay" } });
599
- const confirmationToken = inspection.structuredContent.confirmationToken;
600
- await client.callTool({ name: "tenant_delete", arguments: { tenantId: "tenant-replay", confirmationToken } });
601
- const result = await client.callTool({ name: "tenant_delete", arguments: { tenantId: "tenant-replay", confirmationToken } });
602
- expect(result).toMatchObject({ isError: true, content: [{ text: expect.stringContaining("Invalid confirmation token") }] });
603
- await client.close();
604
- await server.close();
605
- await tenantStore.close();
606
- await store.close();
607
- });
608
- it("rejects a confirmation token for a different tenant deletion", async () => {
609
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
610
- directories.push(directory);
611
- const dbPath = path.join(directory, "agent-issues.db");
612
- const { store } = await openSqliteStore(dbPath);
613
- const { store: sourceStore } = await openSqliteStore(dbPath, { tenant: "tenant-source" });
614
- const { store: targetStore } = await openSqliteStore(dbPath, { tenant: "tenant-target" });
615
- const server = createMcpServer({ openStore: async () => store });
616
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
617
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
618
- await server.connect(serverTransport);
619
- await client.connect(clientTransport);
620
- const inspection = await client.callTool({ name: "tenant_delete_inspect", arguments: { tenantId: "tenant-source" } });
621
- const confirmationToken = inspection.structuredContent.confirmationToken;
622
- const result = await client.callTool({ name: "tenant_delete", arguments: { tenantId: "tenant-target", confirmationToken } });
623
- expect(result).toMatchObject({ isError: true, content: [{ text: expect.stringContaining("does not authorize") }] });
624
- expect(await store.listTenants()).toEqual(expect.arrayContaining([expect.objectContaining({ id: "tenant-source" }), expect.objectContaining({ id: "tenant-target" })]));
625
- await client.close();
626
- await server.close();
627
- await sourceStore.close();
628
- await targetStore.close();
629
- await store.close();
630
- });
631
- it("requires inspection before entity_restore", async () => {
632
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
633
- directories.push(directory);
634
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
635
- const issue = await store.createEntity({ kind: "issue", title: "Original title", body: "Original body" });
636
- await store.updateEntity({
637
- entityId: issue.id,
638
- title: "Updated title",
639
- expectedRevision: issue.revision,
640
- expectedContentHash: issue.contentHash
641
- });
642
- const server = createMcpServer({ openStore: async () => store });
643
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
644
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
645
- await server.connect(serverTransport);
646
- await client.connect(clientTransport);
647
- const inspection = await client.callTool({ name: "entity_restore_inspect", arguments: { entityId: issue.id, revision: 1 } });
648
- const confirmationToken = inspection.structuredContent.confirmationToken;
649
- const result = await client.callTool({ name: "entity_restore", arguments: { entityId: issue.id, revision: 1, confirmationToken } });
650
- expect(inspection).toMatchObject({ structuredContent: { impact: { entityId: issue.id, title: "Original title" }, confirmationToken: expect.any(String) } });
651
- expect(result).toMatchObject({ structuredContent: { entityId: issue.id, title: "Original title", restoredFromRevision: 1 } });
652
- await client.close();
653
- await server.close();
654
- await store.close();
655
- });
656
- it("requires inspection before body_backfill", async () => {
657
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-server-"));
658
- directories.push(directory);
659
- const { store } = await openSqliteStore(path.join(directory, "agent-issues.db"));
660
- await store.createEntity({ kind: "issue", title: "Backfill this body" });
661
- const server = createMcpServer({ openStore: async () => store });
662
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
663
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
664
- await server.connect(serverTransport);
665
- await client.connect(clientTransport);
666
- const inspection = await client.callTool({ name: "body_backfill_inspect", arguments: { kinds: ["issue"] } });
667
- const confirmationToken = inspection.structuredContent.confirmationToken;
668
- const result = await client.callTool({ name: "body_backfill", arguments: { kinds: ["issue"], confirmationToken } });
669
- expect(inspection).toMatchObject({ structuredContent: { impact: { dryRun: true, updated: 1 }, confirmationToken: expect.any(String) } });
670
- expect(result).toMatchObject({ structuredContent: { dryRun: false, updated: 1 } });
671
- await client.close();
672
- await server.close();
673
- await store.close();
674
- });
675
- it("starts a missing local daemon once and reads entity details through it", async () => {
676
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-daemon-"));
677
- directories.push(directory);
678
- const dbPath = path.join(directory, "agent-issues.db");
679
- const homeDirectory = path.join(directory, "home");
680
- const credentialStoreOptions = fakeCredentialStore();
681
- const { store } = await openSqliteStore(dbPath, { tenant: resolveWellKnownLocalTenantId() });
682
- const issue = await store.createEntity({ kind: "issue", title: "Read through the daemon" });
683
- await store.close();
684
- let daemon;
685
- let spawnCount = 0;
686
- const server = createLocalMcpServer({
687
- dbPath,
688
- homeDirectory,
689
- credentialStoreOptions,
690
- spawn: () => {
691
- spawnCount++;
692
- daemon = createLocalDaemonServer({ dbPath, homeDirectory, credentialStoreOptions, idleTimeoutMs: 0 });
693
- }
694
- });
695
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
696
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
697
- await server.connect(serverTransport);
698
- await client.connect(clientTransport);
699
- const result = await client.callTool({ name: "entity_show", arguments: { reference: issue.reference } });
700
- const created = await client.callTool({ name: "entity_create", arguments: { kind: "issue", title: "Create through the daemon" } });
701
- expect(result).toMatchObject({ structuredContent: { entity: { reference: issue.reference, title: "Read through the daemon" } } });
702
- expect(created).toMatchObject({ structuredContent: { entity: { title: "Create through the daemon" } } });
703
- expect(spawnCount).toBe(1);
704
- await client.close();
705
- await server.close();
706
- await daemon?.close();
707
- });
708
- it("writes authored comment and Plan-entry bodies through the local daemon", async () => {
709
- const directory = mkdtempSync(path.join(tmpdir(), "agent-issues-mcp-daemon-"));
710
- directories.push(directory);
711
- const dbPath = path.join(directory, "agent-issues.db");
712
- const homeDirectory = path.join(directory, "home");
713
- const credentialStoreOptions = fakeCredentialStore();
714
- const { store } = await openSqliteStore(dbPath, { tenant: resolveWellKnownLocalTenantId() });
715
- const initiative = await store.createEntity({ kind: "initiative", title: "Daemon initiative" });
716
- const plan = await store.createEntity({ kind: "plan", title: "Daemon Plan", parentId: initiative.id });
717
- const issue = await store.createEntity({ kind: "issue", title: "Daemon issue" });
718
- await store.close();
719
- let daemon;
720
- let spawnCount = 0;
721
- const server = createLocalMcpServer({
722
- dbPath,
723
- homeDirectory,
724
- credentialStoreOptions,
725
- spawn: () => {
726
- spawnCount++;
727
- daemon = createLocalDaemonServer({ dbPath, homeDirectory, credentialStoreOptions, idleTimeoutMs: 0 });
728
- }
729
- });
730
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
731
- const client = new Client({ name: "agent-issues-test", version: "1.0.0" });
732
- await server.connect(serverTransport);
733
- await client.connect(clientTransport);
734
- const comment = await client.callTool({ name: "comment_create", arguments: { issueId: issue.reference, body: "Authored daemon comment" } });
735
- const entry = await client.callTool({ name: "plan_entry_create", arguments: { planId: plan.reference, role: "decision", body: "Authored daemon decision" } });
736
- expect(comment).toMatchObject({ structuredContent: { comment: { issueId: issue.id } } });
737
- expect(comment.structuredContent.comment).not.toHaveProperty("body");
738
- expect(entry).toMatchObject({ structuredContent: { entry: { planId: plan.id } } });
739
- expect(entry.structuredContent.entry).not.toHaveProperty("body");
740
- expect(spawnCount).toBe(1);
741
- await client.close();
742
- await server.close();
743
- await daemon?.close();
744
- });
745
- });
746
- //# sourceMappingURL=index.test.js.map