huly-mcp-selfhost 2.1.0 → 3.2.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.
Files changed (200) hide show
  1. package/.env.example +39 -2
  2. package/README.md +462 -16
  3. package/dist/connection.d.ts +8 -0
  4. package/dist/connection.js +13 -1
  5. package/dist/connection.js.map +1 -1
  6. package/dist/env.d.ts +1 -0
  7. package/dist/env.js +17 -0
  8. package/dist/env.js.map +1 -0
  9. package/dist/guard.d.ts +28 -0
  10. package/dist/guard.js +152 -0
  11. package/dist/guard.js.map +1 -0
  12. package/dist/guides/costs.md +48 -0
  13. package/dist/guides/features.md +49 -0
  14. package/dist/guides/onboarding.md +56 -0
  15. package/dist/guides/workflow.md +57 -0
  16. package/dist/index.js +18 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/junit-cli.d.ts +8 -0
  19. package/dist/junit-cli.js +106 -0
  20. package/dist/junit-cli.js.map +1 -0
  21. package/dist/registry.d.ts +18 -0
  22. package/dist/registry.js +318 -0
  23. package/dist/registry.js.map +1 -0
  24. package/dist/resources.d.ts +12 -0
  25. package/dist/resources.js +212 -0
  26. package/dist/resources.js.map +1 -0
  27. package/dist/schemas.d.ts +1269 -17
  28. package/dist/schemas.js +890 -97
  29. package/dist/schemas.js.map +1 -1
  30. package/dist/server.d.ts +27 -1
  31. package/dist/server.js +197 -128
  32. package/dist/server.js.map +1 -1
  33. package/dist/tools/activity.d.ts +10 -0
  34. package/dist/tools/activity.js +62 -0
  35. package/dist/tools/activity.js.map +1 -0
  36. package/dist/tools/agent.d.ts +156 -0
  37. package/dist/tools/agent.js +523 -0
  38. package/dist/tools/agent.js.map +1 -0
  39. package/dist/tools/attachments.d.ts +4 -4
  40. package/dist/tools/calendar.d.ts +49 -0
  41. package/dist/tools/calendar.js +116 -0
  42. package/dist/tools/calendar.js.map +1 -0
  43. package/dist/tools/cards.d.ts +87 -0
  44. package/dist/tools/cards.js +246 -0
  45. package/dist/tools/cards.js.map +1 -0
  46. package/dist/tools/channels.d.ts +76 -7
  47. package/dist/tools/channels.js +86 -5
  48. package/dist/tools/channels.js.map +1 -1
  49. package/dist/tools/comments.d.ts +16 -4
  50. package/dist/tools/comments.js +26 -9
  51. package/dist/tools/comments.js.map +1 -1
  52. package/dist/tools/components.d.ts +5 -5
  53. package/dist/tools/components.js +4 -22
  54. package/dist/tools/components.js.map +1 -1
  55. package/dist/tools/contacts.d.ts +69 -0
  56. package/dist/tools/contacts.js +130 -0
  57. package/dist/tools/contacts.js.map +1 -0
  58. package/dist/tools/crm.d.ts +61 -0
  59. package/dist/tools/crm.js +117 -0
  60. package/dist/tools/crm.js.map +1 -0
  61. package/dist/tools/documents.d.ts +35 -12
  62. package/dist/tools/documents.js +107 -22
  63. package/dist/tools/documents.js.map +1 -1
  64. package/dist/tools/drive.d.ts +107 -0
  65. package/dist/tools/drive.js +277 -0
  66. package/dist/tools/drive.js.map +1 -0
  67. package/dist/tools/fields.d.ts +33 -0
  68. package/dist/tools/fields.js +124 -0
  69. package/dist/tools/fields.js.map +1 -0
  70. package/dist/tools/generic.d.ts +72 -0
  71. package/dist/tools/generic.js +242 -0
  72. package/dist/tools/generic.js.map +1 -0
  73. package/dist/tools/github.d.ts +21 -0
  74. package/dist/tools/github.js +80 -0
  75. package/dist/tools/github.js.map +1 -0
  76. package/dist/tools/hr.d.ts +97 -0
  77. package/dist/tools/hr.js +150 -0
  78. package/dist/tools/hr.js.map +1 -0
  79. package/dist/tools/inbox.d.ts +23 -0
  80. package/dist/tools/inbox.js +76 -0
  81. package/dist/tools/inbox.js.map +1 -0
  82. package/dist/tools/inventory.d.ts +61 -0
  83. package/dist/tools/inventory.js +103 -0
  84. package/dist/tools/inventory.js.map +1 -0
  85. package/dist/tools/issues.d.ts +63 -12
  86. package/dist/tools/issues.js +259 -48
  87. package/dist/tools/issues.js.map +1 -1
  88. package/dist/tools/labels.d.ts +52 -4
  89. package/dist/tools/labels.js +86 -4
  90. package/dist/tools/labels.js.map +1 -1
  91. package/dist/tools/log-time.d.ts +15 -1
  92. package/dist/tools/log-time.js +70 -6
  93. package/dist/tools/log-time.js.map +1 -1
  94. package/dist/tools/meetings.d.ts +30 -0
  95. package/dist/tools/meetings.js +78 -0
  96. package/dist/tools/meetings.js.map +1 -0
  97. package/dist/tools/members.d.ts +1 -1
  98. package/dist/tools/members.js +17 -1
  99. package/dist/tools/members.js.map +1 -1
  100. package/dist/tools/message-templates.d.ts +54 -0
  101. package/dist/tools/message-templates.js +88 -0
  102. package/dist/tools/message-templates.js.map +1 -0
  103. package/dist/tools/milestones.d.ts +9 -9
  104. package/dist/tools/organizations.d.ts +5 -5
  105. package/dist/tools/organizations.js +2 -1
  106. package/dist/tools/organizations.js.map +1 -1
  107. package/dist/tools/overview.d.ts +55 -0
  108. package/dist/tools/overview.js +395 -0
  109. package/dist/tools/overview.js.map +1 -0
  110. package/dist/tools/planner.d.ts +60 -0
  111. package/dist/tools/planner.js +145 -0
  112. package/dist/tools/planner.js.map +1 -0
  113. package/dist/tools/process.d.ts +50 -0
  114. package/dist/tools/process.js +137 -0
  115. package/dist/tools/process.js.map +1 -0
  116. package/dist/tools/products.d.ts +17 -0
  117. package/dist/tools/products.js +45 -0
  118. package/dist/tools/products.js.map +1 -0
  119. package/dist/tools/projects.d.ts +17 -4
  120. package/dist/tools/projects.js +31 -2
  121. package/dist/tools/projects.js.map +1 -1
  122. package/dist/tools/qms.d.ts +57 -0
  123. package/dist/tools/qms.js +171 -0
  124. package/dist/tools/qms.js.map +1 -0
  125. package/dist/tools/recruiting.d.ts +112 -0
  126. package/dist/tools/recruiting.js +203 -0
  127. package/dist/tools/recruiting.js.map +1 -0
  128. package/dist/tools/relations.d.ts +45 -3
  129. package/dist/tools/relations.js +128 -39
  130. package/dist/tools/relations.js.map +1 -1
  131. package/dist/tools/search.d.ts +14 -1
  132. package/dist/tools/search.js +29 -1
  133. package/dist/tools/search.js.map +1 -1
  134. package/dist/tools/sharing.d.ts +62 -0
  135. package/dist/tools/sharing.js +149 -0
  136. package/dist/tools/sharing.js.map +1 -0
  137. package/dist/tools/statuses.d.ts +3 -3
  138. package/dist/tools/surveys.d.ts +22 -0
  139. package/dist/tools/surveys.js +53 -0
  140. package/dist/tools/surveys.js.map +1 -0
  141. package/dist/tools/templates.d.ts +95 -0
  142. package/dist/tools/templates.js +260 -0
  143. package/dist/tools/templates.js.map +1 -0
  144. package/dist/tools/test-automation.d.ts +81 -0
  145. package/dist/tools/test-automation.js +225 -0
  146. package/dist/tools/test-automation.js.map +1 -0
  147. package/dist/tools/testing.d.ts +234 -0
  148. package/dist/tools/testing.js +401 -0
  149. package/dist/tools/testing.js.map +1 -0
  150. package/dist/tools/workspace-admin.d.ts +55 -0
  151. package/dist/tools/workspace-admin.js +117 -0
  152. package/dist/tools/workspace-admin.js.map +1 -0
  153. package/dist/tools/workspaces.d.ts +1 -1
  154. package/dist/utils/attributes.d.ts +37 -0
  155. package/dist/utils/attributes.js +188 -0
  156. package/dist/utils/attributes.js.map +1 -0
  157. package/dist/utils/classes.d.ts +51 -0
  158. package/dist/utils/classes.js +73 -0
  159. package/dist/utils/classes.js.map +1 -0
  160. package/dist/utils/content.d.ts +11 -0
  161. package/dist/utils/content.js +86 -0
  162. package/dist/utils/content.js.map +1 -0
  163. package/dist/utils/errors.js +1 -1
  164. package/dist/utils/errors.js.map +1 -1
  165. package/dist/utils/files.d.ts +5 -0
  166. package/dist/utils/files.js +50 -0
  167. package/dist/utils/files.js.map +1 -0
  168. package/dist/utils/format.d.ts +2 -0
  169. package/dist/utils/format.js +7 -13
  170. package/dist/utils/format.js.map +1 -1
  171. package/dist/utils/junit.d.ts +13 -0
  172. package/dist/utils/junit.js +81 -0
  173. package/dist/utils/junit.js.map +1 -0
  174. package/dist/utils/markdown.js +9 -4
  175. package/dist/utils/markdown.js.map +1 -1
  176. package/dist/utils/meeting.d.ts +13 -0
  177. package/dist/utils/meeting.js +118 -0
  178. package/dist/utils/meeting.js.map +1 -0
  179. package/dist/utils/people.d.ts +25 -0
  180. package/dist/utils/people.js +80 -0
  181. package/dist/utils/people.js.map +1 -0
  182. package/dist/utils/redact.d.ts +3 -0
  183. package/dist/utils/redact.js +26 -0
  184. package/dist/utils/redact.js.map +1 -0
  185. package/dist/utils/resolve.d.ts +17 -0
  186. package/dist/utils/resolve.js +54 -0
  187. package/dist/utils/resolve.js.map +1 -0
  188. package/dist/utils/spec.d.ts +10 -0
  189. package/dist/utils/spec.js +90 -0
  190. package/dist/utils/spec.js.map +1 -0
  191. package/dist/utils/storage.js +7 -3
  192. package/dist/utils/storage.js.map +1 -1
  193. package/dist/utils/tasks.d.ts +22 -0
  194. package/dist/utils/tasks.js +81 -0
  195. package/dist/utils/tasks.js.map +1 -0
  196. package/dist/watch.d.ts +62 -0
  197. package/dist/watch.js +405 -0
  198. package/dist/watch.js.map +1 -0
  199. package/package.json +4 -3
  200. package/scripts/setup.js +3 -1
