specweave 0.7.1 → 0.8.2

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 (137) hide show
  1. package/CLAUDE.md +307 -11
  2. package/README.md +41 -3
  3. package/bin/specweave.js +0 -27
  4. package/dist/cli/commands/import-docs.d.ts +21 -0
  5. package/dist/cli/commands/import-docs.d.ts.map +1 -0
  6. package/dist/cli/commands/import-docs.js +146 -0
  7. package/dist/cli/commands/import-docs.js.map +1 -0
  8. package/dist/cli/commands/init-multiproject.d.ts +11 -0
  9. package/dist/cli/commands/init-multiproject.d.ts.map +1 -0
  10. package/dist/cli/commands/init-multiproject.js +202 -0
  11. package/dist/cli/commands/init-multiproject.js.map +1 -0
  12. package/dist/cli/commands/init.d.ts.map +1 -1
  13. package/dist/cli/commands/init.js +4 -93
  14. package/dist/cli/commands/init.js.map +1 -1
  15. package/dist/cli/commands/migrate-to-multiproject.d.ts +37 -0
  16. package/dist/cli/commands/migrate-to-multiproject.d.ts.map +1 -0
  17. package/dist/cli/commands/migrate-to-multiproject.js +189 -0
  18. package/dist/cli/commands/migrate-to-multiproject.js.map +1 -0
  19. package/dist/cli/commands/migrate-to-profiles.d.ts +25 -0
  20. package/dist/cli/commands/migrate-to-profiles.d.ts.map +1 -0
  21. package/dist/cli/commands/migrate-to-profiles.js +350 -0
  22. package/dist/cli/commands/migrate-to-profiles.js.map +1 -0
  23. package/dist/cli/commands/switch-project.d.ts +13 -0
  24. package/dist/cli/commands/switch-project.d.ts.map +1 -0
  25. package/dist/cli/commands/switch-project.js +91 -0
  26. package/dist/cli/commands/switch-project.js.map +1 -0
  27. package/dist/cli/helpers/issue-tracker/index.js +4 -4
  28. package/dist/cli/helpers/issue-tracker/index.js.map +1 -1
  29. package/dist/cli/helpers/issue-tracker/utils.d.ts +6 -3
  30. package/dist/cli/helpers/issue-tracker/utils.d.ts.map +1 -1
  31. package/dist/cli/helpers/issue-tracker/utils.js +9 -7
  32. package/dist/cli/helpers/issue-tracker/utils.js.map +1 -1
  33. package/dist/core/brownfield/analyzer.d.ts +86 -0
  34. package/dist/core/brownfield/analyzer.d.ts.map +1 -0
  35. package/dist/core/brownfield/analyzer.js +365 -0
  36. package/dist/core/brownfield/analyzer.js.map +1 -0
  37. package/dist/core/brownfield/importer.d.ts +76 -0
  38. package/dist/core/brownfield/importer.d.ts.map +1 -0
  39. package/dist/core/brownfield/importer.js +287 -0
  40. package/dist/core/brownfield/importer.js.map +1 -0
  41. package/dist/core/config-manager.d.ts +47 -0
  42. package/dist/core/config-manager.d.ts.map +1 -0
  43. package/dist/core/config-manager.js +136 -0
  44. package/dist/core/config-manager.js.map +1 -0
  45. package/dist/core/project-manager.d.ts +127 -0
  46. package/dist/core/project-manager.d.ts.map +1 -0
  47. package/dist/core/project-manager.js +524 -0
  48. package/dist/core/project-manager.js.map +1 -0
  49. package/dist/core/sync/profile-manager.d.ts +72 -0
  50. package/dist/core/sync/profile-manager.d.ts.map +1 -0
  51. package/dist/core/sync/profile-manager.js +338 -0
  52. package/dist/core/sync/profile-manager.js.map +1 -0
  53. package/dist/core/sync/profile-selector.d.ts +52 -0
  54. package/dist/core/sync/profile-selector.d.ts.map +1 -0
  55. package/dist/core/sync/profile-selector.js +179 -0
  56. package/dist/core/sync/profile-selector.js.map +1 -0
  57. package/dist/core/sync/project-context.d.ts +81 -0
  58. package/dist/core/sync/project-context.d.ts.map +1 -0
  59. package/dist/core/sync/project-context.js +354 -0
  60. package/dist/core/sync/project-context.js.map +1 -0
  61. package/dist/core/sync/rate-limiter.d.ts +116 -0
  62. package/dist/core/sync/rate-limiter.d.ts.map +1 -0
  63. package/dist/core/sync/rate-limiter.js +308 -0
  64. package/dist/core/sync/rate-limiter.js.map +1 -0
  65. package/dist/core/sync/time-range-selector.d.ts +48 -0
  66. package/dist/core/sync/time-range-selector.d.ts.map +1 -0
  67. package/dist/core/sync/time-range-selector.js +224 -0
  68. package/dist/core/sync/time-range-selector.js.map +1 -0
  69. package/dist/core/types/config.d.ts +4 -0
  70. package/dist/core/types/config.d.ts.map +1 -1
  71. package/dist/core/types/config.js.map +1 -1
  72. package/dist/core/types/sync-profile.d.ts +205 -0
  73. package/dist/core/types/sync-profile.d.ts.map +1 -0
  74. package/dist/core/types/sync-profile.js +8 -0
  75. package/dist/core/types/sync-profile.js.map +1 -0
  76. package/dist/utils/project-detection.d.ts +141 -0
  77. package/dist/utils/project-detection.d.ts.map +1 -0
  78. package/dist/utils/project-detection.js +321 -0
  79. package/dist/utils/project-detection.js.map +1 -0
  80. package/package.json +2 -1
  81. package/plugins/specweave/agents/pm/AGENT.md +7 -4
  82. package/plugins/specweave/commands/specweave-abandon.md +17 -17
  83. package/plugins/specweave/commands/specweave-check-tests.md +14 -14
  84. package/plugins/specweave/commands/specweave-costs.md +1 -1
  85. package/plugins/specweave/commands/specweave-do.md +12 -12
  86. package/plugins/specweave/commands/specweave-done.md +28 -15
  87. package/plugins/specweave/commands/specweave-import-docs.md +212 -0
  88. package/plugins/specweave/commands/specweave-increment.md +10 -10
  89. package/plugins/specweave/commands/specweave-init-multiproject.md +146 -0
  90. package/plugins/specweave/commands/specweave-next.md +16 -16
  91. package/plugins/specweave/commands/specweave-pause.md +17 -17
  92. package/plugins/specweave/commands/specweave-progress.md +10 -10
  93. package/plugins/specweave/commands/specweave-qa.md +11 -11
  94. package/plugins/specweave/commands/specweave-resume.md +22 -22
  95. package/plugins/specweave/commands/specweave-status.md +18 -18
  96. package/plugins/specweave/commands/specweave-switch-project.md +168 -0
  97. package/plugins/specweave/commands/specweave-sync-docs.md +1 -1
  98. package/plugins/specweave/commands/specweave-sync-tasks.md +9 -9
  99. package/plugins/specweave/commands/specweave-tdd-cycle.md +7 -0
  100. package/plugins/specweave/commands/specweave-tdd-green.md +7 -0
  101. package/plugins/specweave/commands/specweave-tdd-red.md +7 -0
  102. package/plugins/specweave/commands/specweave-tdd-refactor.md +7 -0
  103. package/plugins/specweave/commands/specweave-translate.md +1 -1
  104. package/plugins/specweave/commands/specweave-update-scope.md +8 -8
  105. package/plugins/specweave/commands/specweave-validate.md +18 -20
  106. package/plugins/specweave/commands/specweave.md +5 -5
  107. package/plugins/specweave/skills/SKILLS-INDEX.md +1 -1
  108. package/plugins/specweave/skills/increment-planner/SKILL.md +40 -4
  109. package/plugins/specweave/skills/increment-quality-judge/SKILL.md +5 -5
  110. package/plugins/specweave/skills/increment-quality-judge-v2/SKILL.md +5 -5
  111. package/plugins/specweave/skills/specweave-detector/SKILL.md +3 -3
  112. package/plugins/specweave-ado/commands/{close-workitem.md → specweave-ado-close-workitem.md} +1 -1
  113. package/plugins/specweave-ado/commands/{create-workitem.md → specweave-ado-create-workitem.md} +1 -1
  114. package/plugins/specweave-ado/commands/{status.md → specweave-ado-status.md} +1 -1
  115. package/plugins/specweave-ado/commands/{sync.md → specweave-ado-sync.md} +1 -1
  116. package/plugins/specweave-ado/lib/ado-client-v2.ts +547 -0
  117. package/plugins/specweave-github/commands/{close-issue.md → specweave-github-close-issue.md} +1 -1
  118. package/plugins/specweave-github/commands/{create-issue.md → specweave-github-create-issue.md} +1 -1
  119. package/plugins/specweave-github/commands/{status.md → specweave-github-status.md} +1 -1
  120. package/plugins/specweave-github/commands/{sync-tasks.md → specweave-github-sync-tasks.md} +1 -1
  121. package/plugins/specweave-github/commands/specweave-github-sync.md +568 -0
  122. package/plugins/specweave-github/lib/github-client-v2.ts +555 -0
  123. package/plugins/specweave-infrastructure/commands/{monitor-setup.md → specweave-infrastructure-monitor-setup.md} +1 -1
  124. package/plugins/specweave-infrastructure/commands/{slo-implement.md → specweave-infrastructure-slo-implement.md} +1 -1
  125. package/plugins/specweave-jira/commands/{sync.md → specweave-jira-sync.md} +1 -1
  126. package/plugins/specweave-jira/lib/jira-client-v2.ts +529 -0
  127. package/plugins/specweave-ml/commands/{ml-deploy.md → specweave-ml-deploy.md} +1 -1
  128. package/plugins/specweave-ml/commands/{ml-evaluate.md → specweave-ml-evaluate.md} +1 -1
  129. package/plugins/specweave-ml/commands/{ml-explain.md → specweave-ml-explain.md} +1 -1
  130. package/plugins/specweave-ml/commands/{ml-pipeline.md → specweave-ml-pipeline.md} +1 -1
  131. package/src/templates/AGENTS.md.template +1 -0
  132. package/src/templates/CLAUDE.md.template +1 -0
  133. package/plugins/specweave/hooks/post-increment-plugin-detect.sh +0 -142
  134. package/plugins/specweave/hooks/pre-task-plugin-detect.sh +0 -96
  135. package/plugins/specweave/skills/plugin-detector/SKILL.md +0 -324
  136. package/plugins/specweave-github/commands/sync.md +0 -443
  137. /package/plugins/specweave/{commands/README.md → COMMANDS.md} +0 -0
