specweave 0.28.19 → 0.28.20

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 (85) hide show
  1. package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts +16 -0
  2. package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts.map +1 -1
  3. package/dist/plugins/specweave-ado/lib/ado-spec-sync.js +63 -3
  4. package/dist/plugins/specweave-ado/lib/ado-spec-sync.js.map +1 -1
  5. package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts +12 -3
  6. package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts.map +1 -1
  7. package/dist/plugins/specweave-ado/lib/ado-status-sync.js +37 -3
  8. package/dist/plugins/specweave-ado/lib/ado-status-sync.js.map +1 -1
  9. package/dist/plugins/specweave-github/lib/github-increment-sync-cli.d.ts +8 -6
  10. package/dist/plugins/specweave-github/lib/github-increment-sync-cli.d.ts.map +1 -1
  11. package/dist/plugins/specweave-github/lib/github-increment-sync-cli.js +230 -165
  12. package/dist/plugins/specweave-github/lib/github-increment-sync-cli.js.map +1 -1
  13. package/dist/plugins/specweave-github/lib/github-status-sync.d.ts +10 -0
  14. package/dist/plugins/specweave-github/lib/github-status-sync.d.ts.map +1 -1
  15. package/dist/plugins/specweave-github/lib/github-status-sync.js +40 -2
  16. package/dist/plugins/specweave-github/lib/github-status-sync.js.map +1 -1
  17. package/dist/plugins/specweave-github/lib/increment-issue-builder.d.ts +2 -0
  18. package/dist/plugins/specweave-github/lib/increment-issue-builder.d.ts.map +1 -1
  19. package/dist/plugins/specweave-github/lib/increment-issue-builder.js +25 -5
  20. package/dist/plugins/specweave-github/lib/increment-issue-builder.js.map +1 -1
  21. package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts +12 -0
  22. package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts.map +1 -1
  23. package/dist/plugins/specweave-jira/lib/jira-spec-sync.js +57 -5
  24. package/dist/plugins/specweave-jira/lib/jira-spec-sync.js.map +1 -1
  25. package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +5 -1
  26. package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -1
  27. package/dist/plugins/specweave-jira/lib/jira-status-sync.js +12 -4
  28. package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -1
  29. package/dist/src/cli/helpers/init/external-import.d.ts.map +1 -1
  30. package/dist/src/cli/helpers/init/external-import.js +186 -19
  31. package/dist/src/cli/helpers/init/external-import.js.map +1 -1
  32. package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts +115 -0
  33. package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts.map +1 -0
  34. package/dist/src/cli/helpers/init/jira-ado-auto-detect.js +590 -0
  35. package/dist/src/cli/helpers/init/jira-ado-auto-detect.js.map +1 -0
  36. package/dist/src/config/types.d.ts +6 -6
  37. package/dist/src/core/background/index.d.ts +11 -0
  38. package/dist/src/core/background/index.d.ts.map +1 -0
  39. package/dist/src/core/background/index.js +11 -0
  40. package/dist/src/core/background/index.js.map +1 -0
  41. package/dist/src/core/background/job-manager.d.ts +65 -0
  42. package/dist/src/core/background/job-manager.d.ts.map +1 -0
  43. package/dist/src/core/background/job-manager.js +192 -0
  44. package/dist/src/core/background/job-manager.js.map +1 -0
  45. package/dist/src/core/background/types.d.ts +59 -0
  46. package/dist/src/core/background/types.d.ts.map +1 -0
  47. package/dist/src/core/background/types.js +8 -0
  48. package/dist/src/core/background/types.js.map +1 -0
  49. package/dist/src/core/repo-structure/multi-repo-configurator.d.ts +25 -0
  50. package/dist/src/core/repo-structure/multi-repo-configurator.d.ts.map +1 -0
  51. package/dist/src/core/repo-structure/multi-repo-configurator.js +614 -0
  52. package/dist/src/core/repo-structure/multi-repo-configurator.js.map +1 -0
  53. package/dist/src/core/repo-structure/repo-initializer.d.ts +40 -0
  54. package/dist/src/core/repo-structure/repo-initializer.d.ts.map +1 -0
  55. package/dist/src/core/repo-structure/repo-initializer.js +252 -0
  56. package/dist/src/core/repo-structure/repo-initializer.js.map +1 -0
  57. package/dist/src/core/repo-structure/repo-structure-manager.d.ts +3 -37
  58. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  59. package/dist/src/core/repo-structure/repo-structure-manager.js +23 -803
  60. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  61. package/dist/src/core/types/spec-metadata.d.ts +2 -0
  62. package/dist/src/core/types/spec-metadata.d.ts.map +1 -1
  63. package/dist/src/importers/import-coordinator.d.ts +20 -0
  64. package/dist/src/importers/import-coordinator.d.ts.map +1 -1
  65. package/dist/src/importers/import-coordinator.js.map +1 -1
  66. package/dist/src/init/architecture/types.d.ts +2 -2
  67. package/dist/src/init/compliance/types.d.ts +1 -1
  68. package/package.json +1 -1
  69. package/plugins/specweave/commands/specweave-jobs.md +160 -0
  70. package/plugins/specweave-ado/lib/ado-spec-sync.js +59 -3
  71. package/plugins/specweave-ado/lib/ado-spec-sync.ts +72 -3
  72. package/plugins/specweave-ado/lib/ado-status-sync.js +35 -3
  73. package/plugins/specweave-ado/lib/ado-status-sync.ts +48 -4
  74. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +6 -0
  75. package/plugins/specweave-github/lib/github-increment-sync-cli.js +268 -155
  76. package/plugins/specweave-github/lib/github-increment-sync-cli.ts +313 -209
  77. package/plugins/specweave-github/lib/github-status-sync.js +37 -1
  78. package/plugins/specweave-github/lib/github-status-sync.ts +60 -4
  79. package/plugins/specweave-github/lib/increment-issue-builder.js +26 -5
  80. package/plugins/specweave-github/lib/increment-issue-builder.ts +36 -5
  81. package/plugins/specweave-jira/lib/jira-spec-sync.js +53 -5
  82. package/plugins/specweave-jira/lib/jira-spec-sync.ts +87 -7
  83. package/plugins/specweave-jira/lib/jira-status-sync.js +9 -3
  84. package/plugins/specweave-jira/lib/jira-status-sync.ts +15 -6
  85. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +9 -0
