@crypto512/jicon-mcp 2.2.1 → 2.3.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROMPT.md +10 -28
- package/README.md +39 -6
- package/TOOL_LIST.md +542 -407
- package/dist/config/constants.d.ts +28 -0
- package/dist/config/constants.d.ts.map +1 -1
- package/dist/config/constants.js +34 -0
- package/dist/config/constants.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +12 -2
- package/dist/config/loader.js.map +1 -1
- package/dist/config/types.d.ts +49 -6
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +18 -0
- package/dist/config/types.js.map +1 -1
- package/dist/confluence/client.d.ts.map +1 -1
- package/dist/confluence/client.js +7 -2
- package/dist/confluence/client.js.map +1 -1
- package/dist/confluence/formatters.d.ts +20 -0
- package/dist/confluence/formatters.d.ts.map +1 -1
- package/dist/confluence/formatters.js +74 -8
- package/dist/confluence/formatters.js.map +1 -1
- package/dist/confluence/tools.d.ts +16 -20
- package/dist/confluence/tools.d.ts.map +1 -1
- package/dist/confluence/tools.js +71 -68
- package/dist/confluence/tools.js.map +1 -1
- package/dist/credentials/extractor.d.ts +15 -5
- package/dist/credentials/extractor.d.ts.map +1 -1
- package/dist/credentials/extractor.js +99 -12
- package/dist/credentials/extractor.js.map +1 -1
- package/dist/credentials/index.d.ts +4 -3
- package/dist/credentials/index.d.ts.map +1 -1
- package/dist/credentials/index.js +3 -2
- package/dist/credentials/index.js.map +1 -1
- package/dist/credentials/types.d.ts +22 -0
- package/dist/credentials/types.d.ts.map +1 -1
- package/dist/credentials/types.js.map +1 -1
- package/dist/index.js +211 -176
- package/dist/index.js.map +1 -1
- package/dist/jira/activity-tools.d.ts +8 -15
- package/dist/jira/activity-tools.d.ts.map +1 -1
- package/dist/jira/activity-tools.js +131 -90
- package/dist/jira/activity-tools.js.map +1 -1
- package/dist/jira/client.d.ts +24 -0
- package/dist/jira/client.d.ts.map +1 -1
- package/dist/jira/client.js +65 -6
- package/dist/jira/client.js.map +1 -1
- package/dist/jira/formatters.d.ts +61 -0
- package/dist/jira/formatters.d.ts.map +1 -1
- package/dist/jira/formatters.js +83 -11
- package/dist/jira/formatters.js.map +1 -1
- package/dist/jira/tools.d.ts +78 -26
- package/dist/jira/tools.d.ts.map +1 -1
- package/dist/jira/tools.js +293 -130
- package/dist/jira/tools.js.map +1 -1
- package/dist/permissions/filter.d.ts.map +1 -1
- package/dist/permissions/filter.js +8 -4
- package/dist/permissions/filter.js.map +1 -1
- package/dist/permissions/tool-registry.d.ts +15 -13
- package/dist/permissions/tool-registry.d.ts.map +1 -1
- package/dist/permissions/tool-registry.js +19 -10
- package/dist/permissions/tool-registry.js.map +1 -1
- package/dist/session/context.d.ts +81 -0
- package/dist/session/context.d.ts.map +1 -0
- package/dist/session/context.js +107 -0
- package/dist/session/context.js.map +1 -0
- package/dist/session/index.d.ts +12 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +22 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/manager.d.ts +186 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +383 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/tempo/client.d.ts +14 -0
- package/dist/tempo/client.d.ts.map +1 -1
- package/dist/tempo/client.js +57 -0
- package/dist/tempo/client.js.map +1 -1
- package/dist/tempo/formatters.d.ts +13 -0
- package/dist/tempo/formatters.d.ts.map +1 -1
- package/dist/tempo/formatters.js +106 -20
- package/dist/tempo/formatters.js.map +1 -1
- package/dist/tempo/tools.d.ts +14 -13
- package/dist/tempo/tools.d.ts.map +1 -1
- package/dist/tempo/tools.js +203 -33
- package/dist/tempo/tools.js.map +1 -1
- package/dist/tempo/types.d.ts +20 -6
- package/dist/tempo/types.d.ts.map +1 -1
- package/dist/transport/http.d.ts +21 -5
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +193 -22
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/index.d.ts +7 -2
- package/dist/transport/index.d.ts.map +1 -1
- package/dist/transport/index.js +10 -4
- package/dist/transport/index.js.map +1 -1
- package/dist/utils/buffer-tools.d.ts +48 -724
- package/dist/utils/buffer-tools.d.ts.map +1 -1
- package/dist/utils/buffer-tools.js +337 -170
- package/dist/utils/buffer-tools.js.map +1 -1
- package/dist/utils/content-buffer.d.ts +10 -31
- package/dist/utils/content-buffer.d.ts.map +1 -1
- package/dist/utils/content-buffer.js +12 -86
- package/dist/utils/content-buffer.js.map +1 -1
- package/dist/utils/http-client.d.ts.map +1 -1
- package/dist/utils/http-client.js +99 -2
- package/dist/utils/http-client.js.map +1 -1
- package/dist/utils/jicon-help.d.ts +3 -3
- package/dist/utils/jicon-help.d.ts.map +1 -1
- package/dist/utils/jicon-help.js +164 -312
- package/dist/utils/jicon-help.js.map +1 -1
- package/dist/utils/logger.d.ts +43 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +102 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/plantuml/tools.d.ts.map +1 -1
- package/dist/utils/plantuml/tools.js +10 -9
- package/dist/utils/plantuml/tools.js.map +1 -1
- package/dist/utils/response-formatter.d.ts +20 -2
- package/dist/utils/response-formatter.d.ts.map +1 -1
- package/dist/utils/response-formatter.js +147 -17
- package/dist/utils/response-formatter.js.map +1 -1
- package/dist/utils/sandbox/formatters.d.ts +25 -0
- package/dist/utils/sandbox/formatters.d.ts.map +1 -0
- package/dist/utils/sandbox/formatters.js +690 -0
- package/dist/utils/sandbox/formatters.js.map +1 -0
- package/dist/utils/sandbox/helpers.d.ts +16 -0
- package/dist/utils/sandbox/helpers.d.ts.map +1 -0
- package/dist/utils/sandbox/helpers.js +252 -0
- package/dist/utils/sandbox/helpers.js.map +1 -0
- package/dist/utils/sandbox/index.d.ts +19 -0
- package/dist/utils/sandbox/index.d.ts.map +1 -0
- package/dist/utils/sandbox/index.js +269 -0
- package/dist/utils/sandbox/index.js.map +1 -0
- package/dist/utils/sandbox/schema.d.ts +55 -0
- package/dist/utils/sandbox/schema.d.ts.map +1 -0
- package/dist/utils/sandbox/schema.js +39 -0
- package/dist/utils/sandbox/schema.js.map +1 -0
- package/dist/utils/sandbox/types.d.ts +179 -0
- package/dist/utils/sandbox/types.d.ts.map +1 -0
- package/dist/utils/sandbox/types.js +8 -0
- package/dist/utils/sandbox/types.js.map +1 -0
- package/dist/utils/schemas/confluence.d.ts +41 -0
- package/dist/utils/schemas/confluence.d.ts.map +1 -0
- package/dist/utils/schemas/confluence.js +105 -0
- package/dist/utils/schemas/confluence.js.map +1 -0
- package/dist/utils/schemas/index.d.ts +77 -0
- package/dist/utils/schemas/index.d.ts.map +1 -0
- package/dist/utils/schemas/index.js +107 -0
- package/dist/utils/schemas/index.js.map +1 -0
- package/dist/utils/schemas/jira.d.ts +49 -0
- package/dist/utils/schemas/jira.d.ts.map +1 -0
- package/dist/utils/schemas/jira.js +153 -0
- package/dist/utils/schemas/jira.js.map +1 -0
- package/dist/utils/schemas/tempo.d.ts +29 -0
- package/dist/utils/schemas/tempo.d.ts.map +1 -0
- package/dist/utils/schemas/tempo.js +72 -0
- package/dist/utils/schemas/tempo.js.map +1 -0
- package/dist/utils/whoami-tools.d.ts +17 -0
- package/dist/utils/whoami-tools.d.ts.map +1 -0
- package/dist/utils/whoami-tools.js +90 -0
- package/dist/utils/whoami-tools.js.map +1 -0
- package/dist/utils/xhtml/error-locator.js +5 -5
- package/dist/utils/xhtml/error-locator.js.map +1 -1
- package/package.json +10 -9
- package/dist/credentials/client-factory.d.ts +0 -64
- package/dist/credentials/client-factory.d.ts.map +0 -1
- package/dist/credentials/client-factory.js +0 -110
- package/dist/credentials/client-factory.js.map +0 -1
- package/dist/credentials/context.d.ts +0 -25
- package/dist/credentials/context.d.ts.map +0 -1
- package/dist/credentials/context.js +0 -35
- package/dist/credentials/context.js.map +0 -1
- package/dist/utils/buffer-pipeline/index.d.ts +0 -30
- package/dist/utils/buffer-pipeline/index.d.ts.map +0 -1
- package/dist/utils/buffer-pipeline/index.js +0 -317
- package/dist/utils/buffer-pipeline/index.js.map +0 -1
- package/dist/utils/buffer-pipeline/output/csv.d.ts +0 -20
- package/dist/utils/buffer-pipeline/output/csv.d.ts.map +0 -1
- package/dist/utils/buffer-pipeline/output/csv.js +0 -117
- package/dist/utils/buffer-pipeline/output/csv.js.map +0 -1
- package/dist/utils/buffer-pipeline/output/json.d.ts +0 -16
- package/dist/utils/buffer-pipeline/output/json.d.ts.map +0 -1
- package/dist/utils/buffer-pipeline/output/json.js +0 -48
- package/dist/utils/buffer-pipeline/output/json.js.map +0 -1
- package/dist/utils/buffer-pipeline/output/markdown.d.ts +0 -15
- package/dist/utils/buffer-pipeline/output/markdown.d.ts.map +0 -1
- package/dist/utils/buffer-pipeline/output/markdown.js +0 -105
- package/dist/utils/buffer-pipeline/output/markdown.js.map +0 -1
- package/dist/utils/buffer-pipeline/output/xhtml-list.d.ts +0 -16
- package/dist/utils/buffer-pipeline/output/xhtml-list.d.ts.map +0 -1
- package/dist/utils/buffer-pipeline/output/xhtml-list.js +0 -81
- package/dist/utils/buffer-pipeline/output/xhtml-list.js.map +0 -1
- package/dist/utils/buffer-pipeline/output/xhtml-table.d.ts +0 -15
- package/dist/utils/buffer-pipeline/output/xhtml-table.d.ts.map +0 -1
- package/dist/utils/buffer-pipeline/output/xhtml-table.js +0 -176
- package/dist/utils/buffer-pipeline/output/xhtml-table.js.map +0 -1
- package/dist/utils/buffer-pipeline/schema.d.ts +0 -1878
- package/dist/utils/buffer-pipeline/schema.d.ts.map +0 -1
- package/dist/utils/buffer-pipeline/schema.js +0 -168
- package/dist/utils/buffer-pipeline/schema.js.map +0 -1
- package/dist/utils/buffer-pipeline/stages/filter.d.ts +0 -32
- package/dist/utils/buffer-pipeline/stages/filter.d.ts.map +0 -1
- package/dist/utils/buffer-pipeline/stages/filter.js +0 -208
- package/dist/utils/buffer-pipeline/stages/filter.js.map +0 -1
- package/dist/utils/buffer-pipeline/stages/format.d.ts +0 -45
- package/dist/utils/buffer-pipeline/stages/format.d.ts.map +0 -1
- package/dist/utils/buffer-pipeline/stages/format.js +0 -160
- package/dist/utils/buffer-pipeline/stages/format.js.map +0 -1
- package/dist/utils/buffer-pipeline/stages/group-by.d.ts +0 -25
- package/dist/utils/buffer-pipeline/stages/group-by.d.ts.map +0 -1
- package/dist/utils/buffer-pipeline/stages/group-by.js +0 -190
- package/dist/utils/buffer-pipeline/stages/group-by.js.map +0 -1
- package/dist/utils/buffer-pipeline/stages/select.d.ts +0 -54
- package/dist/utils/buffer-pipeline/stages/select.d.ts.map +0 -1
- package/dist/utils/buffer-pipeline/stages/select.js +0 -228
- package/dist/utils/buffer-pipeline/stages/select.js.map +0 -1
- package/dist/utils/buffer-pipeline/stages/sort.d.ts +0 -20
- package/dist/utils/buffer-pipeline/stages/sort.d.ts.map +0 -1
- package/dist/utils/buffer-pipeline/stages/sort.js +0 -96
- package/dist/utils/buffer-pipeline/stages/sort.js.map +0 -1
- package/dist/utils/buffer-pipeline/types.d.ts +0 -277
- package/dist/utils/buffer-pipeline/types.d.ts.map +0 -1
- package/dist/utils/buffer-pipeline/types.js +0 -8
- package/dist/utils/buffer-pipeline/types.js.map +0 -1
- package/dist/utils/plantuml/docker-manager.d.ts +0 -37
- package/dist/utils/plantuml/docker-manager.d.ts.map +0 -1
- package/dist/utils/plantuml/docker-manager.js +0 -284
- package/dist/utils/plantuml/docker-manager.js.map +0 -1
package/dist/jira/tools.js
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Jira MCP Tools
|
|
3
|
+
*
|
|
4
|
+
* All tool handlers use session-scoped clients and buffers via getSessionXxxClient()
|
|
5
|
+
* and getSessionBuffer() functions which provide per-session isolation.
|
|
3
6
|
*/
|
|
4
7
|
import { z } from "zod";
|
|
5
|
-
import { formatSuccess, formatSuccessJson, formatError, isApiError, formatConflictError } from "../utils/response-formatter.js";
|
|
6
|
-
import {
|
|
7
|
-
import { formatIssueMetadata } from "./formatters.js";
|
|
8
|
+
import { formatSuccess, formatSuccessJson, formatError, isApiError, formatConflictError, getMaxApiResults } from "../utils/response-formatter.js";
|
|
9
|
+
import { getSessionBuffer, getSessionJiraClient } from "../session/context.js";
|
|
10
|
+
import { formatIssueMetadata, formatCommentMetadata, formatProjectMetadata, formatBoardMetadata, formatSprintMetadata, formatWorklogMetadata, } from "./formatters.js";
|
|
8
11
|
import { formatTimeSpent } from "../utils/time-formatter.js";
|
|
9
12
|
/**
|
|
10
|
-
*
|
|
13
|
+
* Schema that accepts either a comma-separated string or an array of strings.
|
|
14
|
+
* Examples: "changelog,renderedFields" or ["changelog", "renderedFields"]
|
|
15
|
+
*/
|
|
16
|
+
const stringOrArray = z.preprocess((val) => typeof val === "string" ? val.split(",").map(s => s.trim()).filter(Boolean) : val, z.array(z.string()));
|
|
17
|
+
/**
|
|
18
|
+
* Create Jira tools using session-scoped clients
|
|
11
19
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
20
|
+
* Tools automatically use the JiraClient from the current session context,
|
|
21
|
+
* which provides per-session caching and credential isolation.
|
|
14
22
|
*/
|
|
15
|
-
export function createJiraTools(
|
|
23
|
+
export function createJiraTools() {
|
|
16
24
|
/**
|
|
17
25
|
* Helper to get client and throw if not available
|
|
18
26
|
*/
|
|
19
27
|
const requireClient = () => {
|
|
20
|
-
const client =
|
|
28
|
+
const client = getSessionJiraClient();
|
|
21
29
|
if (!client) {
|
|
22
30
|
throw new Error("Jira is not configured. Provide JIRA_URL and JIRA_API_TOKEN environment variables, or pass credentials via X-Jira-Url and X-Jira-Token headers.");
|
|
23
31
|
}
|
|
@@ -25,40 +33,56 @@ export function createJiraTools(getClient) {
|
|
|
25
33
|
};
|
|
26
34
|
return {
|
|
27
35
|
jira_search_issues: {
|
|
28
|
-
description: `Search Jira issues using JQL. Auto-fetches all results
|
|
36
|
+
description: `Search Jira issues using JQL. Auto-fetches all matching results.
|
|
29
37
|
|
|
30
38
|
Entry point for any Jira data analysis, reporting, or bulk workflow.
|
|
31
39
|
|
|
32
40
|
REQUIRES: Valid JQL query (see help(topic="jql") for syntax)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
TIP: Issue types are LOCALIZED (e.g., "Épopée" not "Epic", "Bogue" not "Bug").
|
|
42
|
+
Use jira_get_issue_types() first if unsure.
|
|
43
|
+
TIP: If you know a person's name but not their username, call jira_search_users(query="name") first.
|
|
44
|
+
|
|
45
|
+
TIME TRACKING: The "timespent" field is often NULL (time not logged directly on issue).
|
|
46
|
+
For accurate time data, use jira_get_total_worklogs(issueKey) which recursively sums
|
|
47
|
+
worklogs from the issue and all children. For bulk time reports, use tempo_get_worklogs.
|
|
48
|
+
DATE RANGES: JQL created/updated filters find issues CREATED/UPDATED in a period.
|
|
49
|
+
To find issues a user WORKED ON during a period (by logged time), use
|
|
50
|
+
tempo_get_worklogs(workerKey, dateFrom, dateTo) instead — it returns all worklogs
|
|
51
|
+
in the date range regardless of when issues were created.
|
|
52
|
+
|
|
53
|
+
RESPONSE: Returns buffer with flat fields (status, assignee, priority - not nested).
|
|
54
|
+
NEXT: buffer_transform for tables, buffer_get_items for analysis.
|
|
55
|
+
|
|
56
|
+
Example: jira_search_issues(jql="project=PROJ AND statusCategory != Done")`,
|
|
43
57
|
inputSchema: z.object({
|
|
44
58
|
jql: z.string().describe("JQL query string"),
|
|
45
|
-
fields:
|
|
59
|
+
fields: stringOrArray.optional().describe("Specific fields to return (string or array)"),
|
|
46
60
|
}),
|
|
47
61
|
handler: async (args) => {
|
|
48
62
|
try {
|
|
49
|
-
const result = await requireClient().searchIssuesAll(args.jql, args.fields,
|
|
50
|
-
//
|
|
51
|
-
const issues = result.issues || [];
|
|
52
|
-
|
|
63
|
+
const result = await requireClient().searchIssuesAll(args.jql, args.fields, getMaxApiResults());
|
|
64
|
+
// Flatten issues to match jira_issue schema
|
|
65
|
+
const issues = (result.issues || []).map(formatIssueMetadata);
|
|
66
|
+
return formatSuccessJson(issues, {
|
|
53
67
|
resourceType: "jira_search",
|
|
54
68
|
title: `JQL: ${args.jql.substring(0, 100)}${args.jql.length > 100 ? "..." : ""}`,
|
|
69
|
+
schemaType: "jira_issue",
|
|
55
70
|
jql: args.jql,
|
|
56
71
|
totalIssues: result.total,
|
|
57
72
|
maxResults: result.maxResults,
|
|
58
|
-
};
|
|
59
|
-
return formatSuccessJson(issues, metadata);
|
|
73
|
+
});
|
|
60
74
|
}
|
|
61
75
|
catch (error) {
|
|
76
|
+
// Add JQL help hint for search errors
|
|
77
|
+
if (isApiError(error) && (error.statusCode === 400 || error.message?.toLowerCase().includes('jql'))) {
|
|
78
|
+
return formatError({
|
|
79
|
+
...error,
|
|
80
|
+
details: {
|
|
81
|
+
...(error.details || {}),
|
|
82
|
+
tip: 'Call help(topic="jql") for JQL syntax and localization guide',
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
62
86
|
return formatError(isApiError(error) ? error : new Error(String(error)));
|
|
63
87
|
}
|
|
64
88
|
},
|
|
@@ -67,15 +91,19 @@ Example: jira_search_issues(jql="project=PROJ AND status='Open' ORDER BY priorit
|
|
|
67
91
|
description: `Get detailed information about a specific Jira issue.
|
|
68
92
|
|
|
69
93
|
Use for viewing issue details, checking status before update, or getting comments.
|
|
94
|
+
For MULTIPLE issues in a single buffer, use jira_get_issues instead.
|
|
70
95
|
|
|
71
96
|
REQUIRES: Issue key (e.g., "PROJ-123")
|
|
72
97
|
RETURNS: bufferId with issue data including fields, comments, attachments
|
|
73
98
|
NEXT: jira_update_issue (to modify), jira_transition_issue (to change status), jira_add_comment
|
|
74
99
|
|
|
100
|
+
TIME TRACKING: The "timespent" field is often NULL. For accurate time data,
|
|
101
|
+
use jira_get_issue_worklogs or jira_get_total_worklogs instead.
|
|
102
|
+
|
|
75
103
|
Example: jira_get_issue(issueKey="PROJ-123")`,
|
|
76
104
|
inputSchema: z.object({
|
|
77
105
|
issueKey: z.string().describe('Issue key (e.g., "PROJ-123")'),
|
|
78
|
-
fields:
|
|
106
|
+
fields: stringOrArray.optional().describe("Specific fields to return (string or array)"),
|
|
79
107
|
expand: z
|
|
80
108
|
.array(z.string())
|
|
81
109
|
.optional()
|
|
@@ -87,15 +115,64 @@ Example: jira_get_issue(issueKey="PROJ-123")`,
|
|
|
87
115
|
// Handle case where fields might be missing or undefined
|
|
88
116
|
const summary = result?.fields?.summary || args.issueKey;
|
|
89
117
|
const issueKey = result?.key || args.issueKey;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
data: result,
|
|
93
|
-
}, {
|
|
118
|
+
const issueMetadata = formatIssueMetadata(result);
|
|
119
|
+
const metadata = {
|
|
94
120
|
resourceType: "jira_issue",
|
|
121
|
+
schemaType: "jira_issue",
|
|
95
122
|
title: `${issueKey}: ${summary}`,
|
|
96
123
|
resourceId: issueKey,
|
|
97
124
|
projectKey: issueKey.split("-")[0],
|
|
98
125
|
issueId: result?.id,
|
|
126
|
+
};
|
|
127
|
+
// Check if time fields were requested but are null - add guidance
|
|
128
|
+
const timeFields = ["timespent", "timeoriginalestimate", "aggregatetimespent"];
|
|
129
|
+
const requestedTimeFields = args.fields?.filter(f => timeFields.includes(f.toLowerCase())) || [];
|
|
130
|
+
if (requestedTimeFields.length > 0) {
|
|
131
|
+
const hasAnyTimeData = requestedTimeFields.some(f => issueMetadata[f] != null && issueMetadata[f] !== 0);
|
|
132
|
+
if (!hasAnyTimeData) {
|
|
133
|
+
metadata.timeTrackingHint = `Time fields are null for ${issueKey}. Use jira_get_issue_worklogs("${issueKey}") or jira_get_total_worklogs("${issueKey}") for accurate time data.`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return formatSuccessJson({
|
|
137
|
+
...issueMetadata,
|
|
138
|
+
data: result,
|
|
139
|
+
}, metadata);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
return formatError(isApiError(error) ? error : new Error(String(error)));
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
jira_get_issues: {
|
|
147
|
+
description: `Get multiple Jira issues at once in a single buffer.
|
|
148
|
+
|
|
149
|
+
Batch alternative to calling jira_get_issue multiple times. Returns a flat array
|
|
150
|
+
with the same schema as jira_search_issues (jira_issue), ready for buffer_transform.
|
|
151
|
+
|
|
152
|
+
REQUIRES: issueKeys (single key or array of keys)
|
|
153
|
+
RETURNS: bufferId with flat issues array (same format as jira_search_issues)
|
|
154
|
+
NEXT: buffer_transform (for tables/reports), buffer_get_items (for analysis)
|
|
155
|
+
|
|
156
|
+
Example: jira_get_issues(issueKeys="PROJ-123")
|
|
157
|
+
Example: jira_get_issues(issueKeys=["PROJ-123","PROJ-456","PROJ-789"])`,
|
|
158
|
+
inputSchema: z.object({
|
|
159
|
+
issueKeys: stringOrArray.describe('Issue key(s) - single key or array (e.g., "PROJ-123" or ["PROJ-123","PROJ-456"])'),
|
|
160
|
+
fields: stringOrArray.optional().describe("Specific fields to return (string or array)"),
|
|
161
|
+
}),
|
|
162
|
+
handler: async (args) => {
|
|
163
|
+
try {
|
|
164
|
+
const keys = args.issueKeys;
|
|
165
|
+
if (keys.length === 0) {
|
|
166
|
+
return formatError(new Error("issueKeys must contain at least one key"));
|
|
167
|
+
}
|
|
168
|
+
const jql = `key in (${keys.join(",")})`;
|
|
169
|
+
const result = await requireClient().searchIssuesAll(jql, args.fields, keys.length);
|
|
170
|
+
const issues = (result.issues || []).map(formatIssueMetadata);
|
|
171
|
+
return formatSuccessJson(issues, {
|
|
172
|
+
resourceType: "jira_issues",
|
|
173
|
+
title: keys.length === 1 ? keys[0] : `${keys.length} issues`,
|
|
174
|
+
schemaType: "jira_issue",
|
|
175
|
+
issueKeys: keys,
|
|
99
176
|
});
|
|
100
177
|
}
|
|
101
178
|
catch (error) {
|
|
@@ -166,7 +243,7 @@ Example: jira_update_issue(issueKey="PROJ-123", fields={summary: "New title", pr
|
|
|
166
243
|
try {
|
|
167
244
|
await requireClient().updateIssue(args.issueKey, args.fields, args.notifyUsers);
|
|
168
245
|
// Invalidate any cached buffers for this issue to prevent stale data
|
|
169
|
-
|
|
246
|
+
getSessionBuffer().invalidateByMetadata({
|
|
170
247
|
resourceType: "jira_issue",
|
|
171
248
|
resourceId: args.issueKey,
|
|
172
249
|
});
|
|
@@ -183,7 +260,7 @@ Example: jira_update_issue(issueKey="PROJ-123", fields={summary: "New title", pr
|
|
|
183
260
|
try {
|
|
184
261
|
await requireClient().getIssue(args.issueKey);
|
|
185
262
|
await requireClient().updateIssue(args.issueKey, args.fields, args.notifyUsers);
|
|
186
|
-
|
|
263
|
+
getSessionBuffer().invalidateByMetadata({
|
|
187
264
|
resourceType: "jira_issue",
|
|
188
265
|
resourceId: args.issueKey,
|
|
189
266
|
});
|
|
@@ -237,7 +314,7 @@ Example: jira_transition_issue(issueKey="PROJ-123", transitionName="Done", comme
|
|
|
237
314
|
try {
|
|
238
315
|
await requireClient().transitionIssue(args.issueKey, args.transitionName, args.comment, args.fields);
|
|
239
316
|
// Invalidate any cached buffers for this issue
|
|
240
|
-
|
|
317
|
+
getSessionBuffer().invalidateByMetadata({
|
|
241
318
|
resourceType: "jira_issue",
|
|
242
319
|
resourceId: args.issueKey,
|
|
243
320
|
});
|
|
@@ -276,7 +353,7 @@ Example: jira_add_comment(issueKey="PROJ-123", comment="Analysis complete. Ready
|
|
|
276
353
|
try {
|
|
277
354
|
const result = await requireClient().addComment(args.issueKey, args.comment, args.visibility);
|
|
278
355
|
// Invalidate any cached buffers for this issue (comments are part of issue data)
|
|
279
|
-
|
|
356
|
+
getSessionBuffer().invalidateByMetadata({
|
|
280
357
|
resourceType: "jira_issue",
|
|
281
358
|
resourceId: args.issueKey,
|
|
282
359
|
});
|
|
@@ -307,8 +384,11 @@ Example: jira_get_issue_comments(issueKey="PROJ-123")`,
|
|
|
307
384
|
handler: async (args) => {
|
|
308
385
|
try {
|
|
309
386
|
const result = await requireClient().getComments(args.issueKey, args.orderBy);
|
|
310
|
-
|
|
387
|
+
// Flatten comments to match jira_comment schema
|
|
388
|
+
const comments = (result.comments || result || []).map((c) => formatCommentMetadata(c));
|
|
389
|
+
return formatSuccessJson(comments, {
|
|
311
390
|
resourceType: "jira_comments",
|
|
391
|
+
schemaType: "jira_comment",
|
|
312
392
|
title: `${args.issueKey} comments`,
|
|
313
393
|
issueKey: args.issueKey,
|
|
314
394
|
});
|
|
@@ -330,13 +410,16 @@ NEXT: jira_get_project (for details), jira_search_issues (to query project)
|
|
|
330
410
|
Example: jira_list_projects()`,
|
|
331
411
|
inputSchema: z.object({
|
|
332
412
|
recent: z.boolean().optional().describe("Only return recent projects"),
|
|
333
|
-
expand:
|
|
413
|
+
expand: stringOrArray.optional().describe("Additional data to expand (string or array)"),
|
|
334
414
|
}),
|
|
335
415
|
handler: async (args) => {
|
|
336
416
|
try {
|
|
337
417
|
const result = await requireClient().listProjects(args.recent, args.expand);
|
|
338
|
-
|
|
418
|
+
// Flatten projects to match jira_project schema
|
|
419
|
+
const projects = (result || []).map(formatProjectMetadata);
|
|
420
|
+
return formatSuccessJson(projects, {
|
|
339
421
|
resourceType: "jira_projects",
|
|
422
|
+
schemaType: "jira_project",
|
|
340
423
|
title: args.recent ? "Recent Projects" : "All Projects",
|
|
341
424
|
});
|
|
342
425
|
}
|
|
@@ -357,13 +440,18 @@ NEXT: jira_search_issues (to query), jira_create_issue (to create in project)
|
|
|
357
440
|
Example: jira_get_project(projectKey="PROJ")`,
|
|
358
441
|
inputSchema: z.object({
|
|
359
442
|
projectKey: z.string().describe("Project key"),
|
|
360
|
-
expand:
|
|
443
|
+
expand: stringOrArray.optional().describe("Additional data to expand (string or array)"),
|
|
361
444
|
}),
|
|
362
445
|
handler: async (args) => {
|
|
363
446
|
try {
|
|
364
447
|
const result = await requireClient().getProject(args.projectKey, args.expand);
|
|
365
|
-
|
|
448
|
+
// Flatten project to match jira_project schema, keep raw data for extra fields
|
|
449
|
+
return formatSuccessJson({
|
|
450
|
+
...formatProjectMetadata(result),
|
|
451
|
+
data: result,
|
|
452
|
+
}, {
|
|
366
453
|
resourceType: "jira_project",
|
|
454
|
+
schemaType: "jira_project",
|
|
367
455
|
title: result.name || args.projectKey,
|
|
368
456
|
projectKey: args.projectKey,
|
|
369
457
|
});
|
|
@@ -405,26 +493,24 @@ Example workflow:
|
|
|
405
493
|
},
|
|
406
494
|
},
|
|
407
495
|
jira_get_fields: {
|
|
408
|
-
description: `Get
|
|
496
|
+
description: `Get Jira fields (system and custom). Requires a search term or "all".
|
|
409
497
|
|
|
410
498
|
Use this to discover:
|
|
411
499
|
- Field names in user's language (e.g., "Epic Link" vs "Lien d'épopée")
|
|
412
500
|
- Custom field IDs for JQL queries (e.g., cf[10014])
|
|
413
501
|
- Field schema types for identification
|
|
414
502
|
|
|
415
|
-
RETURNS: List of fields with id, name, clauseNames, and
|
|
503
|
+
RETURNS: List of fields with id, name, clauseNames, and schemaType
|
|
416
504
|
|
|
417
505
|
Example workflow for Epic Link queries in any language:
|
|
418
|
-
1. Call jira_get_fields()
|
|
419
|
-
2. Find field with
|
|
420
|
-
3. Use
|
|
421
|
-
- French: "Lien d'épopée" = PROJ-123
|
|
422
|
-
- Language-independent: cf[10014] = PROJ-123`,
|
|
506
|
+
1. Call jira_get_fields(search="epic") — searches name, id, clauseNames, AND schemaType
|
|
507
|
+
2. Find field with schemaType "com.pyxis.greenhopper.jira:gh-epic-link"
|
|
508
|
+
3. Use cf[ID] from clauseNames in JQL: cf[10014] IS EMPTY`,
|
|
423
509
|
inputSchema: z.object({
|
|
510
|
+
search: z.string()
|
|
511
|
+
.describe('Required. "all" to return every field, or a search term to filter by name, id, clauseNames, or schemaType (case-insensitive). Example: "epic", "priority", "all"'),
|
|
424
512
|
filter: z.enum(["all", "custom", "system", "navigable"]).optional()
|
|
425
|
-
.describe("Filter
|
|
426
|
-
search: z.string().optional()
|
|
427
|
-
.describe("Search in field name or id (case-insensitive)"),
|
|
513
|
+
.describe("Filter by category: all (default), custom, system, or navigable"),
|
|
428
514
|
}),
|
|
429
515
|
handler: async (args) => {
|
|
430
516
|
try {
|
|
@@ -439,12 +525,16 @@ Example workflow for Epic Link queries in any language:
|
|
|
439
525
|
else if (args.filter === "navigable") {
|
|
440
526
|
fields = fields.filter(f => f.navigable);
|
|
441
527
|
}
|
|
442
|
-
// Apply search
|
|
443
|
-
if (args.search) {
|
|
528
|
+
// Apply search (skip if "all")
|
|
529
|
+
if (args.search && args.search.toLowerCase() !== "all") {
|
|
444
530
|
const searchLower = args.search.toLowerCase();
|
|
445
|
-
fields = fields.filter(f =>
|
|
446
|
-
f.
|
|
447
|
-
f.
|
|
531
|
+
fields = fields.filter(f => {
|
|
532
|
+
const schema = (f.schema?.custom || f.schema?.system || f.schema?.type || "").toLowerCase();
|
|
533
|
+
return f.id.toLowerCase().includes(searchLower) ||
|
|
534
|
+
f.name.toLowerCase().includes(searchLower) ||
|
|
535
|
+
f.clauseNames?.some(c => c.toLowerCase().includes(searchLower)) ||
|
|
536
|
+
schema.includes(searchLower);
|
|
537
|
+
});
|
|
448
538
|
}
|
|
449
539
|
// Format for readability
|
|
450
540
|
const formatted = fields.map(f => ({
|
|
@@ -464,6 +554,38 @@ Example workflow for Epic Link queries in any language:
|
|
|
464
554
|
}
|
|
465
555
|
},
|
|
466
556
|
},
|
|
557
|
+
jira_search_users: {
|
|
558
|
+
description: `Search for Jira users by name, username, or email.
|
|
559
|
+
|
|
560
|
+
Use this BEFORE JQL queries when you need a person's username but only know their name.
|
|
561
|
+
Returns matching users with username/key for use in JQL (assignee, reporter, etc.).
|
|
562
|
+
|
|
563
|
+
REQUIRES: query (name to search for)
|
|
564
|
+
RETURNS: List of matching users with key, name, displayName, emailAddress
|
|
565
|
+
|
|
566
|
+
Example workflow:
|
|
567
|
+
1. jira_search_users(query="Fabien") → finds users matching "Fabien"
|
|
568
|
+
2. Use username in JQL: assignee = "fthuillier"
|
|
569
|
+
|
|
570
|
+
Example: jira_search_users(query="Fabien")`,
|
|
571
|
+
inputSchema: z.object({
|
|
572
|
+
query: z.string().describe("Search text (display name, username, or email)"),
|
|
573
|
+
maxResults: z.number().optional().describe("Maximum results (default: 20)"),
|
|
574
|
+
}),
|
|
575
|
+
handler: async (args) => {
|
|
576
|
+
try {
|
|
577
|
+
const users = await requireClient().searchUsers(args.query, args.maxResults);
|
|
578
|
+
return formatSuccess({
|
|
579
|
+
count: users.length,
|
|
580
|
+
query: args.query,
|
|
581
|
+
users,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
catch (error) {
|
|
585
|
+
return formatError(isApiError(error) ? error : new Error(String(error)));
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
},
|
|
467
589
|
jira_get_transitions: {
|
|
468
590
|
description: `Get available transitions for an issue.
|
|
469
591
|
|
|
@@ -516,6 +638,38 @@ Example: jira_link_issues(issueKey="PROJ-123", linkedIssueKey="PROJ-456", linkTy
|
|
|
516
638
|
}
|
|
517
639
|
},
|
|
518
640
|
},
|
|
641
|
+
jira_list_boards: {
|
|
642
|
+
description: `List all Jira Agile boards. Entry point for discovering boards.
|
|
643
|
+
|
|
644
|
+
View all Scrum and Kanban boards with optional filtering by project, type, or name.
|
|
645
|
+
|
|
646
|
+
RETURNS: bufferId with board list (id, name, type, projectKey)
|
|
647
|
+
NEXT: jira_get_sprints (for Scrum boards), jira_search_issues (with board filter)
|
|
648
|
+
|
|
649
|
+
Example: jira_list_boards()
|
|
650
|
+
Example: jira_list_boards(projectKeyOrId="PROJ")
|
|
651
|
+
Example: jira_list_boards(type="scrum")`,
|
|
652
|
+
inputSchema: z.object({
|
|
653
|
+
projectKeyOrId: z.string().optional().describe("Filter by project key or ID"),
|
|
654
|
+
type: z.enum(["scrum", "kanban"]).optional().describe("Filter by board type"),
|
|
655
|
+
name: z.string().optional().describe("Filter by board name (contains)"),
|
|
656
|
+
}),
|
|
657
|
+
handler: async (args) => {
|
|
658
|
+
try {
|
|
659
|
+
const result = await requireClient().listBoards(args);
|
|
660
|
+
const boards = (result.values || []).map(formatBoardMetadata);
|
|
661
|
+
return formatSuccessJson(boards, {
|
|
662
|
+
resourceType: "jira_boards",
|
|
663
|
+
schemaType: "jira_board",
|
|
664
|
+
title: "Jira Boards",
|
|
665
|
+
total: result.total,
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
catch (error) {
|
|
669
|
+
return formatError(isApiError(error) ? error : new Error(String(error)));
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
},
|
|
519
673
|
jira_get_board: {
|
|
520
674
|
description: `Get information about an Agile board (Scrum or Kanban).
|
|
521
675
|
|
|
@@ -532,8 +686,13 @@ Example: jira_get_board(boardId=123)`,
|
|
|
532
686
|
handler: async (args) => {
|
|
533
687
|
try {
|
|
534
688
|
const result = await requireClient().getBoard(args.boardId);
|
|
535
|
-
|
|
689
|
+
// Flatten board to match jira_board schema, keep raw data for extra fields
|
|
690
|
+
return formatSuccessJson({
|
|
691
|
+
...formatBoardMetadata(result),
|
|
692
|
+
data: result,
|
|
693
|
+
}, {
|
|
536
694
|
resourceType: "jira_board",
|
|
695
|
+
schemaType: "jira_board",
|
|
537
696
|
title: result.name || `Board #${args.boardId}`,
|
|
538
697
|
boardId: args.boardId,
|
|
539
698
|
});
|
|
@@ -563,8 +722,11 @@ Example: jira_get_sprints(boardId=123, state="active")`,
|
|
|
563
722
|
handler: async (args) => {
|
|
564
723
|
try {
|
|
565
724
|
const result = await requireClient().getSprints(args.boardId, args.state);
|
|
566
|
-
|
|
725
|
+
// Flatten sprints to match jira_sprint schema
|
|
726
|
+
const sprints = (result.values || result || []).map(formatSprintMetadata);
|
|
727
|
+
return formatSuccessJson(sprints, {
|
|
567
728
|
resourceType: "jira_sprints",
|
|
729
|
+
schemaType: "jira_sprint",
|
|
568
730
|
title: `Board #${args.boardId} sprints${args.state ? ` (${args.state})` : ""}`,
|
|
569
731
|
boardId: args.boardId,
|
|
570
732
|
});
|
|
@@ -581,7 +743,7 @@ Retrieve sprint backlog for analysis or reporting.
|
|
|
581
743
|
|
|
582
744
|
REQUIRES: sprintId (from jira_get_sprints)
|
|
583
745
|
RETURNS: bufferId with issues array
|
|
584
|
-
NEXT:
|
|
746
|
+
NEXT: buffer_transform (for sprint report), buffer_get_items (for analysis)
|
|
585
747
|
|
|
586
748
|
Example: jira_get_sprint_issues(sprintId=456)`,
|
|
587
749
|
inputSchema: z.object({
|
|
@@ -590,8 +752,11 @@ Example: jira_get_sprint_issues(sprintId=456)`,
|
|
|
590
752
|
handler: async (args) => {
|
|
591
753
|
try {
|
|
592
754
|
const result = await requireClient().getSprintIssues(args.sprintId);
|
|
593
|
-
|
|
755
|
+
// Flatten issues to match jira_issue schema
|
|
756
|
+
const issues = (result.issues || result || []).map(formatIssueMetadata);
|
|
757
|
+
return formatSuccessJson(issues, {
|
|
594
758
|
resourceType: "jira_sprint_issues",
|
|
759
|
+
schemaType: "jira_issue",
|
|
595
760
|
title: `Sprint #${args.sprintId} issues`,
|
|
596
761
|
sprintId: args.sprintId,
|
|
597
762
|
});
|
|
@@ -640,16 +805,19 @@ Example: jira_get_issue_worklogs(issueKey="PROJ-123")`,
|
|
|
640
805
|
handler: async (args) => {
|
|
641
806
|
try {
|
|
642
807
|
const result = await requireClient().getIssueWorklogs(args.issueKey);
|
|
808
|
+
// Flatten worklogs to match jira_worklog schema
|
|
809
|
+
const worklogs = (result.worklogs || []).map((w) => formatWorklogMetadata(w, args.issueKey));
|
|
643
810
|
// Calculate total time spent
|
|
644
|
-
const totalSeconds = result.worklogs.reduce((sum, w) => sum + (w.timeSpentSeconds || 0), 0);
|
|
811
|
+
const totalSeconds = (result.worklogs || []).reduce((sum, w) => sum + (w.timeSpentSeconds || 0), 0);
|
|
645
812
|
return formatSuccessJson({
|
|
646
813
|
issueKey: args.issueKey,
|
|
647
814
|
worklogCount: result.total,
|
|
648
815
|
totalTimeSpentSeconds: totalSeconds,
|
|
649
816
|
totalTimeSpent: formatTimeSpent(totalSeconds),
|
|
650
|
-
worklogs
|
|
817
|
+
worklogs,
|
|
651
818
|
}, {
|
|
652
819
|
resourceType: "jira_worklogs",
|
|
820
|
+
schemaType: "jira_worklog",
|
|
653
821
|
title: `${args.issueKey} worklogs`,
|
|
654
822
|
issueKey: args.issueKey,
|
|
655
823
|
});
|
|
@@ -666,7 +834,7 @@ Aggregates time from subtasks and Epic children. Ideal for Epic time reports.
|
|
|
666
834
|
|
|
667
835
|
REQUIRES: issueKey (works best with Epics)
|
|
668
836
|
RETURNS: bufferId with breakdown by issue, totalTimeSpent, issueCount
|
|
669
|
-
NEXT:
|
|
837
|
+
NEXT: buffer_transform (for time report), workload_sum (for calculations)
|
|
670
838
|
|
|
671
839
|
Example: jira_get_total_worklogs(issueKey="PROJ-100") - returns all time on Epic + stories + tasks`,
|
|
672
840
|
inputSchema: z.object({
|
|
@@ -677,6 +845,7 @@ Example: jira_get_total_worklogs(issueKey="PROJ-100") - returns all time on Epic
|
|
|
677
845
|
const result = await requireClient().getTotalWorklogs(args.issueKey);
|
|
678
846
|
return formatSuccessJson(result, {
|
|
679
847
|
resourceType: "jira_worklogs_total",
|
|
848
|
+
schemaType: "jira_worklog_total",
|
|
680
849
|
title: `${args.issueKey} total worklogs (recursive)`,
|
|
681
850
|
issueKey: args.issueKey,
|
|
682
851
|
});
|
|
@@ -696,7 +865,7 @@ Automatically detects Epic Link and Parent Link fields. Handles:
|
|
|
696
865
|
|
|
697
866
|
REQUIRES: parentKey (Epic, Initiative, or any parent issue)
|
|
698
867
|
RETURNS: bufferId with child issues (same format as jira_search_issues)
|
|
699
|
-
NEXT:
|
|
868
|
+
NEXT: buffer_transform (for reports), buffer_get_items (for analysis), jira_analyze_epic (for deep analysis)
|
|
700
869
|
|
|
701
870
|
Example: jira_list_epic_children(parentKey="PROJ-100")
|
|
702
871
|
Example: jira_list_epic_children(parentKey="PROJ-100", status="open", type="Story")`,
|
|
@@ -705,89 +874,83 @@ Example: jira_list_epic_children(parentKey="PROJ-100", status="open", type="Stor
|
|
|
705
874
|
type: z.string().optional().describe("Filter by issue type (Story, Task, Bug, etc.)"),
|
|
706
875
|
status: z.enum(["open", "closed", "all"]).optional().describe("Filter by status: open (unresolved), closed (resolved), all (default)"),
|
|
707
876
|
recursive: z.boolean().optional().describe("Include nested children recursively (default: false for direct children only)"),
|
|
708
|
-
fields:
|
|
877
|
+
fields: stringOrArray.optional().describe("Specific fields to return (string or array)"),
|
|
709
878
|
}),
|
|
710
879
|
handler: async (args) => {
|
|
711
880
|
try {
|
|
712
881
|
const parentKey = args.parentKey;
|
|
713
882
|
const recursive = args.recursive ?? false;
|
|
714
|
-
//
|
|
715
|
-
const jqlClauses = [];
|
|
716
|
-
// 1. Subtasks
|
|
717
|
-
jqlClauses.push(`parent = ${parentKey}`);
|
|
718
|
-
// 2. Epic Link children
|
|
883
|
+
// Get field IDs once (cached in client)
|
|
719
884
|
const epicLinkFieldId = await requireClient().getEpicLinkFieldId();
|
|
720
|
-
if (epicLinkFieldId) {
|
|
721
|
-
jqlClauses.push(`cf[${epicLinkFieldId.replace("customfield_", "")}] = ${parentKey}`);
|
|
722
|
-
}
|
|
723
|
-
// 3. Parent Link children (Initiative → Epic)
|
|
724
885
|
const parentLinkFieldId = await requireClient().getParentLinkFieldId();
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
}
|
|
728
|
-
// 4. Jira Cloud parentEpic
|
|
729
|
-
jqlClauses.push(`parentEpic = ${parentKey}`);
|
|
730
|
-
// Build combined JQL
|
|
731
|
-
let jql = `(${jqlClauses.join(" OR ")})`;
|
|
732
|
-
// Apply filters
|
|
733
|
-
if (args.type) {
|
|
734
|
-
jql += ` AND issuetype = "${args.type}"`;
|
|
735
|
-
}
|
|
886
|
+
// Build filters (language-independent using statusCategory)
|
|
887
|
+
let statusFilter = "";
|
|
736
888
|
if (args.status === "open") {
|
|
737
|
-
|
|
889
|
+
statusFilter = " AND statusCategory != Done";
|
|
738
890
|
}
|
|
739
891
|
else if (args.status === "closed") {
|
|
740
|
-
|
|
892
|
+
statusFilter = " AND statusCategory = Done";
|
|
741
893
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
//
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
const
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
for (const child of newChildren) {
|
|
774
|
-
visited.add(child.key);
|
|
775
|
-
allChildren.push(child);
|
|
776
|
-
}
|
|
777
|
-
// Recurse into new children
|
|
778
|
-
if (newChildren.length > 0) {
|
|
779
|
-
await collectRecursive(newChildren.map(i => i.key));
|
|
894
|
+
let typeFilter = "";
|
|
895
|
+
if (args.type) {
|
|
896
|
+
typeFilter = ` AND issuetype = "${args.type}"`;
|
|
897
|
+
}
|
|
898
|
+
// Build JQL patterns for a given parent key
|
|
899
|
+
// Each pattern is tried independently with try-catch
|
|
900
|
+
const buildPatterns = (pk) => {
|
|
901
|
+
const patterns = [];
|
|
902
|
+
// 1. Subtasks (language-independent)
|
|
903
|
+
patterns.push(`parent = ${pk}${typeFilter}${statusFilter} ORDER BY key ASC`);
|
|
904
|
+
// 2. Epic Link children (language-independent via cf[ID])
|
|
905
|
+
if (epicLinkFieldId) {
|
|
906
|
+
patterns.push(`cf[${epicLinkFieldId.replace("customfield_", "")}] = ${pk}${typeFilter}${statusFilter} ORDER BY key ASC`);
|
|
907
|
+
}
|
|
908
|
+
// 3. Parent Link children - Initiative → Epic (language-independent via cf[ID])
|
|
909
|
+
if (parentLinkFieldId) {
|
|
910
|
+
patterns.push(`cf[${parentLinkFieldId.replace("customfield_", "")}] = ${pk}${typeFilter}${statusFilter} ORDER BY key ASC`);
|
|
911
|
+
}
|
|
912
|
+
return patterns;
|
|
913
|
+
};
|
|
914
|
+
const visited = new Set([parentKey]);
|
|
915
|
+
const allChildren = [];
|
|
916
|
+
// Collect children for a parent using try-catch-per-pattern
|
|
917
|
+
const collectChildren = async (pk, maxResults) => {
|
|
918
|
+
for (const jql of buildPatterns(pk)) {
|
|
919
|
+
try {
|
|
920
|
+
const result = await requireClient().searchIssuesAll(jql, args.fields, maxResults);
|
|
921
|
+
for (const issue of result.issues) {
|
|
922
|
+
if (!visited.has(issue.key)) {
|
|
923
|
+
visited.add(issue.key);
|
|
924
|
+
allChildren.push(issue);
|
|
780
925
|
}
|
|
781
926
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
927
|
+
// Don't break - multiple patterns may find different children
|
|
928
|
+
}
|
|
929
|
+
catch {
|
|
930
|
+
// Pattern not supported, try next
|
|
785
931
|
}
|
|
786
|
-
}
|
|
787
|
-
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
// Fetch direct children
|
|
935
|
+
const maxResults = getMaxApiResults();
|
|
936
|
+
await collectChildren(parentKey, maxResults);
|
|
937
|
+
const directChildCount = allChildren.length;
|
|
938
|
+
// If recursive, fetch children of children
|
|
939
|
+
if (recursive && directChildCount > 0) {
|
|
940
|
+
let startIndex = 0;
|
|
941
|
+
while (startIndex < allChildren.length) {
|
|
942
|
+
const currentBatchEnd = allChildren.length;
|
|
943
|
+
for (let i = startIndex; i < currentBatchEnd; i++) {
|
|
944
|
+
await collectChildren(allChildren[i].key, maxResults);
|
|
945
|
+
}
|
|
946
|
+
startIndex = currentBatchEnd;
|
|
947
|
+
}
|
|
788
948
|
}
|
|
949
|
+
// Flatten issues to match jira_issue schema
|
|
950
|
+
const flattenedChildren = allChildren.map(formatIssueMetadata);
|
|
789
951
|
const metadata = {
|
|
790
952
|
resourceType: "jira_epic_children",
|
|
953
|
+
schemaType: "jira_issue",
|
|
791
954
|
title: `Children of ${parentKey}`,
|
|
792
955
|
parentKey,
|
|
793
956
|
recursive,
|
|
@@ -796,7 +959,7 @@ Example: jira_list_epic_children(parentKey="PROJ-100", status="open", type="Stor
|
|
|
796
959
|
status: args.status || "all",
|
|
797
960
|
},
|
|
798
961
|
};
|
|
799
|
-
return formatSuccessJson(
|
|
962
|
+
return formatSuccessJson(flattenedChildren, metadata);
|
|
800
963
|
}
|
|
801
964
|
catch (error) {
|
|
802
965
|
return formatError(isApiError(error) ? error : new Error(String(error)));
|