@@ -0,0 +1,529 @@
1
+ /**
2
+ * JIRA REST API Client (Multi-Project Support)
3
+ *
4
+ * Profile-based JIRA client for SpecWeave that supports:
5
+ * - Multiple JIRA projects via sync profiles
6
+ * - Time range filtering with JQL queries
7
+ * - Rate limiting protection
8
+ * - Secure HTTPS requests (no shell injection)
9
+ */
10
+
11
+ import https from 'https';
12
+ import {
13
+ SyncProfile,
14
+ JiraConfig,
15
+ TimeRangePreset,
16
+ } from '../../../src/core/types/sync-profile';
17
+
18
+ // ============================================================================
19
+ // Types
20
+ // ============================================================================
21
+
22
+ export interface JiraIssue {
23
+ id: string;
24
+ key: string;
25
+ self: string;
26
+ fields: {
27
+ summary: string;
28
+ description?: string;
29
+ status: {
30
+ name: string;
31
+ statusCategory: { key: string };
32
+ };
33
+ issuetype: {
34
+ name: string;
35
+ hierarchyLevel?: number;
36
+ };
37
+ created: string;
38
+ updated: string;
39
+ assignee?: { displayName: string };
40
+ reporter?: { displayName: string };
41
+ labels?: string[];
42
+ [key: string]: any;
43
+ };
44
+ }
45
+
46
+ export interface JiraSearchResult {
47
+ issues: JiraIssue[];
48
+ total: number;
49
+ maxResults: number;
50
+ startAt: number;
51
+ }
52
+
53
+ export interface CreateIssueRequest {
54
+ summary: string;
55
+ description?: string;
56
+ issueType?: string;
57
+ labels?: string[];
58
+ epicLink?: string; // Link to epic (for stories/tasks)
59
+ }
60
+
61
+ export interface UpdateIssueRequest {
62
+ summary?: string;
63
+ description?: string;
64
+ status?: string;
65
+ labels?: string[];
66
+ }
67
+
68
+ // ============================================================================
69
+ // JIRA Client V2
70
+ // ============================================================================
71
+
72
+ export class JiraClientV2 {
73
+ private domain: string;
74
+ private projectKey: string;
75
+ private issueType: string;
76
+ private baseUrl: string;
77
+ private authHeader: string;
78
+
79
+ /**
80
+ * Create JIRA client from sync profile
81
+ */
82
+ constructor(profile: SyncProfile, apiToken: string, email: string) {
83
+ if (profile.provider !== 'jira') {
84
+ throw new Error(`Expected JIRA profile, got ${profile.provider}`);
85
+ }
86
+
87
+ const config = profile.config as JiraConfig;
88
+ this.domain = config.domain;
89
+ this.projectKey = config.projectKey;
90
+ this.issueType = config.issueType || 'Epic';
91
+
92
+ this.baseUrl = `https://${this.domain}/rest/api/3`;
93
+
94
+ // Basic Auth: base64(email:api_token)
95
+ const credentials = `${email}:${apiToken}`;
96
+ this.authHeader =
97
+ 'Basic ' + Buffer.from(credentials).toString('base64');
98
+ }
99
+
100
+ /**
101
+ * Create client from domain/project directly
102
+ */
103
+ static fromProject(
104
+ domain: string,
105
+ projectKey: string,
106
+ apiToken: string,
107
+ email: string,
108
+ issueType: string = 'Epic'
109
+ ): JiraClientV2 {
110
+ const profile: SyncProfile = {
111
+ provider: 'jira',
112
+ displayName: `${domain}/${projectKey}`,
113
+ config: { domain, projectKey, issueType },
114
+ timeRange: { default: '1M', max: '6M' },
115
+ };
116
+ return new JiraClientV2(profile, apiToken, email);
117
+ }
118
+
119
+ // ==========================================================================
120
+ // Authentication & Setup
121
+ // ==========================================================================
122
+
123
+ /**
124
+ * Test connection and authentication
125
+ */
126
+ async testConnection(): Promise<{ success: boolean; error?: string }> {
127
+ try {
128
+ await this.request('GET', '/myself');
129
+ return { success: true };
130
+ } catch (error: any) {
131
+ return { success: false, error: error.message };
132
+ }
133
+ }
134
+
135
+ // ==========================================================================
136
+ // Issues
137
+ // ==========================================================================
138
+
139
+ /**
140
+ * Create epic issue
141
+ */
142
+ async createEpic(request: CreateIssueRequest): Promise<JiraIssue> {
143
+ const payload = {
144
+ fields: {
145
+ project: { key: this.projectKey },
146
+ summary: request.summary,
147
+ description: this.formatDescription(request.description),
148
+ issuetype: { name: this.issueType },
149
+ labels: request.labels || [],
150
+ },
151
+ };
152
+
153
+ const response = await this.request('POST', '/issue', payload);
154
+ return await this.getIssue(response.key);
155
+ }
156
+
157
+ /**
158
+ * Create story/task linked to epic
159
+ */
160
+ async createStory(
161
+ request: CreateIssueRequest,
162
+ epicKey: string
163
+ ): Promise<JiraIssue> {
164
+ const payload = {
165
+ fields: {
166
+ project: { key: this.projectKey },
167
+ summary: request.summary,
168
+ description: this.formatDescription(request.description),
169
+ issuetype: { name: request.issueType || 'Story' },
170
+ labels: request.labels || [],
171
+ // Epic link (JIRA Cloud uses 'parent' field for epics)
172
+ parent: { key: epicKey },
173
+ },
174
+ };
175
+
176
+ const response = await this.request('POST', '/issue', payload);
177
+ return await this.getIssue(response.key);
178
+ }
179
+
180
+ /**
181
+ * Get issue by key
182
+ */
183
+ async getIssue(issueKey: string): Promise<JiraIssue> {
184
+ return await this.request('GET', `/issue/${issueKey}`);
185
+ }
186
+
187
+ /**
188
+ * Update issue
189
+ */
190
+ async updateIssue(
191
+ issueKey: string,
192
+ updates: UpdateIssueRequest
193
+ ): Promise<void> {
194
+ const payload: any = { fields: {} };
195
+
196
+ if (updates.summary) {
197
+ payload.fields.summary = updates.summary;
198
+ }
199
+
200
+ if (updates.description !== undefined) {
201
+ payload.fields.description = this.formatDescription(updates.description);
202
+ }
203
+
204
+ if (updates.labels) {
205
+ payload.fields.labels = updates.labels;
206
+ }
207
+
208
+ await this.request('PUT', `/issue/${issueKey}`, payload);
209
+
210
+ // Status transition (if specified)
211
+ if (updates.status) {
212
+ await this.transitionIssue(issueKey, updates.status);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Transition issue to a different status
218
+ */
219
+ async transitionIssue(issueKey: string, statusName: string): Promise<void> {
220
+ // Get available transitions
221
+ const transitions = await this.request(
222
+ 'GET',
223
+ `/issue/${issueKey}/transitions`
224
+ );
225
+
226
+ // Find transition matching status name
227
+ const transition = transitions.transitions.find(
228
+ (t: any) => t.to.name.toLowerCase() === statusName.toLowerCase()
229
+ );
230
+
231
+ if (!transition) {
232
+ throw new Error(
233
+ `Status '${statusName}' not found for issue ${issueKey}. Available: ${transitions.transitions.map((t: any) => t.to.name).join(', ')}`
234
+ );
235
+ }
236
+
237
+ // Execute transition
238
+ await this.request('POST', `/issue/${issueKey}/transitions`, {
239
+ transition: { id: transition.id },
240
+ });
241
+ }
242
+
243
+ /**
244
+ * Add comment to issue
245
+ */
246
+ async addComment(issueKey: string, comment: string): Promise<void> {
247
+ await this.request('POST', `/issue/${issueKey}/comment`, {
248
+ body: this.formatDescription(comment),
249
+ });
250
+ }
251
+
252
+ /**
253
+ * Add labels to issue
254
+ */
255
+ async addLabels(issueKey: string, labels: string[]): Promise<void> {
256
+ if (labels.length === 0) return;
257
+
258
+ const issue = await this.getIssue(issueKey);
259
+ const existingLabels = issue.fields.labels || [];
260
+ const newLabels = [...new Set([...existingLabels, ...labels])];
261
+
262
+ await this.updateIssue(issueKey, { labels: newLabels });
263
+ }
264
+
265
+ // ==========================================================================
266
+ // Search & Time Range Filtering
267
+ // ==========================================================================
268
+
269
+ /**
270
+ * Search issues using JQL
271
+ */
272
+ async searchIssues(
273
+ jql: string,
274
+ options: {
275
+ startAt?: number;
276
+ maxResults?: number;
277
+ fields?: string[];
278
+ } = {}
279
+ ): Promise<JiraSearchResult> {
280
+ const params = new URLSearchParams({
281
+ jql,
282
+ startAt: String(options.startAt || 0),
283
+ maxResults: String(options.maxResults || 50),
284
+ });
285
+
286
+ if (options.fields) {
287
+ params.append('fields', options.fields.join(','));
288
+ }
289
+
290
+ return await this.request('GET', `/search?${params.toString()}`);
291
+ }
292
+
293
+ /**
294
+ * List issues within time range
295
+ */
296
+ async listIssuesInTimeRange(
297
+ timeRange: TimeRangePreset,
298
+ customStart?: string,
299
+ customEnd?: string
300
+ ): Promise<JiraIssue[]> {
301
+ const { since, until } = this.calculateTimeRange(
302
+ timeRange,
303
+ customStart,
304
+ customEnd
305
+ );
306
+
307
+ // JQL query for time range
308
+ const jql = `project = ${this.projectKey} AND created >= "${since}" AND created <= "${until}" ORDER BY created DESC`;
309
+
310
+ const allIssues: JiraIssue[] = [];
311
+ let startAt = 0;
312
+ const maxResults = 100;
313
+
314
+ // Paginate through all results
315
+ while (true) {
316
+ const result = await this.searchIssues(jql, { startAt, maxResults });
317
+ allIssues.push(...result.issues);
318
+
319
+ if (allIssues.length >= result.total) {
320
+ break;
321
+ }
322
+
323
+ startAt += maxResults;
324
+ }
325
+
326
+ return allIssues;
327
+ }
328
+
329
+ /**
330
+ * Calculate date range from preset
331
+ */
332
+ private calculateTimeRange(
333
+ timeRange: TimeRangePreset,
334
+ customStart?: string,
335
+ customEnd?: string
336
+ ): { since: string; until: string } {
337
+ if (timeRange === 'ALL') {
338
+ return {
339
+ since: '1970-01-01',
340
+ until: new Date().toISOString().split('T')[0],
341
+ };
342
+ }
343
+
344
+ if (customStart) {
345
+ return {
346
+ since: customStart,
347
+ until: customEnd || new Date().toISOString().split('T')[0],
348
+ };
349
+ }
350
+
351
+ const now = new Date();
352
+ const since = new Date(now);
353
+
354
+ switch (timeRange) {
355
+ case '1W':
356
+ since.setDate(now.getDate() - 7);
357
+ break;
358
+ case '2W':
359
+ since.setDate(now.getDate() - 14);
360
+ break;
361
+ case '1M':
362
+ since.setMonth(now.getMonth() - 1);
363
+ break;
364
+ case '3M':
365
+ since.setMonth(now.getMonth() - 3);
366
+ break;
367
+ case '6M':
368
+ since.setMonth(now.getMonth() - 6);
369
+ break;
370
+ case '1Y':
371
+ since.setFullYear(now.getFullYear() - 1);
372
+ break;
373
+ }
374
+
375
+ return {
376
+ since: since.toISOString().split('T')[0],
377
+ until: now.toISOString().split('T')[0],
378
+ };
379
+ }
380
+
381
+ // ==========================================================================
382
+ // Batch Operations
383
+ // ==========================================================================
384
+
385
+ /**
386
+ * Batch create issues with rate limit handling
387
+ */
388
+ async batchCreateIssues(
389
+ issues: CreateIssueRequest[],
390
+ epicKey?: string,
391
+ options: { batchSize?: number; delayMs?: number } = {}
392
+ ): Promise<JiraIssue[]> {
393
+ const { batchSize = 5, delayMs = 12000 } = options; // 5 issues per minute (JIRA: 100/min limit)
394
+ const createdIssues: JiraIssue[] = [];
395
+
396
+ for (let i = 0; i < issues.length; i += batchSize) {
397
+ const batch = issues.slice(i, i + batchSize);
398
+
399
+ console.log(
400
+ `Creating issues ${i + 1}-${Math.min(i + batchSize, issues.length)} of ${issues.length}...`
401
+ );
402
+
403
+ for (const issue of batch) {
404
+ try {
405
+ const created = epicKey
406
+ ? await this.createStory(issue, epicKey)
407
+ : await this.createEpic(issue);
408
+
409
+ createdIssues.push(created);
410
+ } catch (error: any) {
411
+ console.error(
412
+ `Failed to create issue "${issue.summary}":`,
413
+ error.message
414
+ );
415
+ }
416
+ }
417
+
418
+ // Delay between batches
419
+ if (i + batchSize < issues.length) {
420
+ console.log(`Waiting ${delayMs / 1000}s to avoid rate limits...`);
421
+ await this.sleep(delayMs);
422
+ }
423
+ }
424
+
425
+ return createdIssues;
426
+ }
427
+
428
+ // ==========================================================================
429
+ // HTTP Request Handler
430
+ // ==========================================================================
431
+
432
+ /**
433
+ * Make HTTPS request to JIRA API
434
+ */
435
+ private async request(
436
+ method: string,
437
+ path: string,
438
+ body?: any
439
+ ): Promise<any> {
440
+ return new Promise((resolve, reject) => {
441
+ const url = `${this.baseUrl}${path}`;
442
+ const { hostname, pathname, search } = new URL(url);
443
+
444
+ const options = {
445
+ hostname,
446
+ path: pathname + search,
447
+ method,
448
+ headers: {
449
+ Authorization: this.authHeader,
450
+ 'Content-Type': 'application/json',
451
+ Accept: 'application/json',
452
+ },
453
+ };
454
+
455
+ const req = https.request(options, (res) => {
456
+ let data = '';
457
+
458
+ res.on('data', (chunk) => {
459
+ data += chunk;
460
+ });
461
+
462
+ res.on('end', () => {
463
+ // Parse response
464
+ let parsed: any;
465
+ try {
466
+ parsed = data ? JSON.parse(data) : {};
467
+ } catch {
468
+ parsed = { raw: data };
469
+ }
470
+
471
+ // Check status code
472
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
473
+ resolve(parsed);
474
+ } else {
475
+ const errorMsg =
476
+ parsed.errorMessages?.join(', ') ||
477
+ parsed.message ||
478
+ `HTTP ${res.statusCode}: ${data}`;
479
+ reject(new Error(errorMsg));
480
+ }
481
+ });
482
+ });
483
+
484
+ req.on('error', (error) => {
485
+ reject(error);
486
+ });
487
+
488
+ // Send body if present
489
+ if (body) {
490
+ req.write(JSON.stringify(body));
491
+ }
492
+
493
+ req.end();
494
+ });
495
+ }
496
+
497
+ // ==========================================================================
498
+ // Helper Methods
499
+ // ==========================================================================
500
+
501
+ /**
502
+ * Format description for JIRA (Atlassian Document Format)
503
+ */
504
+ private formatDescription(text?: string): any {
505
+ if (!text) {
506
+ return undefined;
507
+ }
508
+
509
+ // Convert Markdown to Atlassian Document Format (ADF)
510
+ // For now, simple text paragraphs
511
+ return {
512
+ type: 'doc',
513
+ version: 1,
514
+ content: text.split('\n\n').map((paragraph) => ({
515
+ type: 'paragraph',
516
+ content: [
517
+ {
518
+ type: 'text',
519
+ text: paragraph,
520
+ },
521
+ ],
522
+ })),
523
+ };
524
+ }
525
+
526
+ private sleep(ms: number): Promise<void> {
527
+ return new Promise((resolve) => setTimeout(resolve, ms));
528
+ }
529
+ }
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ml-deploy
2
+ name: specweave-ml:ml-deploy
3
3
  description: Generate deployment artifacts (API, Docker, monitoring)
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ml-evaluate
2
+ name: specweave-ml:ml-evaluate
3
3
  description: Evaluate ML model with comprehensive metrics
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ml-explain
2
+ name: specweave-ml:ml-explain
3
3
  description: Generate model explainability reports (SHAP, LIME, feature importance)
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ml-pipeline
2
+ name: specweave-ml:ml-pipeline
3
3
  description: Design and implement a complete ML pipeline with multi-agent MLOps orchestration
4
4
  ---
5
5
 
@@ -36,6 +36,7 @@ This is a **SpecWeave project** where specifications and documentation are the s
36
36
  │ └── reports/ # Analysis, completion reports
37
37
  ├── docs/internal/
38
38
  │ ├── strategy/ # Business specs (WHAT, WHY)
39
+ │ ├── specs/ # Feature specifications (detailed requirements)
39
40
  │ ├── architecture/ # Technical design (HOW)
40
41
  │ ├── delivery/ # Roadmap, CI/CD, guides
41
42
  │ ├── operations/ # Runbooks, SLOs
@@ -231,6 +231,7 @@ Config: Auto-detected from project files
231
231
  │ ├── docs/ # Strategic documentation
232
232
  │ │ ├── internal/
233
233
  │ │ │ ├── strategy/ # Business specs (WHAT, WHY)
234
+ │ │ │ ├── specs/ # Feature specifications (detailed requirements)
234
235
  │ │ │ ├── architecture/ # Technical design (HOW)
235
236
  │ │ │ ├── delivery/ # Guides, roadmap, CI/CD
236
237
  │ │ │ ├── operations/ # Runbooks, monitoring