@@ -26,6 +26,9 @@ class GitHubStatusSync {
26
26
  /**
27
27
  * Update GitHub issue status
28
28
  *
29
+ * Preserves existing labels that are not status-related.
30
+ * Only replaces labels that start with "status:" prefix.
31
+ *
29
32
  * @param issueNumber - GitHub issue number
30
33
  * @param status - New status (state and labels)
31
34
  */
@@ -37,10 +40,43 @@ class GitHubStatusSync {
37
40
  state: status.state
38
41
  };
39
42
  if (status.labels && status.labels.length > 0) {
40
- updateData.labels = status.labels;
43
+ const currentLabels = await this.getCurrentLabels(issueNumber);
44
+ const preservedLabels = currentLabels.filter(
45
+ (label) => !label.startsWith("status:")
46
+ );
47
+ const newStatusLabels = status.labels.filter(
48
+ (label) => label.startsWith("status:")
49
+ );
50
+ const newOtherLabels = status.labels.filter(
51
+ (label) => !label.startsWith("status:")
52
+ );
53
+ const mergedLabels = [.../* @__PURE__ */ new Set([
54
+ ...preservedLabels,
55
+ ...newStatusLabels,
56
+ ...newOtherLabels
57
+ ])];
58
+ updateData.labels = mergedLabels;
41
59
  }
42
60
  await this.octokit.rest.issues.update(updateData);
43
61
  }
