@muzzamil-khan/redmine-agent-mcp 1.0.0 → 1.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 (119) hide show
  1. package/dist/config/env.d.ts +2 -0
  2. package/dist/config/env.d.ts.map +1 -1
  3. package/dist/config/env.js +8 -0
  4. package/dist/config/env.js.map +1 -1
  5. package/dist/mcp/server.js +719 -147
  6. package/dist/mcp/server.js.map +1 -1
  7. package/dist/services/assigneeDirectory.d.ts +34 -0
  8. package/dist/services/assigneeDirectory.d.ts.map +1 -0
  9. package/dist/services/assigneeDirectory.js +138 -0
  10. package/dist/services/assigneeDirectory.js.map +1 -0
  11. package/dist/services/googleSheets.d.ts +32 -0
  12. package/dist/services/googleSheets.d.ts.map +1 -0
  13. package/dist/services/googleSheets.js +109 -0
  14. package/dist/services/googleSheets.js.map +1 -0
  15. package/dist/services/redmine.d.ts +72 -0
  16. package/dist/services/redmine.d.ts.map +1 -1
  17. package/dist/services/redmine.js +141 -0
  18. package/dist/services/redmine.js.map +1 -1
  19. package/dist/services/redmineConfig.d.ts +90 -0
  20. package/dist/services/redmineConfig.d.ts.map +1 -0
  21. package/dist/services/redmineConfig.js +300 -0
  22. package/dist/services/redmineConfig.js.map +1 -0
  23. package/dist/spreadsheet/batchColumnMapping.d.ts +93 -0
  24. package/dist/spreadsheet/batchColumnMapping.d.ts.map +1 -0
  25. package/dist/spreadsheet/batchColumnMapping.js +666 -0
  26. package/dist/spreadsheet/batchColumnMapping.js.map +1 -0
  27. package/dist/spreadsheet/batchCreationTypes.d.ts +61 -0
  28. package/dist/spreadsheet/batchCreationTypes.d.ts.map +1 -0
  29. package/dist/spreadsheet/batchCreationTypes.js +18 -0
  30. package/dist/spreadsheet/batchCreationTypes.js.map +1 -0
  31. package/dist/spreadsheet/batchIssueConfig.d.ts +26 -0
  32. package/dist/spreadsheet/batchIssueConfig.d.ts.map +1 -0
  33. package/dist/spreadsheet/batchIssueConfig.js +106 -0
  34. package/dist/spreadsheet/batchIssueConfig.js.map +1 -0
  35. package/dist/spreadsheet/batchPreviewFormat.d.ts +41 -0
  36. package/dist/spreadsheet/batchPreviewFormat.d.ts.map +1 -0
  37. package/dist/spreadsheet/batchPreviewFormat.js +194 -0
  38. package/dist/spreadsheet/batchPreviewFormat.js.map +1 -0
  39. package/dist/spreadsheet/batchSheetHeuristics.d.ts +13 -0
  40. package/dist/spreadsheet/batchSheetHeuristics.d.ts.map +1 -0
  41. package/dist/spreadsheet/batchSheetHeuristics.js +30 -0
  42. package/dist/spreadsheet/batchSheetHeuristics.js.map +1 -0
  43. package/dist/spreadsheet/bulkUpdateExtract.d.ts +14 -0
  44. package/dist/spreadsheet/bulkUpdateExtract.d.ts.map +1 -0
  45. package/dist/spreadsheet/bulkUpdateExtract.js +83 -0
  46. package/dist/spreadsheet/bulkUpdateExtract.js.map +1 -0
  47. package/dist/spreadsheet/csvUtils.d.ts +14 -0
  48. package/dist/spreadsheet/csvUtils.d.ts.map +1 -0
  49. package/dist/spreadsheet/csvUtils.js +113 -0
  50. package/dist/spreadsheet/csvUtils.js.map +1 -0
  51. package/dist/spreadsheet/extractRawBatchSheet.d.ts +14 -0
  52. package/dist/spreadsheet/extractRawBatchSheet.d.ts.map +1 -0
  53. package/dist/spreadsheet/extractRawBatchSheet.js +33 -0
  54. package/dist/spreadsheet/extractRawBatchSheet.js.map +1 -0
  55. package/dist/spreadsheet/featureRow.d.ts +15 -0
  56. package/dist/spreadsheet/featureRow.d.ts.map +1 -0
  57. package/dist/spreadsheet/featureRow.js +119 -0
  58. package/dist/spreadsheet/featureRow.js.map +1 -0
  59. package/dist/spreadsheet/parseBatchCreationCsv.d.ts +8 -0
  60. package/dist/spreadsheet/parseBatchCreationCsv.d.ts.map +1 -0
  61. package/dist/spreadsheet/parseBatchCreationCsv.js +22 -0
  62. package/dist/spreadsheet/parseBatchCreationCsv.js.map +1 -0
  63. package/dist/spreadsheet/parseBatchCreationCsvV1.d.ts +3 -0
  64. package/dist/spreadsheet/parseBatchCreationCsvV1.d.ts.map +1 -0
  65. package/dist/spreadsheet/parseBatchCreationCsvV1.js +226 -0
  66. package/dist/spreadsheet/parseBatchCreationCsvV1.js.map +1 -0
  67. package/dist/spreadsheet/parseBatchCreationCsvV2.d.ts +4 -0
  68. package/dist/spreadsheet/parseBatchCreationCsvV2.d.ts.map +1 -0
  69. package/dist/spreadsheet/parseBatchCreationCsvV2.js +180 -0
  70. package/dist/spreadsheet/parseBatchCreationCsvV2.js.map +1 -0
  71. package/dist/spreadsheet/redmineIssueCell.d.ts +38 -0
  72. package/dist/spreadsheet/redmineIssueCell.d.ts.map +1 -0
  73. package/dist/spreadsheet/redmineIssueCell.js +176 -0
  74. package/dist/spreadsheet/redmineIssueCell.js.map +1 -0
  75. package/dist/spreadsheet/sheetColumnMap.d.ts +38 -0
  76. package/dist/spreadsheet/sheetColumnMap.d.ts.map +1 -0
  77. package/dist/spreadsheet/sheetColumnMap.js +75 -0
  78. package/dist/spreadsheet/sheetColumnMap.js.map +1 -0
  79. package/dist/spreadsheet/sheetGridToCsv.d.ts +28 -0
  80. package/dist/spreadsheet/sheetGridToCsv.d.ts.map +1 -0
  81. package/dist/spreadsheet/sheetGridToCsv.js +88 -0
  82. package/dist/spreadsheet/sheetGridToCsv.js.map +1 -0
  83. package/dist/spreadsheet/sheetRedmineFields.d.ts +7 -0
  84. package/dist/spreadsheet/sheetRedmineFields.d.ts.map +1 -0
  85. package/dist/spreadsheet/sheetRedmineFields.js +22 -0
  86. package/dist/spreadsheet/sheetRedmineFields.js.map +1 -0
  87. package/dist/spreadsheet/sheetWriteBack.d.ts +27 -0
  88. package/dist/spreadsheet/sheetWriteBack.d.ts.map +1 -0
  89. package/dist/spreadsheet/sheetWriteBack.js +90 -0
  90. package/dist/spreadsheet/sheetWriteBack.js.map +1 -0
  91. package/dist/workflows/batchCreateIssuesWorkflow.d.ts +69 -0
  92. package/dist/workflows/batchCreateIssuesWorkflow.d.ts.map +1 -0
  93. package/dist/workflows/batchCreateIssuesWorkflow.js +1102 -0
  94. package/dist/workflows/batchCreateIssuesWorkflow.js.map +1 -0
  95. package/dist/workflows/bulkUpdateIssuesWorkflow.d.ts +21 -0
  96. package/dist/workflows/bulkUpdateIssuesWorkflow.d.ts.map +1 -0
  97. package/dist/workflows/bulkUpdateIssuesWorkflow.js +384 -0
  98. package/dist/workflows/bulkUpdateIssuesWorkflow.js.map +1 -0
  99. package/dist/workflows/createIssueWorkflow.d.ts +41 -0
  100. package/dist/workflows/createIssueWorkflow.d.ts.map +1 -0
  101. package/dist/workflows/createIssueWorkflow.js +697 -0
  102. package/dist/workflows/createIssueWorkflow.js.map +1 -0
  103. package/dist/workflows/getAllUsersWorkflow.d.ts +27 -0
  104. package/dist/workflows/getAllUsersWorkflow.d.ts.map +1 -0
  105. package/dist/workflows/getAllUsersWorkflow.js +48 -0
  106. package/dist/workflows/getAllUsersWorkflow.js.map +1 -0
  107. package/dist/workflows/issueCreationHelpers.d.ts +54 -0
  108. package/dist/workflows/issueCreationHelpers.d.ts.map +1 -0
  109. package/dist/workflows/issueCreationHelpers.js +272 -0
  110. package/dist/workflows/issueCreationHelpers.js.map +1 -0
  111. package/dist/workflows/logTimeWorkflow.d.ts +6 -1
  112. package/dist/workflows/logTimeWorkflow.d.ts.map +1 -1
  113. package/dist/workflows/logTimeWorkflow.js +139 -20
  114. package/dist/workflows/logTimeWorkflow.js.map +1 -1
  115. package/dist/workflows/sheetToCsvWorkflow.d.ts +15 -0
  116. package/dist/workflows/sheetToCsvWorkflow.d.ts.map +1 -0
  117. package/dist/workflows/sheetToCsvWorkflow.js +65 -0
  118. package/dist/workflows/sheetToCsvWorkflow.js.map +1 -0
  119. package/package.json +2 -2