@@ -0,0 +1,234 @@
1
+ export declare const TM: {
2
+ readonly Project: 'testManagement:class:TestProject';
3
+ readonly Suite: 'testManagement:class:TestSuite';
4
+ readonly Case: 'testManagement:class:TestCase';
5
+ readonly Plan: 'testManagement:class:TestPlan';
6
+ readonly PlanItem: 'testManagement:class:TestPlanItem';
7
+ readonly Run: 'testManagement:class:TestRun';
8
+ readonly Result: 'testManagement:class:TestResult';
9
+ readonly NoParent: 'testManagement:ids:NoParent';
10
+ readonly SpaceType: 'testManagement:spaceType:DefaultProject';
11
+ readonly TypeMixin: 'testManagement:mixin:DefaultProjectTypeData';
12
+ };
13
+ export declare const CASE_TYPES: readonly ['Functional', 'Performance', 'Regression', 'Security', 'Smoke', 'Usability'];
14
+ export declare const CASE_PRIORITIES: readonly ['Low', 'Medium', 'High', 'Urgent'];
15
+ export declare const CASE_STATUSES: readonly ['Draft', 'ReadyForReview', 'FixReviewComments', 'Approved', 'Rejected'];
16
+ export declare const RESULT_STATUSES: readonly ['Untested', 'Blocked', 'Passed', 'Failed'];
17
+ export declare const idx: (list: readonly string[], v: string) => number;
18
+ export declare const name: (list: readonly string[], i: number | undefined) => string;
19
+ export declare function findProject(client: any, ref: string): Promise<any>;
20
+ export declare function findSuite(client: any, project: any, ref: string): Promise<any>;
21
+ export declare function findDoc(client: any, _class: string, id: string, what: string): Promise<any>;
22
+ export declare const listTestProjects: (args: {
23
+ workspace?: string | undefined;
24
+ }) => Promise<{
25
+ content: Array<{
26
+ type: 'text';
27
+ text: string;
28
+ }>;
29
+ }>;
30
+ export declare const createTestProject: (args: {
31
+ workspace: string;
32
+ name: string;
33
+ description?: string | undefined;
34
+ isPrivate: boolean;
35
+ }) => Promise<{
36
+ content: Array<{
37
+ type: 'text';
38
+ text: string;
39
+ }>;
40
+ }>;
41
+ export declare const listTestCases: (args: {
42
+ workspace?: string | undefined;
43
+ project: string;
44
+ suite?: string | undefined;
45
+ status?: "Approved" | "Draft" | "FixReviewComments" | "ReadyForReview" | "Rejected" | undefined;
46
+ }) => Promise<{
47
+ content: Array<{
48
+ type: 'text';
49
+ text: string;
50
+ }>;
51
+ }>;
52
+ export declare const createTestSuite: (args: {
53
+ workspace: string;
54
+ project: string;
55
+ name: string;
56
+ description?: string | undefined;
57
+ parent?: string | undefined;
58
+ }) => Promise<{
59
+ content: Array<{
60
+ type: 'text';
61
+ text: string;
62
+ }>;
63
+ }>;
64
+ export declare const getTestCase: (args: {
65
+ workspace?: string | undefined;
66
+ caseId: string;
67
+ }) => Promise<{
68
+ content: Array<{
69
+ type: 'text';
70
+ text: string;
71
+ }>;
72
+ }>;
73
+ export declare const createTestCase: (args: {
74
+ workspace: string;
75
+ project: string;
76
+ suite: string;
77
+ name: string;
78
+ description?: string | undefined;
79
+ type: "Functional" | "Performance" | "Regression" | "Security" | "Smoke" | "Usability";
80
+ priority: "High" | "Low" | "Medium" | "Urgent";
81
+ status: "Approved" | "Draft" | "FixReviewComments" | "ReadyForReview" | "Rejected";
82
+ assignee?: string | undefined;
83
+ }) => Promise<{
84
+ content: Array<{
85
+ type: 'text';
86
+ text: string;
87
+ }>;
88
+ }>;
89
+ export declare const updateTestCase: (args: {
90
+ workspace: string;
91
+ caseId: string;
92
+ name?: string | undefined;
93
+ description?: string | undefined;
94
+ type?: "Functional" | "Performance" | "Regression" | "Security" | "Smoke" | "Usability" | undefined;
95
+ priority?: "High" | "Low" | "Medium" | "Urgent" | undefined;
96
+ status?: "Approved" | "Draft" | "FixReviewComments" | "ReadyForReview" | "Rejected" | undefined;
97
+ assignee?: string | null | undefined;
98
+ }) => Promise<{
99
+ content: Array<{
100
+ type: 'text';
101
+ text: string;
102
+ }>;
103
+ }>;
104
+ export declare const deleteTestCase: (args: {
105
+ workspace: string;
106
+ caseId: string;
107
+ }) => Promise<{
108
+ content: Array<{
109
+ type: 'text';
110
+ text: string;
111
+ }>;
112
+ }>;
113
+ export declare const listTestPlans: (args: {
114
+ workspace?: string | undefined;
115
+ project: string;
116
+ }) => Promise<{
117
+ content: Array<{
118
+ type: 'text';
119
+ text: string;
120
+ }>;
121
+ }>;
122
+ export declare const createTestPlan: (args: {
123
+ workspace: string;
124
+ project: string;
125
+ name: string;
126
+ suites?: string[] | undefined;
127
+ caseIds?: string[] | undefined;
128
+ }) => Promise<{
129
+ content: Array<{
130
+ type: 'text';
131
+ text: string;
132
+ }>;
133
+ }>;
134
+ export declare const createTestRun: (args: {
135
+ workspace: string;
136
+ project: string;
137
+ name: string;
138
+ plan?: string | undefined;
139
+ suites?: string[] | undefined;
140
+ caseIds?: string[] | undefined;
141
+ dueDate?: string | undefined;
142
+ }) => Promise<{
143
+ content: Array<{
144
+ type: 'text';
145
+ text: string;
146
+ }>;
147
+ }>;
148
+ export declare const listTestRuns: (args: {
149
+ workspace?: string | undefined;
150
+ project: string;
151
+ limit: number;
152
+ }) => Promise<{
153
+ content: Array<{
154
+ type: 'text';
155
+ text: string;
156
+ }>;
157
+ }>;
158
+ export declare const getTestRun: (args: {
159
+ workspace?: string | undefined;
160
+ runId: string;
161
+ }) => Promise<{
162
+ content: Array<{
163
+ type: 'text';
164
+ text: string;
165
+ }>;
166
+ }>;
167
+ export declare const setTestResult: (args: {
168
+ workspace: string;
169
+ result: string;
170
+ runId?: string | undefined;
171
+ status: "Blocked" | "Failed" | "Passed" | "Untested";
172
+ comment?: string | undefined;
173
+ assignee?: string | null | undefined;
174
+ }) => Promise<{
175
+ content: Array<{
176
+ type: 'text';
177
+ text: string;
178
+ }>;
179
+ }>;
180
+ export declare const updateTestSuite: (args: {
181
+ workspace: string;
182
+ project: string;
183
+ suite: string;
184
+ name?: string | undefined;
185
+ description?: string | undefined;
186
+ parent?: string | null | undefined;
187
+ }) => Promise<{
188
+ content: Array<{
189
+ type: 'text';
190
+ text: string;
191
+ }>;
192
+ }>;
193
+ export declare const deleteTestSuite: (args: {
194
+ workspace: string;
195
+ project: string;
196
+ suite: string;
197
+ recursive?: boolean | undefined;
198
+ }) => Promise<{
199
+ content: Array<{
200
+ type: 'text';
201
+ text: string;
202
+ }>;
203
+ }>;
204
+ export declare const updateTestRun: (args: {
205
+ workspace: string;
206
+ runId: string;
207
+ name?: string | undefined;
208
+ dueDate?: string | null | undefined;
209
+ description?: string | undefined;
210
+ }) => Promise<{
211
+ content: Array<{
212
+ type: 'text';
213
+ text: string;
214
+ }>;
215
+ }>;
216
+ export declare const deleteTestRun: (args: {
217
+ workspace: string;
218
+ runId: string;
219
+ }) => Promise<{
220
+ content: Array<{
221
+ type: 'text';
222
+ text: string;
223
+ }>;
224
+ }>;
225
+ export declare const deleteTestPlan: (args: {
226
+ workspace: string;
227
+ project: string;
228
+ plan: string;
229
+ }) => Promise<{
230
+ content: Array<{
231
+ type: 'text';
232
+ text: string;
233
+ }>;
234
+ }>;
@@ -0,0 +1,401 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteTestPlan = exports.deleteTestRun = exports.updateTestRun = exports.deleteTestSuite = exports.updateTestSuite = exports.setTestResult = exports.getTestRun = exports.listTestRuns = exports.createTestRun = exports.createTestPlan = exports.listTestPlans = exports.deleteTestCase = exports.updateTestCase = exports.createTestCase = exports.getTestCase = exports.createTestSuite = exports.listTestCases = exports.createTestProject = exports.listTestProjects = exports.name = exports.idx = exports.RESULT_STATUSES = exports.CASE_STATUSES = exports.CASE_PRIORITIES = exports.CASE_TYPES = exports.TM = void 0;
4
+ exports.findProject = findProject;
5
+ exports.findSuite = findSuite;
6
+ exports.findDoc = findDoc;
7
+ // Test management: projects → suites (nested) → cases; plans pick cases; runs hold one result per case.
8
+ // Mirrors Huly's test-management UI (CreateTestSuite, CreateTestCase, NewTestPlanPanel, NewTestRunPanel).
9
+ const core_1 = require("@hcengineering/core");
10
+ const connection_1 = require("../connection");
11
+ const errors_1 = require("../utils/errors");
12
+ const content_1 = require("../utils/content");
13
+ const format_1 = require("../utils/format");
14
+ const people_1 = require("../utils/people");
15
+ exports.TM = {
16
+ Project: 'testManagement:class:TestProject',
17
+ Suite: 'testManagement:class:TestSuite',
18
+ Case: 'testManagement:class:TestCase',
19
+ Plan: 'testManagement:class:TestPlan',
20
+ PlanItem: 'testManagement:class:TestPlanItem',
21
+ Run: 'testManagement:class:TestRun',
22
+ Result: 'testManagement:class:TestResult',
23
+ NoParent: 'testManagement:ids:NoParent',
24
+ SpaceType: 'testManagement:spaceType:DefaultProject',
25
+ TypeMixin: 'testManagement:mixin:DefaultProjectTypeData'
26
+ };
27
+ exports.CASE_TYPES = ['Functional', 'Performance', 'Regression', 'Security', 'Smoke', 'Usability'];
28
+ exports.CASE_PRIORITIES = ['Low', 'Medium', 'High', 'Urgent'];
29
+ exports.CASE_STATUSES = ['Draft', 'ReadyForReview', 'FixReviewComments', 'Approved', 'Rejected'];
30
+ exports.RESULT_STATUSES = ['Untested', 'Blocked', 'Passed', 'Failed'];
31
+ const idx = (list, v) => list.indexOf(v);
32
+ exports.idx = idx;
33
+ const name = (list, i) => (i != null ? list[i] : undefined) ?? '—';
34
+ exports.name = name;
35
+ async function findProject(client, ref) {
36
+ const all = await client.findAll(exports.TM.Project, {});
37
+ const hits = all.filter((p) => p._id === ref || String(p.name).toLowerCase() === ref.toLowerCase());
38
+ if (hits.length === 1)
39
+ return hits[0];
40
+ if (hits.length > 1)
41
+ throw new Error(`Test project '${ref}' is ambiguous: ${hits.map((p) => p._id).join(', ')}.`);
42
+ throw new Error(`Test project '${ref}' not found. Available: ${all.map((p) => p.name).join(', ') || 'none'}.`);
43
+ }
44
+ async function findSuite(client, project, ref) {
45
+ const suites = await client.findAll(exports.TM.Suite, { space: project._id });
46
+ const hits = suites.filter((s) => s._id === ref || String(s.name).toLowerCase() === ref.toLowerCase());
47
+ if (hits.length === 1)
48
+ return hits[0];
49
+ if (hits.length > 1)
50
+ throw new Error(`Suite '${ref}' is ambiguous — use its id: ${hits.map((s) => s._id).join(', ')}.`);
51
+ throw new Error(`Suite '${ref}' not found in ${String(project.name)}.`);
52
+ }
53
+ async function findDoc(client, _class, id, what) {
54
+ const doc = await client.findOne(_class, { _id: id });
55
+ if (doc == null)
56
+ throw new Error(`${what} '${id}' not found.`);
57
+ return doc;
58
+ }
59
+ exports.listTestProjects = (0, errors_1.wrapToolHandler)(async (args) => {
60
+ const client = await (0, connection_1.getConnection)(args.workspace);
61
+ const projects = await client.findAll(exports.TM.Project, { archived: false });
62
+ if (projects.length === 0)
63
+ return 'No test projects.';
64
+ const lines = [];
65
+ for (const p of projects) {
66
+ const cases = await client.findAll(exports.TM.Case, { space: p._id }, { limit: 1, total: true });
67
+ const runs = await client.findAll(exports.TM.Run, { space: p._id }, { limit: 1, total: true });
68
+ lines.push(`- **${String(p.name)}** — ${String(cases.total ?? cases.length)} case(s), ${String(runs.total ?? runs.length)} run(s) — \`${String(p._id)}\``);
69
+ }
70
+ return `## Test projects (${projects.length})\n\n${lines.join('\n')}`;
71
+ });
72
+ exports.createTestProject = (0, errors_1.wrapToolHandler)(async (args) => {
73
+ const client = await (0, connection_1.getConnection)(args.workspace);
74
+ const existing = await client.findAll(exports.TM.Project, {});
75
+ const same = existing.find((p) => String(p.name).toLowerCase() === args.name.toLowerCase());
76
+ if (same != null)
77
+ return `ℹ️ Test project **${String(same.name)}** already exists — \`${String(same._id)}\`.`;
78
+ const me = await (0, people_1.currentAccountUuid)(client);
79
+ const id = await client.createDoc(exports.TM.Project, 'core:space:Space', {
80
+ name: args.name, description: args.description ?? '', private: args.isPrivate, members: [me], owners: [me], archived: false, type: exports.TM.SpaceType
81
+ });
82
+ await client.createMixin(id, exports.TM.Project, 'core:space:Space', exports.TM.TypeMixin, {});
83
+ return `✅ Test project **${args.name}** created — \`${String(id)}\`.`;
84
+ });
85
+ exports.listTestCases = (0, errors_1.wrapToolHandler)(async (args) => {
86
+ const client = await (0, connection_1.getConnection)(args.workspace);
87
+ const project = await findProject(client, args.project);
88
+ const suites = await client.findAll(exports.TM.Suite, { space: project._id }, { sort: { name: core_1.SortingOrder.Ascending } });
89
+ const query = { space: project._id };
90
+ if (args.status != null)
91
+ query.status = (0, exports.idx)(exports.CASE_STATUSES, args.status);
92
+ const cases = await client.findAll(exports.TM.Case, query, { sort: { name: core_1.SortingOrder.Ascending } });
93
+ const people = await (0, people_1.peopleNames)(client);
94
+ const root = args.suite != null ? (await findSuite(client, project, args.suite))._id : exports.TM.NoParent;
95
+ const lines = [];
96
+ const walk = (parent, depth) => {
97
+ for (const s of suites.filter((x) => x.parent === parent)) {
98
+ lines.push(`${' '.repeat(depth)}- 📂 **${String(s.name)}** — \`${String(s._id)}\``);
99
+ for (const c of cases.filter((x) => x.attachedTo === s._id)) {
100
+ const who = c.assignee != null ? ` · @${people.get(c.assignee) ?? '?'}` : '';
101
+ lines.push(`${' '.repeat(depth + 1)}- ${String(c.name)} [${(0, exports.name)(exports.CASE_TYPES, c.type)}, ${(0, exports.name)(exports.CASE_PRIORITIES, c.priority)}, ${(0, exports.name)(exports.CASE_STATUSES, c.status)}]${who} — \`${String(c._id)}\``);
102
+ }
103
+ if (depth < 20)
104
+ walk(s._id, depth + 1);
105
+ }
106
+ };
107
+ if (args.suite != null) {
108
+ const s = suites.find((x) => x._id === root);
109
+ lines.push(`- 📂 **${String(s.name)}** — \`${String(s._id)}\``);
110
+ for (const c of cases.filter((x) => x.attachedTo === root))
111
+ lines.push(` - ${String(c.name)} [${(0, exports.name)(exports.CASE_TYPES, c.type)}, ${(0, exports.name)(exports.CASE_PRIORITIES, c.priority)}, ${(0, exports.name)(exports.CASE_STATUSES, c.status)}] — \`${String(c._id)}\``);
112
+ walk(root, 1);
113
+ }
114
+ else
115
+ walk(root, 0);
116
+ if (lines.length === 0)
117
+ return `No suites in ${String(project.name)} yet — create one with create_test_suite.`;
118
+ return `## ${String(project.name)}: ${cases.length} case(s)\n\n${lines.join('\n')}`;
119
+ });
120
+ exports.createTestSuite = (0, errors_1.wrapToolHandler)(async (args) => {
121
+ const client = await (0, connection_1.getConnection)(args.workspace);
122
+ const project = await findProject(client, args.project);
123
+ const parent = args.parent != null ? await findSuite(client, project, args.parent) : undefined;
124
+ const id = await client.createDoc(exports.TM.Suite, project._id, { name: args.name, description: args.description ?? '', parent: parent?._id ?? exports.TM.NoParent });
125
+ return `✅ Suite **${args.name}** created${parent != null ? ` in "${String(parent.name)}"` : ''} — \`${String(id)}\`.`;
126
+ });
127
+ exports.getTestCase = (0, errors_1.wrapToolHandler)(async (args) => {
128
+ const client = await (0, connection_1.getConnection)(args.workspace);
129
+ const c = await findDoc(client, exports.TM.Case, args.caseId, 'Test case');
130
+ const suite = await client.findOne(exports.TM.Suite, { _id: c.attachedTo });
131
+ const people = await (0, people_1.peopleNames)(client);
132
+ const results = await client.findAll(exports.TM.Result, { testCase: c._id }, { sort: { modifiedOn: core_1.SortingOrder.Descending }, limit: 5 });
133
+ const lines = [
134
+ `## ${String(c.name)}`,
135
+ '',
136
+ `**Suite:** ${String(suite?.name ?? '—')} · **Type:** ${(0, exports.name)(exports.CASE_TYPES, c.type)} · **Priority:** ${(0, exports.name)(exports.CASE_PRIORITIES, c.priority)} · **Status:** ${(0, exports.name)(exports.CASE_STATUSES, c.status)}`,
137
+ `**Assignee:** ${c.assignee != null ? people.get(c.assignee) ?? '?' : '—'} · **Id:** \`${String(c._id)}\``,
138
+ '',
139
+ '### Steps / description',
140
+ (await (0, content_1.readMarkup)(args.workspace, c.description)) || '_(empty)_'
141
+ ];
142
+ if (results.length > 0) {
143
+ lines.push('', '### Latest results');
144
+ for (const r of results) {
145
+ const run = await client.findOne(exports.TM.Run, { _id: r.attachedTo });
146
+ lines.push(`- ${(0, exports.name)(exports.RESULT_STATUSES, r.status)} in run "${String(run?.name ?? '?')}" (${(0, format_1.formatDate)(r.modifiedOn)})`);
147
+ }
148
+ }
149
+ return lines.join('\n');
150
+ });
151
+ exports.createTestCase = (0, errors_1.wrapToolHandler)(async (args) => {
152
+ const client = await (0, connection_1.getConnection)(args.workspace);
153
+ const project = await findProject(client, args.project);
154
+ const suite = await findSuite(client, project, args.suite);
155
+ const assignee = args.assignee != null ? (await (0, people_1.findPerson)(client, args.assignee))._id : null;
156
+ const id = await client.addCollection(exports.TM.Case, project._id, suite._id, exports.TM.Suite, 'testCases', {
157
+ name: args.name,
158
+ description: args.description != null && args.description !== '' ? await (0, content_1.writeMarkup)(args.workspace, 'testcase', args.description) : null,
159
+ type: (0, exports.idx)(exports.CASE_TYPES, args.type ?? 'Functional'),
160
+ priority: (0, exports.idx)(exports.CASE_PRIORITIES, args.priority ?? 'Medium'),
161
+ status: (0, exports.idx)(exports.CASE_STATUSES, args.status ?? 'Draft'),
162
+ assignee,
163
+ attachments: 0
164
+ });
165
+ return `✅ Test case **${args.name}** created in "${String(suite.name)}" — \`${String(id)}\`.`;
166
+ });
167
+ exports.updateTestCase = (0, errors_1.wrapToolHandler)(async (args) => {
168
+ const client = await (0, connection_1.getConnection)(args.workspace);
169
+ const c = await findDoc(client, exports.TM.Case, args.caseId, 'Test case');
170
+ const update = {};
171
+ if (args.name != null)
172
+ update.name = args.name;
173
+ if (args.description != null)
174
+ update.description = await (0, content_1.writeMarkup)(args.workspace, 'testcase', args.description);
175
+ if (args.type != null)
176
+ update.type = (0, exports.idx)(exports.CASE_TYPES, args.type);
177
+ if (args.priority != null)
178
+ update.priority = (0, exports.idx)(exports.CASE_PRIORITIES, args.priority);
179
+ if (args.status != null)
180
+ update.status = (0, exports.idx)(exports.CASE_STATUSES, args.status);
181
+ if (args.assignee !== undefined)
182
+ update.assignee = args.assignee != null ? (await (0, people_1.findPerson)(client, args.assignee))._id : null;
183
+ if (Object.keys(update).length === 0)
184
+ throw new Error('Nothing to change.');
185
+ await client.updateCollection(exports.TM.Case, c.space, c._id, c.attachedTo, exports.TM.Suite, 'testCases', update);
186
+ return `✅ Test case **${String(args.name ?? c.name)}** updated: ${Object.keys(update).join(', ')}.`;
187
+ });
188
+ exports.deleteTestCase = (0, errors_1.wrapToolHandler)(async (args) => {
189
+ const client = await (0, connection_1.getConnection)(args.workspace);
190
+ const c = await findDoc(client, exports.TM.Case, args.caseId, 'Test case');
191
+ await client.removeCollection(exports.TM.Case, c.space, c._id, c.attachedTo, exports.TM.Suite, 'testCases');
192
+ return `✅ Test case **${String(c.name)}** deleted.`;
193
+ });
194
+ /** Cases selected by ids and/or suites (including nested suites). */
195
+ async function selectCases(client, project, caseIds, suites) {
196
+ const out = new Map();
197
+ for (const id of caseIds ?? []) {
198
+ const c = await findDoc(client, exports.TM.Case, id, 'Test case');
199
+ if (c.space !== project._id)
200
+ throw new Error(`Test case '${id}' belongs to another project.`);
201
+ out.set(c._id, c);
202
+ }
203
+ if ((suites ?? []).length > 0) {
204
+ const all = await client.findAll(exports.TM.Suite, { space: project._id });
205
+ let level = await Promise.all((suites ?? []).map(async (s) => (await findSuite(client, project, s))._id));
206
+ const ids = new Set(level);
207
+ while (level.length > 0) {
208
+ level = all.filter((s) => level.includes(s.parent) && !ids.has(s._id)).map((s) => s._id);
209
+ for (const l of level)
210
+ ids.add(l);
211
+ }
212
+ const cases = await client.findAll(exports.TM.Case, { space: project._id, attachedTo: { $in: [...ids] } });
213
+ for (const c of cases)
214
+ out.set(c._id, c);
215
+ }
216
+ return [...out.values()];
217
+ }
218
+ exports.listTestPlans = (0, errors_1.wrapToolHandler)(async (args) => {
219
+ const client = await (0, connection_1.getConnection)(args.workspace);
220
+ const project = await findProject(client, args.project);
221
+ const plans = await client.findAll(exports.TM.Plan, { space: project._id });
222
+ if (plans.length === 0)
223
+ return `No test plans in ${String(project.name)}.`;
224
+ const lines = [];
225
+ for (const p of plans) {
226
+ const items = await client.findAll(exports.TM.PlanItem, { attachedTo: p._id });
227
+ lines.push(`- **${String(p.name)}** — ${items.length} case(s) — \`${String(p._id)}\``);
228
+ }
229
+ return `## Test plans in ${String(project.name)} (${plans.length})\n\n${lines.join('\n')}`;
230
+ });
231
+ exports.createTestPlan = (0, errors_1.wrapToolHandler)(async (args) => {
232
+ const client = await (0, connection_1.getConnection)(args.workspace);
233
+ const project = await findProject(client, args.project);
234
+ const cases = await selectCases(client, project, args.caseIds, args.suites);
235
+ if (cases.length === 0)
236
+ throw new Error('No test cases selected (caseIds or suites).');
237
+ const ops = client.apply?.() ?? client;
238
+ const id = await ops.createDoc(exports.TM.Plan, project._id, { name: args.name, description: null });
239
+ for (const c of cases) {
240
+ await ops.addCollection(exports.TM.PlanItem, project._id, id, exports.TM.Plan, 'items', { testCase: c._id, testSuite: c.attachedTo, assignee: c.assignee ?? null });
241
+ }
242
+ if (ops !== client)
243
+ await ops.commit();
244
+ return `✅ Test plan **${args.name}** with ${cases.length} case(s) — \`${String(id)}\`.`;
245
+ });
246
+ exports.createTestRun = (0, errors_1.wrapToolHandler)(async (args) => {
247
+ const client = await (0, connection_1.getConnection)(args.workspace);
248
+ const project = await findProject(client, args.project);
249
+ let cases = await selectCases(client, project, args.caseIds, args.suites);
250
+ if (args.plan != null) {
251
+ const plans = await client.findAll(exports.TM.Plan, { space: project._id });
252
+ const plan = plans.find((p) => p._id === args.plan || String(p.name).toLowerCase() === args.plan.toLowerCase());
253
+ if (plan == null)
254
+ throw new Error(`Test plan '${args.plan}' not found.`);
255
+ const items = await client.findAll(exports.TM.PlanItem, { attachedTo: plan._id });
256
+ const fromPlan = await Promise.all(items.map(async (i) => await client.findOne(exports.TM.Case, { _id: i.testCase })));
257
+ cases = [...new Map([...cases, ...fromPlan.filter((c) => c != null)].map((c) => [c._id, c])).values()];
258
+ }
259
+ if (cases.length === 0)
260
+ throw new Error('No test cases selected (plan, suites or caseIds).');
261
+ const dueDate = args.dueDate != null ? new Date(args.dueDate).getTime() : undefined;
262
+ if (dueDate != null && Number.isNaN(dueDate))
263
+ throw new Error(`Invalid dueDate '${args.dueDate}'.`);
264
+ const ops = client.apply?.() ?? client;
265
+ const id = await ops.createDoc(exports.TM.Run, project._id, { name: args.name, description: null, ...(dueDate != null ? { dueDate } : {}) });
266
+ for (const c of cases) {
267
+ await ops.addCollection(exports.TM.Result, project._id, id, exports.TM.Run, 'results', { name: c.name, testCase: c._id, testSuite: c.attachedTo, description: null, status: (0, exports.idx)(exports.RESULT_STATUSES, 'Untested'), assignee: c.assignee ?? null });
268
+ }
269
+ if (ops !== client)
270
+ await ops.commit();
271
+ return `✅ Test run **${args.name}** started with ${cases.length} case(s) — \`${String(id)}\`. Record results with set_test_result.`;
272
+ });
273
+ exports.listTestRuns = (0, errors_1.wrapToolHandler)(async (args) => {
274
+ const client = await (0, connection_1.getConnection)(args.workspace);
275
+ const project = await findProject(client, args.project);
276
+ const runs = await client.findAll(exports.TM.Run, { space: project._id }, { sort: { modifiedOn: core_1.SortingOrder.Descending }, limit: args.limit });
277
+ if (runs.length === 0)
278
+ return `No test runs in ${String(project.name)}.`;
279
+ const lines = [];
280
+ for (const r of runs) {
281
+ const results = await client.findAll(exports.TM.Result, { attachedTo: r._id });
282
+ const counts = exports.RESULT_STATUSES.map((s, i) => [s, results.filter((x) => (x.status ?? 0) === i).length]).filter(([, n]) => n > 0);
283
+ lines.push(`- **${String(r.name)}** — ${counts.map(([s, n]) => `${n} ${s}`).join(', ') || 'empty'}${r.dueDate != null ? ` · due ${(0, format_1.formatDate)(r.dueDate)}` : ''} — \`${String(r._id)}\``);
284
+ }
285
+ return `## Test runs in ${String(project.name)} (${runs.length})\n\n${lines.join('\n')}`;
286
+ });
287
+ exports.getTestRun = (0, errors_1.wrapToolHandler)(async (args) => {
288
+ const client = await (0, connection_1.getConnection)(args.workspace);
289
+ const run = await findDoc(client, exports.TM.Run, args.runId, 'Test run');
290
+ const results = await client.findAll(exports.TM.Result, { attachedTo: run._id });
291
+ const people = await (0, people_1.peopleNames)(client);
292
+ const lines = results.sort((a, b) => String(a.name).localeCompare(String(b.name))).map((r) => `- ${(0, exports.name)(exports.RESULT_STATUSES, r.status ?? 0)} — **${String(r.name)}**${r.assignee != null ? ` · @${people.get(r.assignee) ?? '?'}` : ''} — \`${String(r._id)}\``);
293
+ const done = results.filter((r) => (r.status ?? 0) !== 0).length;
294
+ return `## Run: ${String(run.name)} (${done}/${results.length} tested)\n\n${lines.join('\n')}`;
295
+ });
296
+ exports.setTestResult = (0, errors_1.wrapToolHandler)(async (args) => {
297
+ const client = await (0, connection_1.getConnection)(args.workspace);
298
+ let result = await client.findOne(exports.TM.Result, { _id: args.result });
299
+ if (result == null && args.runId != null) {
300
+ const all = await client.findAll(exports.TM.Result, { attachedTo: args.runId });
301
+ const hits = all.filter((r) => r.testCase === args.result || String(r.name).toLowerCase() === args.result.toLowerCase());
302
+ if (hits.length > 1)
303
+ throw new Error(`'${args.result}' matches several results in this run — use the result id.`);
304
+ result = hits[0];
305
+ }
306
+ if (result == null)
307
+ throw new Error(`Test result '${args.result}' not found (pass a result id, or runId plus case id/name).`);
308
+ const update = { status: (0, exports.idx)(exports.RESULT_STATUSES, args.status) };
309
+ if (args.comment != null)
310
+ update.description = await (0, content_1.writeMarkup)(args.workspace, 'testresult', args.comment);
311
+ if (args.assignee !== undefined)
312
+ update.assignee = args.assignee != null ? (await (0, people_1.findPerson)(client, args.assignee))._id : null;
313
+ await client.updateCollection(exports.TM.Result, result.space, result._id, result.attachedTo, exports.TM.Run, 'results', update);
314
+ return `✅ **${String(result.name)}**: ${args.status}${args.comment != null ? ' (with comment)' : ''}.`;
315
+ });
316
+ exports.updateTestSuite = (0, errors_1.wrapToolHandler)(async (args) => {
317
+ const client = await (0, connection_1.getConnection)(args.workspace);
318
+ const project = await findProject(client, args.project);
319
+ const suite = await findSuite(client, project, args.suite);
320
+ const update = {};
321
+ if (args.name != null)
322
+ update.name = args.name;
323
+ if (args.description != null)
324
+ update.description = args.description;
325
+ if (args.parent !== undefined) {
326
+ if (args.parent == null)
327
+ update.parent = exports.TM.NoParent;
328
+ else {
329
+ const parent = await findSuite(client, project, args.parent);
330
+ const all = await client.findAll(exports.TM.Suite, { space: project._id });
331
+ for (let p = parent; p != null; p = all.find((s) => s._id === p.parent)) {
332
+ if (p._id === suite._id)
333
+ throw new Error('A suite cannot move into itself or one of its sub-suites.');
334
+ }
335
+ update.parent = parent._id;
336
+ }
337
+ }
338
+ if (Object.keys(update).length === 0)
339
+ throw new Error('Nothing to change.');
340
+ await client.updateDoc(exports.TM.Suite, suite.space, suite._id, update);
341
+ return `✅ Suite **${String(args.name ?? suite.name)}** updated: ${Object.keys(update).join(', ')}.`;
342
+ });
343
+ exports.deleteTestSuite = (0, errors_1.wrapToolHandler)(async (args) => {
344
+ const client = await (0, connection_1.getConnection)(args.workspace);
345
+ const project = await findProject(client, args.project);
346
+ const suite = await findSuite(client, project, args.suite);
347
+ const all = await client.findAll(exports.TM.Suite, { space: project._id });
348
+ const ids = [suite._id];
349
+ for (let i = 0; i < ids.length; i++)
350
+ ids.push(...all.filter((s) => s.parent === ids[i]).map((s) => s._id));
351
+ const cases = await client.findAll(exports.TM.Case, { space: project._id, attachedTo: { $in: ids } });
352
+ if ((ids.length > 1 || cases.length > 0) && args.recursive !== true)
353
+ throw new Error(`Suite '${String(suite.name)}' holds ${ids.length - 1} sub-suite(s) and ${cases.length} case(s) — pass recursive: true to delete them too.`);
354
+ for (const c of cases)
355
+ await client.removeCollection(exports.TM.Case, c.space, c._id, c.attachedTo, exports.TM.Suite, 'testCases');
356
+ for (const id of ids.reverse())
357
+ await client.removeDoc(exports.TM.Suite, project._id, id);
358
+ return `🗑️ Suite **${String(suite.name)}** deleted${ids.length > 1 || cases.length > 0 ? ` with ${ids.length - 1} sub-suite(s) and ${cases.length} case(s)` : ''}.`;
359
+ });
360
+ exports.updateTestRun = (0, errors_1.wrapToolHandler)(async (args) => {
361
+ const client = await (0, connection_1.getConnection)(args.workspace);
362
+ const run = await findDoc(client, exports.TM.Run, args.runId, 'Test run');
363
+ const update = {};
364
+ if (args.name != null)
365
+ update.name = args.name;
366
+ if (args.dueDate !== undefined) {
367
+ const t = args.dueDate != null ? new Date(args.dueDate).getTime() : null;
368
+ if (t != null && Number.isNaN(t))
369
+ throw new Error(`Invalid dueDate '${String(args.dueDate)}'.`);
370
+ update.dueDate = t;
371
+ }
372
+ if (args.description != null)
373
+ update.description = await (0, content_1.writeMarkup)(args.workspace, 'testrun', args.description);
374
+ if (Object.keys(update).length === 0)
375
+ throw new Error('Nothing to change.');
376
+ await client.updateDoc(exports.TM.Run, run.space, run._id, update);
377
+ return `✅ Run **${String(args.name ?? run.name)}** updated: ${Object.keys(update).join(', ')}.`;
378
+ });
379
+ exports.deleteTestRun = (0, errors_1.wrapToolHandler)(async (args) => {
380
+ const client = await (0, connection_1.getConnection)(args.workspace);
381
+ const run = await findDoc(client, exports.TM.Run, args.runId, 'Test run');
382
+ const results = await client.findAll(exports.TM.Result, { attachedTo: run._id });
383
+ for (const r of results)
384
+ await client.removeCollection(exports.TM.Result, r.space, r._id, run._id, exports.TM.Run, 'results');
385
+ await client.removeDoc(exports.TM.Run, run.space, run._id);
386
+ return `🗑️ Run **${String(run.name)}** deleted with ${results.length} result(s).`;
387
+ });
388
+ exports.deleteTestPlan = (0, errors_1.wrapToolHandler)(async (args) => {
389
+ const client = await (0, connection_1.getConnection)(args.workspace);
390
+ const project = await findProject(client, args.project);
391
+ const plans = await client.findAll(exports.TM.Plan, { space: project._id });
392
+ const plan = plans.find((p) => p._id === args.plan || String(p.name).toLowerCase() === args.plan.toLowerCase());
393
+ if (plan == null)
394
+ throw new Error(`Test plan '${args.plan}' not found.`);
395
+ const items = await client.findAll(exports.TM.PlanItem, { attachedTo: plan._id });
396
+ for (const i of items)
397
+ await client.removeCollection(exports.TM.PlanItem, i.space, i._id, plan._id, exports.TM.Plan, 'items');
398
+ await client.removeDoc(exports.TM.Plan, plan.space, plan._id);
399
+ return `🗑️ Test plan **${String(plan.name)}** deleted (${items.length} item(s); runs made from it stay).`;
400
+ });
401
+ //# sourceMappingURL=testing.js.map