62
+ /**
63
+ * Get current labels from GitHub issue
64
+ *
65
+ * @param issueNumber - GitHub issue number
66
+ * @returns Array of current label names
67
+ */
68
+ async getCurrentLabels(issueNumber) {
69
+ try {
70
+ const response = await this.octokit.rest.issues.get({
71
+ owner: this.owner,
72
+ repo: this.repo,
73
+ issue_number: issueNumber
74
+ });
75
+ return response.data.labels.map((label) => typeof label === "string" ? label : label.name).filter(Boolean);
76
+ } catch {
77
+ return [];
78
+ }
79
+ }
44
80
  /**
45
81
  * Post status change comment to GitHub issue
46
82
  *
@@ -53,6 +53,9 @@ export class GitHubStatusSync {
53
53
  /**
54
54
  * Update GitHub issue status
55
55
  *
56
+ * Preserves existing labels that are not status-related.
57
+ * Only replaces labels that start with "status:" prefix.
58
+ *
56
59
  * @param issueNumber - GitHub issue number
57
60
  * @param status - New status (state and labels)
58
61
  */
@@ -60,21 +63,74 @@ export class GitHubStatusSync {
60
63
  issueNumber: number,
61
64
  status: ExternalStatus
62
65
  ): Promise<void> {
63
- const updateData: any = {
66
+ const updateData: {
67
+ owner: string;
68
+ repo: string;
69
+ issue_number: number;
70
+ state: 'open' | 'closed';
71
+ labels?: string[];
72
+ } = {
64
73
  owner: this.owner,
65
74
  repo: this.repo,
66
75
  issue_number: issueNumber,
67
- state: status.state
76
+ state: status.state as 'open' | 'closed'
68
77
  };
69
78
 
70
- // Add labels if provided
79
+ // Merge labels - preserve non-status labels, replace status labels
71
80
  if (status.labels && status.labels.length > 0) {
72
- updateData.labels = status.labels;
81
+ // Fetch current labels to preserve non-status ones
82
+ const currentLabels = await this.getCurrentLabels(issueNumber);
83
+
84
+ // Filter out status-related labels (start with "status:")
85
+ const preservedLabels = currentLabels.filter(
86
+ label => !label.startsWith('status:')
87
+ );
88
+
89
+ // Get new status labels from the provided status
90
+ const newStatusLabels = status.labels.filter(
91
+ label => label.startsWith('status:')
92
+ );
93
+
94
+ // Get non-status labels from the provided status (e.g., priority, type)
95
+ const newOtherLabels = status.labels.filter(
96
+ label => !label.startsWith('status:')
97
+ );
98
+
99
+ // Combine: preserved non-status + new status + new other labels
100
+ const mergedLabels = [...new Set([
101
+ ...preservedLabels,
102
+ ...newStatusLabels,
103
+ ...newOtherLabels
104
+ ])];
105
+
106
+ updateData.labels = mergedLabels;
73
107
  }
74
108
 
75
109
  await this.octokit.rest.issues.update(updateData);
76
110
  }
77
111
 
112
+ /**
113
+ * Get current labels from GitHub issue
114
+ *
115
+ * @param issueNumber - GitHub issue number
116
+ * @returns Array of current label names
117
+ */
118
+ private async getCurrentLabels(issueNumber: number): Promise<string[]> {
119
+ try {
120
+ const response = await this.octokit.rest.issues.get({
121
+ owner: this.owner,
122
+ repo: this.repo,
123
+ issue_number: issueNumber
124
+ });
125
+
126
+ return response.data.labels
127
+ .map((label: any) => (typeof label === 'string' ? label : label.name))
128
+ .filter(Boolean) as string[];
129
+ } catch {
130
+ return [];
131
+ }
132
+ }
133
+
78
134
  /**
79
135
  * Post status change comment to GitHub issue
80
136
  *
@@ -207,6 +207,24 @@ class IncrementIssueBuilder {
207
207
  body += `---
208
208
 
209
209
  `;
210
+ const storyTasks = incrementData.tasks.filter(
211
+ (t) => t.userStories.includes(story.id) || t.userStories.some((us) => us.toUpperCase() === story.id.toUpperCase())
212
+ );
213
+ if (storyTasks.length > 0) {
214
+ body += `## Tasks
215
+
216
+ `;
217
+ const completedTasks = storyTasks.filter((t) => t.completed).length;
218
+ body += `Progress: ${completedTasks}/${storyTasks.length} tasks
219
+
220
+ `;
221
+ for (const task of storyTasks) {
222
+ const checkbox = task.completed ? "[x]" : "[ ]";
223
+ body += `- ${checkbox} **${task.id}**: ${task.title}
224
+ `;
225
+ }
226
+ body += "\n---\n\n";
227
+ }
210
228
  body += `## Implementation
211
229
 
212
230
  `;
@@ -225,8 +243,10 @@ class IncrementIssueBuilder {
225
243
  body += `\u{1F916} Auto-synced by SpecWeave Increment Sync`;
226
244
  const labels = ["specweave", "user-story"];
227
245
  if (incrementData.frontmatter.type) {
228
- labels.push(incrementData.frontmatter.type);
246
+ labels.push(incrementData.frontmatter.type.toLowerCase());
229
247
  }
248
+ const priority = story.priority?.toLowerCase() || incrementData.frontmatter.priority?.toLowerCase() || "p2";
249
+ labels.push(priority);
230
250
  return { title, body, labels };
231
251
  }
232
252
  /**
@@ -338,10 +358,11 @@ class IncrementIssueBuilder {
338
358
 
339
359
  `;
340
360
  body += `\u{1F916} Auto-synced by SpecWeave Increment Sync`;
341
- const labels = ["specweave", "increment", "enhancement"];
342
- if (incrementData.frontmatter.type) {
343
- labels.push(incrementData.frontmatter.type);
344
- }
361
+ const labels = ["specweave", "increment"];
362
+ const typeLabel = incrementData.frontmatter.type?.toLowerCase() || "enhancement";
363
+ labels.push(typeLabel);
364
+ const priority = incrementData.frontmatter.priority?.toLowerCase() || "p2";
365
+ labels.push(priority);
345
366
  return { title, body, labels };
346
367
  }
347
368
  /**
@@ -22,6 +22,7 @@ export interface IncrementFrontmatter {
22
22
  type?: string;
23
23
  status?: string;
24
24
  created?: string;
25
+ priority?: string;
25
26
  }
26
27
 
27
28
  export interface AcceptanceCriterion {
@@ -37,6 +38,7 @@ export interface UserStory {
37
38
  iWant: string;
38
39
  soThat: string;
39
40
  acceptanceCriteria: AcceptanceCriterion[];
41
+ priority?: string;
40
42
  }
41
43
 
42
44
  export interface Task {
@@ -322,6 +324,24 @@ export class IncrementIssueBuilder {
322
324
 
323
325
  body += `---\n\n`;
324
326
 
327
+ // Tasks for this user story
328
+ const storyTasks = incrementData.tasks.filter(t =>
329
+ t.userStories.includes(story.id) ||
330
+ t.userStories.some(us => us.toUpperCase() === story.id.toUpperCase())
331
+ );
332
+
333
+ if (storyTasks.length > 0) {
334
+ body += `## Tasks\n\n`;
335
+ const completedTasks = storyTasks.filter(t => t.completed).length;
336
+ body += `Progress: ${completedTasks}/${storyTasks.length} tasks\n\n`;
337
+
338
+ for (const task of storyTasks) {
339
+ const checkbox = task.completed ? '[x]' : '[ ]';
340
+ body += `- ${checkbox} **${task.id}**: ${task.title}\n`;
341
+ }
342
+ body += '\n---\n\n';
343
+ }
344
+
325
345
  // Link to increment
326
346
  body += `## Implementation\n\n`;
327
347
  if (githubRepo) {
@@ -335,10 +355,16 @@ export class IncrementIssueBuilder {
335
355
 
336
356
  // Labels
337
357
  const labels = ['specweave', 'user-story'];
358
+
359
+ // Add type label if available
338
360
  if (incrementData.frontmatter.type) {
339
- labels.push(incrementData.frontmatter.type);
361
+ labels.push(incrementData.frontmatter.type.toLowerCase());
340
362
  }
341
363
 
364
+ // Add priority label (from story or default to p2)
365
+ const priority = story.priority?.toLowerCase() || incrementData.frontmatter.priority?.toLowerCase() || 'p2';
366
+ labels.push(priority);
367
+
342
368
  return { title, body, labels };
343
369
  }
344
370
 
@@ -438,10 +464,15 @@ export class IncrementIssueBuilder {
438
464
  body += `🤖 Auto-synced by SpecWeave Increment Sync`;
439
465
 
440
466
  // Labels
441
- const labels = ['specweave', 'increment', 'enhancement'];
442
- if (incrementData.frontmatter.type) {
443
- labels.push(incrementData.frontmatter.type);
444
- }
467
+ const labels = ['specweave', 'increment'];
468
+
469
+ // Add type label (default to 'enhancement' if not specified)
470
+ const typeLabel = incrementData.frontmatter.type?.toLowerCase() || 'enhancement';
471
+ labels.push(typeLabel);
472
+
473
+ // Add priority label (default to 'p2' if not specified)
474
+ const priority = incrementData.frontmatter.priority?.toLowerCase() || 'p2';
475
+ labels.push(priority);
445
476
 
446
477
  return { title, body, labels };
447
478
  }
@@ -132,6 +132,7 @@ class JiraSpecSync {
132
132
  async createJiraEpic(spec) {
133
133
  const epicSummary = `[${spec.metadata.id.toUpperCase()}] ${spec.metadata.title}`;
134
134
  const epicDescription = this.generateEpicDescription(spec);
135
+ const issueType = this.mapTypeToJira(spec.metadata.type, "Epic");
135
136
  const payload = {
136
137
  fields: {
137
138
  project: {
@@ -140,9 +141,13 @@ class JiraSpecSync {
140
141
  summary: epicSummary,
141
142
  description: epicDescription,
142
143
  issuetype: {
143
- name: "Epic"
144
+ name: issueType
144
145
  },
145
- labels: [`spec:${spec.metadata.id}`, `priority:${spec.metadata.priority}`]
146
+ labels: [`spec:${spec.metadata.id}`, `priority:${spec.metadata.priority}`],
147
+ // Set native JIRA priority field (P0→Highest, P1→High, P2→Medium, P3→Low)
148
+ priority: {
149
+ name: this.mapPriorityToJira(spec.metadata.priority)
150
+ }
146
151
  }
147
152
  };
148
153
  const response = await this.client.post("/issue", payload);
@@ -213,7 +218,8 @@ class JiraSpecSync {
213
218
  summary: storySummary,
214
219
  description: storyDescription,
215
220
  epicLink: epicKey,
216
- labels: [`user-story`, `spec:${spec.metadata.id}`, `priority:${us.priority}`]
221
+ labels: [`user-story`, `spec:${spec.metadata.id}`, `priority:${us.priority}`],
222
+ priority: us.priority
217
223
  });
218
224
  created.push(us.id);
219
225
  console.log(` \u2705 Created ${us.id} \u2192 Story ${newStory.key}`);
@@ -347,6 +353,7 @@ ${acList}
347
353
  * Create Jira Story
348
354
  */
349
355
  async createStory(story) {
356
+ const issueType = this.mapTypeToJira(story.type, "Story");
350
357
  const payload = {
351
358
  fields: {
352
359
  project: {
@@ -355,12 +362,16 @@ ${acList}
355
362
  summary: story.summary,
356
363
  description: story.description,
357
364
  issuetype: {
358
- name: "Story"
365
+ name: issueType
359
366
  },
360
367
  labels: story.labels,
361
368
  // Link to epic (field name may vary by Jira configuration)
362
- customfield_10014: story.epicLink
369
+ customfield_10014: story.epicLink,
363
370
  // Epic Link field (adjust if needed)
371
+ // Set native JIRA priority field
372
+ priority: {
373
+ name: this.mapPriorityToJira(story.priority)
374
+ }
364
375
  }
365
376
  };
366
377
  const response = await this.client.post("/issue", payload);
@@ -411,6 +422,43 @@ ${acList}
411
422
  }
412
423
  });