@@ -0,0 +1,697 @@
1
+ import axios from "axios";
2
+ import { createIssue, getCustomFields, getProjects, getProjectVersions, } from "../services/redmine.js";
3
+ import { applyPriorityAlias, applyTrackerAlias, configOptionsPayload, enumerationOptions, loadRedmineConfig, resolvePriority, resolveStatus, resolveTracker, } from "../services/redmineConfig.js";
4
+ import { requireRedmineEnv } from "../config/env.js";
5
+ import { lookupIdByName, resolveAssignedToId } from "./issueCreationHelpers.js";
6
+ const OPTIONAL_TEXT_FIELDS = [
7
+ "description",
8
+ "assignedTo",
9
+ "targetVersion",
10
+ "parentTask",
11
+ "startDate",
12
+ "dueDate",
13
+ "estimatedTime",
14
+ "qaStatus",
15
+ "gitLink",
16
+ "gitLink1",
17
+ "gitLink2",
18
+ "expectedTime",
19
+ "prLink",
20
+ "storyPoints",
21
+ ];
22
+ function todayMmDdYyyy() {
23
+ const now = new Date();
24
+ const month = String(now.getMonth() + 1).padStart(2, "0");
25
+ const day = String(now.getDate()).padStart(2, "0");
26
+ const year = String(now.getFullYear());
27
+ return `${month}/${day}/${year}`;
28
+ }
29
+ function defaultTrackerName(config) {
30
+ return (resolveTracker("User Story", config)?.name ??
31
+ resolveTracker("story", config)?.name ??
32
+ config.trackers[0]?.name ??
33
+ "User Story");
34
+ }
35
+ function defaultStatusName(config) {
36
+ return (resolveStatus("New", config)?.name ??
37
+ config.statuses[0]?.name ??
38
+ "New");
39
+ }
40
+ function defaultPriorityName(config) {
41
+ return (resolvePriority("Normal", config)?.name ??
42
+ config.priorities[0]?.name ??
43
+ "Normal");
44
+ }
45
+ function buildDraft(input, config) {
46
+ const trackerRaw = input.tracker?.trim();
47
+ const statusRaw = input.status?.trim();
48
+ const priorityRaw = input.priority?.trim();
49
+ return {
50
+ project: input.project?.trim() || "",
51
+ tracker: trackerRaw
52
+ ? (resolveTracker(trackerRaw, config)?.name ??
53
+ applyTrackerAlias(trackerRaw))
54
+ : defaultTrackerName(config),
55
+ status: statusRaw
56
+ ? (resolveStatus(statusRaw, config)?.name ?? statusRaw)
57
+ : defaultStatusName(config),
58
+ priority: priorityRaw
59
+ ? (resolvePriority(priorityRaw, config)?.name ??
60
+ applyPriorityAlias(priorityRaw))
61
+ : defaultPriorityName(config),
62
+ subject: "",
63
+ description: "",
64
+ assignedTo: "",
65
+ targetVersion: "",
66
+ parentTask: "",
67
+ startDate: todayMmDdYyyy(),
68
+ dueDate: "",
69
+ estimatedTime: "",
70
+ qaStatus: "",
71
+ gitLink: "",
72
+ gitLink1: "",
73
+ gitLink2: "",
74
+ expectedTime: "",
75
+ prLink: "",
76
+ storyPoints: "",
77
+ };
78
+ }
79
+ function resolveTextField(params) {
80
+ const mode = params.mode;
81
+ const customValue = (params.custom ?? "").trim();
82
+ // Custom textbox has priority: if user typed text, use it.
83
+ if (customValue.length) {
84
+ return customValue;
85
+ }
86
+ // Explicit default keeps existing default behavior.
87
+ if (mode === "default") {
88
+ return params.defaultValue;
89
+ }
90
+ // No selection means blank for optional fields.
91
+ if (params.allowBlank) {
92
+ return "";
93
+ }
94
+ return undefined;
95
+ }
96
+ function modeOptions(allowBlank) {
97
+ return ["default"];
98
+ }
99
+ function parseProjectSelection(value) {
100
+ const raw = value.trim();
101
+ const idPrefix = /^(\d+)\s*[-—]\s*/.exec(raw);
102
+ if (idPrefix) {
103
+ return idPrefix[1] ?? raw;
104
+ }
105
+ return raw;
106
+ }
107
+ function normalizeNone(value) {
108
+ const t = value.trim();
109
+ if (!t || /^(none|blank|skip|leave blank|n\/a)$/i.test(t)) {
110
+ return "";
111
+ }
112
+ return t;
113
+ }
114
+ function intakeComplete(input, draft) {
115
+ if (!draft.project || !draft.subject) {
116
+ return false;
117
+ }
118
+ if (input.tracker === undefined ||
119
+ input.status === undefined ||
120
+ input.priority === undefined) {
121
+ return false;
122
+ }
123
+ // Optional text fields should be allowed to be blank/omitted without causing
124
+ // another intake prompt; draft builder will normalize missing values to "".
125
+ return true;
126
+ }
127
+ async function buildFullIntakeQuestions(draft, config) {
128
+ const projectsData = await getProjects();
129
+ const projects = (projectsData.projects ?? []);
130
+ const projectOptions = projects.map((p) => `${p.id} — ${p.name}`);
131
+ const optionalHint = "(Default keeps default; use input value for custom; leave both empty for blank)";
132
+ return [
133
+ {
134
+ field: "project",
135
+ question: "Project *",
136
+ options: projectOptions,
137
+ },
138
+ {
139
+ field: "tracker",
140
+ question: "Tracker *",
141
+ options: enumerationOptions(config.trackers),
142
+ },
143
+ {
144
+ field: "subjectMode",
145
+ question: "Subject *",
146
+ options: ["default"],
147
+ },
148
+ {
149
+ field: "subjectCustom",
150
+ question: "Subject — input value (required)",
151
+ },
152
+ {
153
+ field: "status",
154
+ question: "Status *",
155
+ options: enumerationOptions(config.statuses),
156
+ },
157
+ {
158
+ field: "priority",
159
+ question: "Priority *",
160
+ options: enumerationOptions(config.priorities),
161
+ },
162
+ {
163
+ field: "descriptionMode",
164
+ question: `Description ${optionalHint}`,
165
+ options: modeOptions(true),
166
+ },
167
+ {
168
+ field: "descriptionCustom",
169
+ question: "Description — input value",
170
+ },
171
+ {
172
+ field: "assignedToMode",
173
+ question: `Assigned To ${optionalHint}`,
174
+ options: modeOptions(true),
175
+ },
176
+ {
177
+ field: "assignedToCustom",
178
+ question: "Assigned To — input value",
179
+ },
180
+ {
181
+ field: "targetVersionMode",
182
+ question: `Target version ${optionalHint}`,
183
+ options: modeOptions(true),
184
+ },
185
+ {
186
+ field: "targetVersionCustom",
187
+ question: "Target version — input value",
188
+ },
189
+ {
190
+ field: "parentTaskMode",
191
+ question: `Parent task (issue ID) ${optionalHint}`,
192
+ options: modeOptions(true),
193
+ },
194
+ {
195
+ field: "parentTaskCustom",
196
+ question: "Parent task — input value",
197
+ },
198
+ {
199
+ field: "startDateMode",
200
+ question: `Start date (default ${draft.startDate}) ${optionalHint}`,
201
+ options: modeOptions(true),
202
+ },
203
+ {
204
+ field: "startDateCustom",
205
+ question: "Start date — input value",
206
+ },
207
+ {
208
+ field: "dueDateMode",
209
+ question: `Due date ${optionalHint}`,
210
+ options: modeOptions(true),
211
+ },
212
+ {
213
+ field: "dueDateCustom",
214
+ question: "Due date — input value",
215
+ },
216
+ {
217
+ field: "estimatedTimeMode",
218
+ question: `Estimated time (hours or e.g. 3 days) ${optionalHint}`,
219
+ options: modeOptions(true),
220
+ },
221
+ {
222
+ field: "estimatedTimeCustom",
223
+ question: "Estimated time — input value",
224
+ },
225
+ {
226
+ field: "qaStatusMode",
227
+ question: `QA Status ${optionalHint}`,
228
+ options: modeOptions(true),
229
+ },
230
+ {
231
+ field: "qaStatusCustom",
232
+ question: "QA Status — input value",
233
+ },
234
+ {
235
+ field: "gitLinkMode",
236
+ question: `Git Link ${optionalHint}`,
237
+ options: modeOptions(true),
238
+ },
239
+ {
240
+ field: "gitLinkCustom",
241
+ question: "Git Link — input value",
242
+ },
243
+ {
244
+ field: "gitLink1Mode",
245
+ question: `Git Link1 ${optionalHint}`,
246
+ options: modeOptions(true),
247
+ },
248
+ {
249
+ field: "gitLink1Custom",
250
+ question: "Git Link1 — input value",
251
+ },
252
+ {
253
+ field: "gitLink2Mode",
254
+ question: `Git Link2 ${optionalHint}`,
255
+ options: modeOptions(true),
256
+ },
257
+ {
258
+ field: "gitLink2Custom",
259
+ question: "Git Link2 — input value",
260
+ },
261
+ {
262
+ field: "expectedTimeMode",
263
+ question: `Expected Time ${optionalHint}`,
264
+ options: modeOptions(true),
265
+ },
266
+ {
267
+ field: "expectedTimeCustom",
268
+ question: "Expected Time — input value",
269
+ },
270
+ {
271
+ field: "prLinkMode",
272
+ question: `PR Link ${optionalHint}`,
273
+ options: modeOptions(true),
274
+ },
275
+ {
276
+ field: "prLinkCustom",
277
+ question: "PR Link — input value",
278
+ },
279
+ {
280
+ field: "storyPointsMode",
281
+ question: `Story points ${optionalHint}`,
282
+ options: modeOptions(true),
283
+ },
284
+ {
285
+ field: "storyPointsCustom",
286
+ question: "Story points — input value",
287
+ },
288
+ ];
289
+ }
290
+ function normalizeDateToIso(value) {
291
+ const text = value.trim();
292
+ if (!text)
293
+ return undefined;
294
+ if (/^\d{4}-\d{2}-\d{2}$/.test(text))
295
+ return text;
296
+ const mmddyyyy = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(text);
297
+ if (mmddyyyy) {
298
+ return `${mmddyyyy[3]}-${mmddyyyy[1]}-${mmddyyyy[2]}`;
299
+ }
300
+ const parsed = new Date(text);
301
+ if (!Number.isNaN(parsed.getTime())) {
302
+ return parsed.toISOString().slice(0, 10);
303
+ }
304
+ return undefined;
305
+ }
306
+ function parseEstimatedTime(value) {
307
+ const trimmed = value.trim();
308
+ const daysMatch = /^(\d+(?:\.\d+)?)\s*days?$/i.exec(trimmed);
309
+ if (daysMatch) {
310
+ return Number(daysMatch[1]) * 8;
311
+ }
312
+ return parsePositiveNumber(trimmed);
313
+ }
314
+ function parsePositiveNumber(value) {
315
+ const parsed = Number(value.trim());
316
+ if (!Number.isFinite(parsed) || parsed <= 0)
317
+ return undefined;
318
+ return parsed;
319
+ }
320
+ function lookupIdByNameLocal(rows, name) {
321
+ return lookupIdByName(rows, name);
322
+ }
323
+ function validateEnumerationChoice(value, resolve, options, label) {
324
+ if (!value) {
325
+ return undefined;
326
+ }
327
+ if (resolve(value)) {
328
+ return undefined;
329
+ }
330
+ return `${label} must be one of: ${options.join(", ")}`;
331
+ }
332
+ function normalizeInput(input) {
333
+ const normalized = { ...input };
334
+ if (input.project) {
335
+ normalized.project = parseProjectSelection(input.project);
336
+ }
337
+ // Normalize custom text inputs
338
+ const customKeys = [
339
+ "subjectCustom",
340
+ "descriptionCustom",
341
+ "assignedToCustom",
342
+ "targetVersionCustom",
343
+ "parentTaskCustom",
344
+ "startDateCustom",
345
+ "dueDateCustom",
346
+ "estimatedTimeCustom",
347
+ "qaStatusCustom",
348
+ "gitLinkCustom",
349
+ "gitLink1Custom",
350
+ "gitLink2Custom",
351
+ "expectedTimeCustom",
352
+ "prLinkCustom",
353
+ "storyPointsCustom",
354
+ ];
355
+ for (const key of customKeys) {
356
+ const v = normalized[key];
357
+ if (typeof v === "string") {
358
+ normalized[key] = normalizeNone(v);
359
+ }
360
+ }
361
+ return normalized;
362
+ }
363
+ export async function createIssueWorkflow(input) {
364
+ const config = await loadRedmineConfig();
365
+ const normalizedInput = normalizeInput(input);
366
+ const baseDraft = buildDraft({
367
+ ...normalizedInput,
368
+ ...(normalizedInput.project
369
+ ? { project: parseProjectSelection(normalizedInput.project) }
370
+ : {}),
371
+ }, config);
372
+ const subject = resolveTextField({
373
+ mode: normalizedInput.subjectMode,
374
+ custom: normalizedInput.subjectCustom,
375
+ defaultValue: "",
376
+ allowBlank: false,
377
+ });
378
+ const description = resolveTextField({
379
+ mode: normalizedInput.descriptionMode,
380
+ custom: normalizedInput.descriptionCustom,
381
+ defaultValue: "",
382
+ allowBlank: true,
383
+ });
384
+ const assignedTo = resolveTextField({
385
+ mode: normalizedInput.assignedToMode,
386
+ custom: normalizedInput.assignedToCustom,
387
+ defaultValue: "",
388
+ allowBlank: true,
389
+ });
390
+ const targetVersion = resolveTextField({
391
+ mode: normalizedInput.targetVersionMode,
392
+ custom: normalizedInput.targetVersionCustom,
393
+ defaultValue: "",
394
+ allowBlank: true,
395
+ });
396
+ const parentTask = resolveTextField({
397
+ mode: normalizedInput.parentTaskMode,
398
+ custom: normalizedInput.parentTaskCustom,
399
+ defaultValue: "",
400
+ allowBlank: true,
401
+ });
402
+ const startDate = resolveTextField({
403
+ mode: normalizedInput.startDateMode,
404
+ custom: normalizedInput.startDateCustom,
405
+ defaultValue: todayMmDdYyyy(),
406
+ allowBlank: true,
407
+ });
408
+ const dueDate = resolveTextField({
409
+ mode: normalizedInput.dueDateMode,
410
+ custom: normalizedInput.dueDateCustom,
411
+ defaultValue: "",
412
+ allowBlank: true,
413
+ });
414
+ const estimatedTime = resolveTextField({
415
+ mode: normalizedInput.estimatedTimeMode,
416
+ custom: normalizedInput.estimatedTimeCustom,
417
+ defaultValue: "",
418
+ allowBlank: true,
419
+ });
420
+ const qaStatus = resolveTextField({
421
+ mode: normalizedInput.qaStatusMode,
422
+ custom: normalizedInput.qaStatusCustom,
423
+ defaultValue: "",
424
+ allowBlank: true,
425
+ });
426
+ const gitLink = resolveTextField({
427
+ mode: normalizedInput.gitLinkMode,
428
+ custom: normalizedInput.gitLinkCustom,
429
+ defaultValue: "",
430
+ allowBlank: true,
431
+ });
432
+ const gitLink1 = resolveTextField({
433
+ mode: normalizedInput.gitLink1Mode,
434
+ custom: normalizedInput.gitLink1Custom,
435
+ defaultValue: "",
436
+ allowBlank: true,
437
+ });
438
+ const gitLink2 = resolveTextField({
439
+ mode: normalizedInput.gitLink2Mode,
440
+ custom: normalizedInput.gitLink2Custom,
441
+ defaultValue: "",
442
+ allowBlank: true,
443
+ });
444
+ const expectedTime = resolveTextField({
445
+ mode: normalizedInput.expectedTimeMode,
446
+ custom: normalizedInput.expectedTimeCustom,
447
+ defaultValue: "",
448
+ allowBlank: true,
449
+ });
450
+ const prLink = resolveTextField({
451
+ mode: normalizedInput.prLinkMode,
452
+ custom: normalizedInput.prLinkCustom,
453
+ defaultValue: "",
454
+ allowBlank: true,
455
+ });
456
+ const storyPoints = resolveTextField({
457
+ mode: normalizedInput.storyPointsMode,
458
+ custom: normalizedInput.storyPointsCustom,
459
+ defaultValue: "",
460
+ allowBlank: true,
461
+ });
462
+ const draft = {
463
+ ...baseDraft,
464
+ subject: subject ?? "",
465
+ description: description ?? "",
466
+ assignedTo: assignedTo ?? "",
467
+ targetVersion: targetVersion ?? "",
468
+ parentTask: parentTask ?? "",
469
+ startDate: startDate && startDate.trim() ? startDate : todayMmDdYyyy(),
470
+ dueDate: dueDate ?? "",
471
+ estimatedTime: estimatedTime ?? "",
472
+ qaStatus: qaStatus ?? "",
473
+ gitLink: gitLink ?? "",
474
+ gitLink1: gitLink1 ?? "",
475
+ gitLink2: gitLink2 ?? "",
476
+ expectedTime: expectedTime ?? "",
477
+ prLink: prLink ?? "",
478
+ storyPoints: storyPoints ?? "",
479
+ };
480
+ const trackerResolved = resolveTracker(draft.tracker, config);
481
+ const statusResolved = resolveStatus(draft.status, config);
482
+ const priorityResolved = resolvePriority(draft.priority, config);
483
+ if (trackerResolved)
484
+ draft.tracker = trackerResolved.name;
485
+ if (statusResolved)
486
+ draft.status = statusResolved.name;
487
+ if (priorityResolved)
488
+ draft.priority = priorityResolved.name;
489
+ const configPayload = configOptionsPayload(config);
490
+ const choiceErrors = [
491
+ validateEnumerationChoice(draft.tracker, (v) => resolveTracker(v, config), configPayload.trackerOptions, "Tracker"),
492
+ validateEnumerationChoice(draft.status, (v) => resolveStatus(v, config), configPayload.statusOptions, "Status"),
493
+ validateEnumerationChoice(draft.priority, (v) => resolvePriority(v, config), configPayload.priorityOptions, "Priority"),
494
+ ].filter((e) => Boolean(e));
495
+ if (choiceErrors.length > 0) {
496
+ return {
497
+ success: false,
498
+ error: choiceErrors.join("; "),
499
+ data: {
500
+ draft,
501
+ ...configPayload,
502
+ },
503
+ };
504
+ }
505
+ if (!intakeComplete(normalizedInput, draft) || !normalizedInput.subjectMode) {
506
+ const questions = await buildFullIntakeQuestions(draft, config);
507
+ return {
508
+ success: false,
509
+ needsInput: true,
510
+ question: {
511
+ field: "createIssueBatch",
512
+ question: "Fill all fields in one AskQuestion. For text fields: choose Default/Leave Blank/Enter Custom, and provide the custom value in the paired input box.",
513
+ },
514
+ questions,
515
+ data: {
516
+ batchMode: true,
517
+ step: "collect_issue_fields",
518
+ draft,
519
+ ...configPayload,
520
+ hint: "Answer all questions in one go, then retry redmine_agent_create_issue with each field key set (use empty string to skip optional fields). Tracker aliases: CR→Change Request, bug→Bug, story→User Story.",
521
+ },
522
+ };
523
+ }
524
+ const startDateIso = normalizeDateToIso(draft.startDate);
525
+ const dueDateIso = draft.dueDate ? normalizeDateToIso(draft.dueDate) : undefined;
526
+ if (!startDateIso) {
527
+ return {
528
+ success: false,
529
+ error: "Start date must be MM/DD/YYYY, YYYY-MM-DD, or a parseable date",
530
+ data: { draft },
531
+ };
532
+ }
533
+ if (draft.dueDate && !dueDateIso) {
534
+ return {
535
+ success: false,
536
+ error: "Due date must be MM/DD/YYYY, YYYY-MM-DD, or a parseable date (e.g. 10 June 2026)",
537
+ data: { draft },
538
+ };
539
+ }
540
+ const estimatedHours = draft.estimatedTime
541
+ ? parseEstimatedTime(draft.estimatedTime)
542
+ : undefined;
543
+ if (draft.estimatedTime && estimatedHours === undefined) {
544
+ return {
545
+ success: false,
546
+ error: "Estimated time must be hours or a value like '3 days'",
547
+ data: { draft },
548
+ };
549
+ }
550
+ if (!normalizedInput.confirmSave) {
551
+ return {
552
+ success: false,
553
+ needsInput: true,
554
+ question: {
555
+ field: "confirmSave",
556
+ question: "Review the structured issue payload and confirm upload to Redmine.",
557
+ options: ["yes", "no"],
558
+ },
559
+ data: {
560
+ step: "preview",
561
+ draft,
562
+ structuredIssue: draft,
563
+ previewText: JSON.stringify(draft, null, 2),
564
+ summary: "Issue payload prepared. Confirm with confirmSave: yes to upload.",
565
+ },
566
+ };
567
+ }
568
+ if (normalizedInput.confirmSave !== "yes") {
569
+ return {
570
+ success: false,
571
+ error: "Issue creation cancelled by user",
572
+ data: { draft },
573
+ };
574
+ }
575
+ try {
576
+ const hasCustomFieldValues = draft.qaStatus ||
577
+ draft.gitLink ||
578
+ draft.gitLink1 ||
579
+ draft.gitLink2 ||
580
+ draft.expectedTime ||
581
+ draft.prLink ||
582
+ draft.storyPoints;
583
+ const [customFieldsData] = await Promise.all([
584
+ hasCustomFieldValues
585
+ ? getCustomFields().catch(() => ({ custom_fields: [] }))
586
+ : Promise.resolve({ custom_fields: [] }),
587
+ ]);
588
+ const trackerId = trackerResolved?.id;
589
+ const statusId = statusResolved?.id;
590
+ const priorityId = priorityResolved?.id;
591
+ if (!trackerId) {
592
+ return { success: false, error: `Tracker not found: ${draft.tracker}` };
593
+ }
594
+ if (!statusId) {
595
+ return { success: false, error: `Status not found: ${draft.status}` };
596
+ }
597
+ if (!priorityId) {
598
+ return { success: false, error: `Priority not found: ${draft.priority}` };
599
+ }
600
+ const customFields = (customFieldsData.custom_fields ?? []);
601
+ const warnings = [];
602
+ const assignedToId = draft.assignedTo
603
+ ? await resolveAssignedToId(draft.assignedTo, { projectId: draft.project })
604
+ : undefined;
605
+ if (draft.assignedTo && !assignedToId) {
606
+ warnings.push(`Assigned To "${draft.assignedTo}" could not be resolved and was omitted.`);
607
+ }
608
+ let fixedVersionId;
609
+ if (draft.targetVersion) {
610
+ const versionsData = await getProjectVersions(draft.project);
611
+ const versions = (versionsData.versions ?? []);
612
+ fixedVersionId = lookupIdByNameLocal(versions, draft.targetVersion);
613
+ if (!fixedVersionId) {
614
+ warnings.push(`Target version "${draft.targetVersion}" not found in project and was omitted.`);
615
+ }
616
+ }
617
+ const parentIssueId = draft.parentTask
618
+ ? parsePositiveNumber(draft.parentTask)
619
+ : undefined;
620
+ if (draft.parentTask && parentIssueId === undefined) {
621
+ warnings.push(`Parent task "${draft.parentTask}" is not a valid issue ID and was omitted.`);
622
+ }
623
+ const issueCustomFields = customFields.filter((cf) => !cf.customized_type ||
624
+ cf.customized_type === "Issue" ||
625
+ cf.customized_type === "issue");
626
+ const getCustomFieldId = (name) => lookupIdByNameLocal(issueCustomFields, name);
627
+ const customValues = [
628
+ { name: "QA Status", value: draft.qaStatus },
629
+ { name: "Git Link", value: draft.gitLink },
630
+ { name: "Git Link1", value: draft.gitLink1 },
631
+ { name: "Git Link2", value: draft.gitLink2 },
632
+ { name: "Expected Time", value: draft.expectedTime },
633
+ { name: "PR Link", value: draft.prLink },
634
+ { name: "Story points", value: draft.storyPoints },
635
+ ];
636
+ const mappedCustomFields = [];
637
+ for (const item of customValues) {
638
+ if (!item.value)
639
+ continue;
640
+ const id = getCustomFieldId(item.name);
641
+ if (!id) {
642
+ warnings.push(`Custom field "${item.name}" was not found and was omitted.`);
643
+ continue;
644
+ }
645
+ mappedCustomFields.push({ id, value: item.value });
646
+ }
647
+ const created = await createIssue({
648
+ projectId: draft.project,
649
+ trackerId,
650
+ subject: draft.subject,
651
+ statusId,
652
+ priorityId,
653
+ startDate: startDateIso,
654
+ ...(draft.description ? { description: draft.description } : {}),
655
+ ...(assignedToId !== undefined ? { assignedToId } : {}),
656
+ ...(fixedVersionId !== undefined ? { fixedVersionId } : {}),
657
+ ...(parentIssueId !== undefined ? { parentIssueId } : {}),
658
+ ...(dueDateIso !== undefined ? { dueDate: dueDateIso } : {}),
659
+ ...(estimatedHours !== undefined ? { estimatedHours } : {}),
660
+ ...(mappedCustomFields.length > 0
661
+ ? { customFields: mappedCustomFields }
662
+ : {}),
663
+ });
664
+ const issue = created.issue;
665
+ const { baseUrl } = requireRedmineEnv();
666
+ return {
667
+ success: true,
668
+ data: {
669
+ uploaded: true,
670
+ issueId: issue.id,
671
+ issueUrl: `${baseUrl}/issues/${issue.id}`,
672
+ structuredIssue: draft,
673
+ warnings,
674
+ },
675
+ };
676
+ }
677
+ catch (error) {
678
+ if (axios.isAxiosError(error)) {
679
+ const apiErrors = error.response?.data
680
+ ?.errors;
681
+ if (apiErrors && apiErrors.length > 0) {
682
+ return { success: false, error: apiErrors.join("; "), data: { draft } };
683
+ }
684
+ return {
685
+ success: false,
686
+ error: `Redmine create issue failed (${error.response?.status ?? "unknown status"})`,
687
+ data: { draft },
688
+ };
689
+ }
690
+ return {
691
+ success: false,
692
+ error: "Redmine create issue failed",
693
+ data: { draft },
694
+ };
695
+ }
696
+ }
697
+ //# sourceMappingURL=createIssueWorkflow.js.map