413
424
  }
425
+ /**
426
+ * Map SpecWeave priority to JIRA priority name
427
+ *
428
+ * JIRA standard priority names: Highest, High, Medium, Low, Lowest
429
+ */
430
+ mapPriorityToJira(priority) {
431
+ if (!priority) return "Medium";
432
+ const map = {
433
+ P0: "Highest",
434
+ P1: "High",
435
+ P2: "Medium",
436
+ P3: "Low",
437
+ p0: "Highest",
438
+ p1: "High",
439
+ p2: "Medium",
440
+ p3: "Low"
441
+ };
442
+ return map[priority] || "Medium";
443
+ }
444
+ /**
445
+ * Map SpecWeave type to JIRA issue type
446
+ *
447
+ * Supports: Epic, Story, Bug, Task
448
+ */
449
+ mapTypeToJira(type, defaultType = "Story") {
450
+ if (!type) return defaultType;
451
+ const normalizedType = type.toLowerCase();
452
+ const map = {
453
+ bug: "Bug",
454
+ feature: "Epic",
455
+ epic: "Epic",
456
+ story: "Story",
457
+ task: "Task",
458
+ enhancement: "Story"
459
+ };
460
+ return map[normalizedType] || defaultType;
461
+ }
414
462
  }
415
463
  export {
416
464
  JiraSpecSync
@@ -223,7 +223,19 @@ export class JiraSpecSync {
223
223
  const epicSummary = `[${spec.metadata.id.toUpperCase()}] ${spec.metadata.title}`;
224
224
  const epicDescription = this.generateEpicDescription(spec);
225
225
 
226
- const payload = {
226
+ // Determine issue type based on spec type (supports Bug for bug-type specs)
227
+ const issueType = this.mapTypeToJira(spec.metadata.type, 'Epic');
228
+
229
+ const payload: {
230
+ fields: {
231
+ project: { key: string };
232
+ summary: string;
233
+ description: string;
234
+ issuetype: { name: string };
235
+ labels: string[];
236
+ priority?: { name: string };
237
+ };
238
+ } = {
227
239
  fields: {
228
240
  project: {
229
241
  key: this.config.projectKey
@@ -231,9 +243,13 @@ export class JiraSpecSync {
231
243
  summary: epicSummary,
232
244
  description: epicDescription,
233
245
  issuetype: {
234
- name: 'Epic'
246
+ name: issueType
235
247
  },
236
- labels: [`spec:${spec.metadata.id}`, `priority:${spec.metadata.priority}`]
248
+ labels: [`spec:${spec.metadata.id}`, `priority:${spec.metadata.priority}`],
249
+ // Set native JIRA priority field (P0→Highest, P1→High, P2→Medium, P3→Low)
250
+ priority: {
251
+ name: this.mapPriorityToJira(spec.metadata.priority)
252
+ }
237
253
  }
238
254
  };
239
255
 
@@ -329,7 +345,8 @@ export class JiraSpecSync {
329
345
  summary: storySummary,
330
346
  description: storyDescription,
331
347
  epicLink: epicKey,
332
- labels: [`user-story`, `spec:${spec.metadata.id}`, `priority:${us.priority}`]
348
+ labels: [`user-story`, `spec:${spec.metadata.id}`, `priority:${us.priority}`],
349
+ priority: us.priority
333
350
  });
334
351
 
335
352
  created.push(us.id);
@@ -497,8 +514,23 @@ ${acList}
497
514
  description: string;
498
515
  epicLink: string;
499
516
  labels: string[];
517
+ priority?: string;
518
+ type?: string;
500
519
  }): Promise<JiraStory> {
501
- const payload = {
520
+ // Determine issue type (supports Bug for bug-type stories)
521
+ const issueType = this.mapTypeToJira(story.type, 'Story');
522
+
523
+ const payload: {
524
+ fields: {
525
+ project: { key: string };
526
+ summary: string;
527
+ description: string;
528
+ issuetype: { name: string };
529
+ labels: string[];
530
+ customfield_10014: string;
531
+ priority?: { name: string };
532
+ };
533
+ } = {
502
534
  fields: {
503
535
  project: {
504
536
  key: this.config.projectKey
@@ -506,11 +538,15 @@ ${acList}
506
538
  summary: story.summary,
507
539
  description: story.description,
508
540
  issuetype: {
509
- name: 'Story'
541
+ name: issueType
510
542
  },
511
543
  labels: story.labels,
512
544
  // Link to epic (field name may vary by Jira configuration)
513
- customfield_10014: story.epicLink // Epic Link field (adjust if needed)
545
+ customfield_10014: story.epicLink, // Epic Link field (adjust if needed)
546
+ // Set native JIRA priority field
547
+ priority: {
548
+ name: this.mapPriorityToJira(story.priority)
549
+ }
514
550
  }
515
551
  };
516
552
 
@@ -579,4 +615,48 @@ ${acList}
579
615
  }
580
616
  });
581
617
  }
618
+
619
+ /**
620
+ * Map SpecWeave priority to JIRA priority name
621
+ *
622
+ * JIRA standard priority names: Highest, High, Medium, Low, Lowest
623
+ */
624
+ private mapPriorityToJira(priority?: string): string {
625
+ if (!priority) return 'Medium';
626
+
627
+ const map: Record<string, string> = {
628
+ P0: 'Highest',
629
+ P1: 'High',
630
+ P2: 'Medium',
631
+ P3: 'Low',
632
+ p0: 'Highest',
633
+ p1: 'High',
634
+ p2: 'Medium',
635
+ p3: 'Low'
636
+ };
637
+
638
+ return map[priority] || 'Medium';
639
+ }
640
+
641
+ /**
642
+ * Map SpecWeave type to JIRA issue type
643
+ *
644
+ * Supports: Epic, Story, Bug, Task
645
+ */
646
+ private mapTypeToJira(type?: string, defaultType: string = 'Story'): string {
647
+ if (!type) return defaultType;
648
+
649
+ const normalizedType = type.toLowerCase();
650
+
651
+ const map: Record<string, string> = {
652
+ bug: 'Bug',
653
+ feature: 'Epic',
654
+ epic: 'Epic',
655
+ story: 'Story',
656
+ task: 'Task',
657
+ enhancement: 'Story'
658
+ };
659
+
660
+ return map[normalizedType] || defaultType;
661
+ }
582
662
  }
@@ -33,25 +33,31 @@ class JiraStatusSync {
33
33
  * JIRA requires using transitions to change status.
34
34
  * Cannot directly set status field.
35
35
  *
36
+ * Handles missing transitions gracefully by logging a warning
37
+ * instead of throwing an error.
38
+ *
36
39
  * @param issueKey - JIRA issue key (e.g., PROJ-123)
37
40
  * @param status - Desired status
41
+ * @returns true if transition succeeded, false if not available
38
42
  */
39
43
  async updateStatus(issueKey, status) {
40
44
  const transitionsResponse = await this.client.get(`/issue/${issueKey}/transitions`);
41
45
  const transitions = transitionsResponse.data.transitions;
42
46
  const targetTransition = transitions.find(
43
- (t) => t.to.name === status.state
47
+ (t) => t.to.name.toLowerCase() === status.state.toLowerCase()
44
48
  );
45
49
  if (!targetTransition) {
46
- throw new Error(
47
- `Transition to ${status.state} not available for ${issueKey}. Available transitions: ${transitions.map((t) => t.to.name).join(", ")}`
50
+ console.warn(
51
+ `\u26A0\uFE0F Cannot transition ${issueKey} to "${status.state}". Available transitions: ${transitions.map((t) => t.to.name).join(", ")}. This may be expected if your JIRA workflow doesn't support this status.`
48
52
  );
53
+ return false;
49
54
  }
50
55
  await this.client.post(`/issue/${issueKey}/transitions`, {
51
56
  transition: {
52
57
  id: targetTransition.id
53
58
  }
54
59
  });
60
+ return true;
55
61
  }
56
62
  /**
57
63
  * Post comment about status change to JIRA issue
@@ -85,24 +85,31 @@ export class JiraStatusSync {
85
85
  * JIRA requires using transitions to change status.
86
86
  * Cannot directly set status field.
87
87
  *
88
+ * Handles missing transitions gracefully by logging a warning
89
+ * instead of throwing an error.
90
+ *
88
91
  * @param issueKey - JIRA issue key (e.g., PROJ-123)
89
92
  * @param status - Desired status
93
+ * @returns true if transition succeeded, false if not available
90
94
  */
91
- async updateStatus(issueKey: string, status: ExternalStatus): Promise<void> {
95
+ async updateStatus(issueKey: string, status: ExternalStatus): Promise<boolean> {
92
96
  // 1. Get available transitions for this issue
93
97
  const transitionsResponse = await this.client.get(`/issue/${issueKey}/transitions`);
94
98
  const transitions: JiraTransition[] = transitionsResponse.data.transitions;
95
99
 
96
- // 2. Find transition that leads to desired status
100
+ // 2. Find transition that leads to desired status (case-insensitive)
97
101
  const targetTransition = transitions.find(
98
- (t) => t.to.name === status.state
102
+ (t) => t.to.name.toLowerCase() === status.state.toLowerCase()
99
103
  );
100
104
 
101
105
  if (!targetTransition) {
102
- throw new Error(
103
- `Transition to ${status.state} not available for ${issueKey}. ` +
104
- `Available transitions: ${transitions.map(t => t.to.name).join(', ')}`
106
+ // Log warning instead of throwing - workflow may not support this transition
107
+ console.warn(
108
+ `⚠️ Cannot transition ${issueKey} to "${status.state}". ` +
109
+ `Available transitions: ${transitions.map(t => t.to.name).join(', ')}. ` +
110
+ `This may be expected if your JIRA workflow doesn't support this status.`
105
111
  );
112
+ return false;
106
113
  }
107
114
 
108
115
  // 3. Execute transition
@@ -111,6 +118,8 @@ export class JiraStatusSync {
111
118
  id: targetTransition.id
112
119
  }
113
120
  });
121
+
122
+ return true;
114
123
  }
115
124
 
116
125
  /**
@@ -1207,3 +1207,12 @@
1207
1207
  [2025-11-26 08:52:36] 🎯 Post-Increment-Completion Hook Triggered
1208
1208
  [2025-11-26 08:52:36] ⚠️ DORA calculator not found at /Users/antonabyzov/Projects/github/specweave/plugins/specweave-release/hooks/dist/src/metrics/dora-calculator.js
1209
1209
  [2025-11-26 08:52:36] Run: npm run build
1210
+ [2025-11-26 12:29:21] 🎯 Post-Increment-Completion Hook Triggered
1211
+ [2025-11-26 12:29:21] ⚠️ DORA calculator not found at /Users/antonabyzov/Projects/github/specweave/plugins/specweave-release/hooks/dist/src/metrics/dora-calculator.js
1212
+ [2025-11-26 12:29:21] Run: npm run build
1213
+ [2025-11-26 12:29:21] 🎯 Post-Increment-Completion Hook Triggered
1214
+ [2025-11-26 12:29:21] ⚠️ DORA calculator not found at /Users/antonabyzov/Projects/github/specweave/plugins/specweave-release/hooks/dist/src/metrics/dora-calculator.js
1215
+ [2025-11-26 12:29:21] Run: npm run build
1216
+ [2025-11-26 12:29:21] 🎯 Post-Increment-Completion Hook Triggered
1217
+ [2025-11-26 12:29:21] ⚠️ DORA calculator not found at /Users/antonabyzov/Projects/github/specweave/plugins/specweave-release/hooks/dist/src/metrics/dora-calculator.js
1218
+ [2025-11-26 12:29:21] Run: npm run build