specweave 0.7.1 → 0.8.1

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 (132) hide show
  1. package/CLAUDE.md +307 -11
  2. package/README.md +41 -3
  3. package/dist/cli/commands/import-docs.d.ts +21 -0
  4. package/dist/cli/commands/import-docs.d.ts.map +1 -0
  5. package/dist/cli/commands/import-docs.js +146 -0
  6. package/dist/cli/commands/import-docs.js.map +1 -0
  7. package/dist/cli/commands/init-multiproject.d.ts +11 -0
  8. package/dist/cli/commands/init-multiproject.d.ts.map +1 -0
  9. package/dist/cli/commands/init-multiproject.js +202 -0
  10. package/dist/cli/commands/init-multiproject.js.map +1 -0
  11. package/dist/cli/commands/init.js +3 -3
  12. package/dist/cli/commands/init.js.map +1 -1
  13. package/dist/cli/commands/migrate-to-multiproject.d.ts +37 -0
  14. package/dist/cli/commands/migrate-to-multiproject.d.ts.map +1 -0
  15. package/dist/cli/commands/migrate-to-multiproject.js +189 -0
  16. package/dist/cli/commands/migrate-to-multiproject.js.map +1 -0
  17. package/dist/cli/commands/migrate-to-profiles.d.ts +25 -0
  18. package/dist/cli/commands/migrate-to-profiles.d.ts.map +1 -0
  19. package/dist/cli/commands/migrate-to-profiles.js +350 -0
  20. package/dist/cli/commands/migrate-to-profiles.js.map +1 -0
  21. package/dist/cli/commands/switch-project.d.ts +13 -0
  22. package/dist/cli/commands/switch-project.d.ts.map +1 -0
  23. package/dist/cli/commands/switch-project.js +91 -0
  24. package/dist/cli/commands/switch-project.js.map +1 -0
  25. package/dist/cli/helpers/issue-tracker/index.js +4 -4
  26. package/dist/cli/helpers/issue-tracker/index.js.map +1 -1
  27. package/dist/cli/helpers/issue-tracker/utils.d.ts +6 -3
  28. package/dist/cli/helpers/issue-tracker/utils.d.ts.map +1 -1
  29. package/dist/cli/helpers/issue-tracker/utils.js +9 -7
  30. package/dist/cli/helpers/issue-tracker/utils.js.map +1 -1
  31. package/dist/core/brownfield/analyzer.d.ts +86 -0
  32. package/dist/core/brownfield/analyzer.d.ts.map +1 -0
  33. package/dist/core/brownfield/analyzer.js +365 -0
  34. package/dist/core/brownfield/analyzer.js.map +1 -0
  35. package/dist/core/brownfield/importer.d.ts +76 -0
  36. package/dist/core/brownfield/importer.d.ts.map +1 -0
  37. package/dist/core/brownfield/importer.js +287 -0
  38. package/dist/core/brownfield/importer.js.map +1 -0
  39. package/dist/core/config-manager.d.ts +47 -0
  40. package/dist/core/config-manager.d.ts.map +1 -0
  41. package/dist/core/config-manager.js +136 -0
  42. package/dist/core/config-manager.js.map +1 -0
  43. package/dist/core/project-manager.d.ts +127 -0
  44. package/dist/core/project-manager.d.ts.map +1 -0
  45. package/dist/core/project-manager.js +524 -0
  46. package/dist/core/project-manager.js.map +1 -0
  47. package/dist/core/sync/profile-manager.d.ts +72 -0
  48. package/dist/core/sync/profile-manager.d.ts.map +1 -0
  49. package/dist/core/sync/profile-manager.js +338 -0
  50. package/dist/core/sync/profile-manager.js.map +1 -0
  51. package/dist/core/sync/profile-selector.d.ts +52 -0
  52. package/dist/core/sync/profile-selector.d.ts.map +1 -0
  53. package/dist/core/sync/profile-selector.js +179 -0
  54. package/dist/core/sync/profile-selector.js.map +1 -0
  55. package/dist/core/sync/project-context.d.ts +81 -0
  56. package/dist/core/sync/project-context.d.ts.map +1 -0
  57. package/dist/core/sync/project-context.js +354 -0
  58. package/dist/core/sync/project-context.js.map +1 -0
  59. package/dist/core/sync/rate-limiter.d.ts +116 -0
  60. package/dist/core/sync/rate-limiter.d.ts.map +1 -0
  61. package/dist/core/sync/rate-limiter.js +308 -0
  62. package/dist/core/sync/rate-limiter.js.map +1 -0
  63. package/dist/core/sync/time-range-selector.d.ts +48 -0
  64. package/dist/core/sync/time-range-selector.d.ts.map +1 -0
  65. package/dist/core/sync/time-range-selector.js +224 -0
  66. package/dist/core/sync/time-range-selector.js.map +1 -0
  67. package/dist/core/types/config.d.ts +4 -0
  68. package/dist/core/types/config.d.ts.map +1 -1
  69. package/dist/core/types/config.js.map +1 -1
  70. package/dist/core/types/sync-profile.d.ts +205 -0
  71. package/dist/core/types/sync-profile.d.ts.map +1 -0
  72. package/dist/core/types/sync-profile.js +8 -0
  73. package/dist/core/types/sync-profile.js.map +1 -0
  74. package/dist/utils/project-detection.d.ts +141 -0
  75. package/dist/utils/project-detection.d.ts.map +1 -0
  76. package/dist/utils/project-detection.js +321 -0
  77. package/dist/utils/project-detection.js.map +1 -0
  78. package/package.json +2 -1
  79. package/plugins/specweave/agents/pm/AGENT.md +7 -4
  80. package/plugins/specweave/commands/specweave-abandon.md +17 -17
  81. package/plugins/specweave/commands/specweave-check-tests.md +14 -14
  82. package/plugins/specweave/commands/specweave-costs.md +1 -1
  83. package/plugins/specweave/commands/specweave-do.md +12 -12
  84. package/plugins/specweave/commands/specweave-done.md +28 -15
  85. package/plugins/specweave/commands/specweave-import-docs.md +212 -0
  86. package/plugins/specweave/commands/specweave-increment.md +10 -10
  87. package/plugins/specweave/commands/specweave-init-multiproject.md +146 -0
  88. package/plugins/specweave/commands/specweave-next.md +16 -16
  89. package/plugins/specweave/commands/specweave-pause.md +17 -17
  90. package/plugins/specweave/commands/specweave-progress.md +10 -10
  91. package/plugins/specweave/commands/specweave-qa.md +11 -11
  92. package/plugins/specweave/commands/specweave-resume.md +22 -22
  93. package/plugins/specweave/commands/specweave-status.md +18 -18
  94. package/plugins/specweave/commands/specweave-switch-project.md +168 -0
  95. package/plugins/specweave/commands/specweave-sync-docs.md +1 -1
  96. package/plugins/specweave/commands/specweave-sync-tasks.md +9 -9
  97. package/plugins/specweave/commands/specweave-tdd-cycle.md +7 -0
  98. package/plugins/specweave/commands/specweave-tdd-green.md +7 -0
  99. package/plugins/specweave/commands/specweave-tdd-red.md +7 -0
  100. package/plugins/specweave/commands/specweave-tdd-refactor.md +7 -0
  101. package/plugins/specweave/commands/specweave-translate.md +1 -1
  102. package/plugins/specweave/commands/specweave-update-scope.md +8 -8
  103. package/plugins/specweave/commands/specweave-validate.md +18 -20
  104. package/plugins/specweave/commands/specweave.md +5 -5
  105. package/plugins/specweave/skills/SKILLS-INDEX.md +1 -1
  106. package/plugins/specweave/skills/increment-planner/SKILL.md +40 -4
  107. package/plugins/specweave/skills/increment-quality-judge/SKILL.md +5 -5
  108. package/plugins/specweave/skills/increment-quality-judge-v2/SKILL.md +5 -5
  109. package/plugins/specweave/skills/specweave-detector/SKILL.md +3 -3
  110. package/plugins/specweave-ado/commands/{close-workitem.md → specweave-ado-close-workitem.md} +1 -1
  111. package/plugins/specweave-ado/commands/{create-workitem.md → specweave-ado-create-workitem.md} +1 -1
  112. package/plugins/specweave-ado/commands/{status.md → specweave-ado-status.md} +1 -1
  113. package/plugins/specweave-ado/commands/{sync.md → specweave-ado-sync.md} +1 -1
  114. package/plugins/specweave-ado/lib/ado-client-v2.ts +547 -0
  115. package/plugins/specweave-github/commands/{close-issue.md → specweave-github-close-issue.md} +1 -1
  116. package/plugins/specweave-github/commands/{create-issue.md → specweave-github-create-issue.md} +1 -1
  117. package/plugins/specweave-github/commands/{status.md → specweave-github-status.md} +1 -1
  118. package/plugins/specweave-github/commands/{sync-tasks.md → specweave-github-sync-tasks.md} +1 -1
  119. package/plugins/specweave-github/commands/specweave-github-sync.md +568 -0
  120. package/plugins/specweave-github/lib/github-client-v2.ts +555 -0
  121. package/plugins/specweave-infrastructure/commands/{monitor-setup.md → specweave-infrastructure-monitor-setup.md} +1 -1
  122. package/plugins/specweave-infrastructure/commands/{slo-implement.md → specweave-infrastructure-slo-implement.md} +1 -1
  123. package/plugins/specweave-jira/commands/{sync.md → specweave-jira-sync.md} +1 -1
  124. package/plugins/specweave-jira/lib/jira-client-v2.ts +529 -0
  125. package/plugins/specweave-ml/commands/{ml-deploy.md → specweave-ml-deploy.md} +1 -1
  126. package/plugins/specweave-ml/commands/{ml-evaluate.md → specweave-ml-evaluate.md} +1 -1
  127. package/plugins/specweave-ml/commands/{ml-explain.md → specweave-ml-explain.md} +1 -1
  128. package/plugins/specweave-ml/commands/{ml-pipeline.md → specweave-ml-pipeline.md} +1 -1
  129. package/src/templates/AGENTS.md.template +1 -0
  130. package/src/templates/CLAUDE.md.template +1 -0
  131. package/plugins/specweave-github/commands/sync.md +0 -443
  132. /package/plugins/specweave/{commands/README.md → COMMANDS.md} +0 -0
@@ -0,0 +1,547 @@
1
+ /**
2
+ * Azure DevOps REST API Client (Multi-Project Support)
3
+ *
4
+ * Profile-based ADO client for SpecWeave that supports:
5
+ * - Multiple ADO projects via sync profiles
6
+ * - Time range filtering with WIQL queries
7
+ * - Rate limiting protection
8
+ * - Secure HTTPS requests
9
+ */
10
+
11
+ import https from 'https';
12
+ import {
13
+ SyncProfile,
14
+ AdoConfig,
15
+ TimeRangePreset,
16
+ } from '../../../src/core/types/sync-profile';
17
+
18
+ // ============================================================================
19
+ // Types
20
+ // ============================================================================
21
+
22
+ export interface WorkItem {
23
+ id: number;
24
+ rev: number;
25
+ fields: {
26
+ 'System.Title': string;
27
+ 'System.Description'?: string;
28
+ 'System.State': string;
29
+ 'System.CreatedDate': string;
30
+ 'System.ChangedDate': string;
31
+ 'System.AreaPath'?: string;
32
+ 'System.IterationPath'?: string;
33
+ 'System.Tags'?: string;
34
+ 'System.WorkItemType': string;
35
+ [key: string]: any;
36
+ };
37
+ _links: {
38
+ html: { href: string };
39
+ };
40
+ url: string;
41
+ }
42
+
43
+ export interface WorkItemQueryResult {
44
+ queryType: string;
45
+ queryResultType: string;
46
+ asOf: string;
47
+ workItems: Array<{ id: number; url: string }>;
48
+ }
49
+
50
+ export interface CreateWorkItemRequest {
51
+ title: string;
52
+ description?: string;
53
+ areaPath?: string;
54
+ iterationPath?: string;
55
+ tags?: string[];
56
+ parentId?: number; // Link to epic/feature
57
+ }
58
+
59
+ export interface UpdateWorkItemRequest {
60
+ state?: string;
61
+ title?: string;
62
+ description?: string;
63
+ tags?: string[];
64
+ }
65
+
66
+ // ============================================================================
67
+ // Azure DevOps Client V2
68
+ // ============================================================================
69
+
70
+ export class AdoClientV2 {
71
+ private organization: string;
72
+ private project: string;
73
+ private workItemType: string;
74
+ private areaPath?: string;
75
+ private iterationPath?: string;
76
+ private baseUrl: string;
77
+ private authHeader: string;
78
+
79
+ /**
80
+ * Create ADO client from sync profile
81
+ */
82
+ constructor(profile: SyncProfile, personalAccessToken: string) {
83
+ if (profile.provider !== 'ado') {
84
+ throw new Error(`Expected ADO profile, got ${profile.provider}`);
85
+ }
86
+
87
+ const config = profile.config as AdoConfig;
88
+ this.organization = config.organization;
89
+ this.project = config.project;
90
+ this.workItemType = config.workItemType || 'Epic';
91
+ this.areaPath = config.areaPath;
92
+ this.iterationPath = config.iterationPath;
93
+
94
+ this.baseUrl = `https://dev.azure.com/${this.organization}/${this.project}`;
95
+
96
+ // Basic Auth: base64(":PAT")
97
+ this.authHeader =
98
+ 'Basic ' + Buffer.from(`:${personalAccessToken}`).toString('base64');
99
+ }
100
+
101
+ /**
102
+ * Create client from organization/project directly
103
+ */
104
+ static fromProject(
105
+ organization: string,
106
+ project: string,
107
+ personalAccessToken: string,
108
+ workItemType: string = 'Epic'
109
+ ): AdoClientV2 {
110
+ const profile: SyncProfile = {
111
+ provider: 'ado',
112
+ displayName: `${organization}/${project}`,
113
+ config: { organization, project, workItemType },
114
+ timeRange: { default: '1M', max: '6M' },
115
+ };
116
+ return new AdoClientV2(profile, personalAccessToken);
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', `/_apis/projects/${this.project}?api-version=7.1`);
129
+ return { success: true };
130
+ } catch (error: any) {
131
+ return { success: false, error: error.message };
132
+ }
133
+ }
134
+
135
+ // ==========================================================================
136
+ // Work Items
137
+ // ==========================================================================
138
+
139
+ /**
140
+ * Create epic work item
141
+ */
142
+ async createEpic(request: CreateWorkItemRequest): Promise<WorkItem> {
143
+ const url = `/_apis/wit/workitems/$${this.workItemType}?api-version=7.1`;
144
+
145
+ // Build JSON Patch document
146
+ const operations: any[] = [
147
+ {
148
+ op: 'add',
149
+ path: '/fields/System.Title',
150
+ value: request.title,
151
+ },
152
+ ];
153
+
154
+ if (request.description) {
155
+ operations.push({
156
+ op: 'add',
157
+ path: '/fields/System.Description',
158
+ value: `<pre>${request.description}</pre>`,
159
+ });
160
+ }
161
+
162
+ if (request.areaPath || this.areaPath) {
163
+ operations.push({
164
+ op: 'add',
165
+ path: '/fields/System.AreaPath',
166
+ value: request.areaPath || this.areaPath,
167
+ });
168
+ }
169
+
170
+ if (request.iterationPath || this.iterationPath) {
171
+ operations.push({
172
+ op: 'add',
173
+ path: '/fields/System.IterationPath',
174
+ value: request.iterationPath || this.iterationPath,
175
+ });
176
+ }
177
+
178
+ if (request.tags && request.tags.length > 0) {
179
+ operations.push({
180
+ op: 'add',
181
+ path: '/fields/System.Tags',
182
+ value: request.tags.join('; '),
183
+ });
184
+ }
185
+
186
+ return this.request('POST', url, operations, {
187
+ 'Content-Type': 'application/json-patch+json',
188
+ });
189
+ }
190
+
191
+ /**
192
+ * Create child work item (feature/story) linked to epic
193
+ */
194
+ async createChildWorkItem(
195
+ request: CreateWorkItemRequest,
196
+ parentId: number,
197
+ childType: string = 'User Story'
198
+ ): Promise<WorkItem> {
199
+ const url = `/_apis/wit/workitems/$${childType}?api-version=7.1`;
200
+
201
+ const operations: any[] = [
202
+ {
203
+ op: 'add',
204
+ path: '/fields/System.Title',
205
+ value: request.title,
206
+ },
207
+ {
208
+ op: 'add',
209
+ path: '/relations/-',
210
+ value: {
211
+ rel: 'System.LinkTypes.Hierarchy-Reverse',
212
+ url: `${this.baseUrl}/_apis/wit/workItems/${parentId}`,
213
+ },
214
+ },
215
+ ];
216
+
217
+ if (request.description) {
218
+ operations.push({
219
+ op: 'add',
220
+ path: '/fields/System.Description',
221
+ value: `<pre>${request.description}</pre>`,
222
+ });
223
+ }
224
+
225
+ if (request.tags && request.tags.length > 0) {
226
+ operations.push({
227
+ op: 'add',
228
+ path: '/fields/System.Tags',
229
+ value: request.tags.join('; '),
230
+ });
231
+ }
232
+
233
+ return this.request('POST', url, operations, {
234
+ 'Content-Type': 'application/json-patch+json',
235
+ });
236
+ }
237
+
238
+ /**
239
+ * Get work item by ID
240
+ */
241
+ async getWorkItem(id: number): Promise<WorkItem> {
242
+ return this.request('GET', `/_apis/wit/workitems/${id}?api-version=7.1`);
243
+ }
244
+
245
+ /**
246
+ * Update work item
247
+ */
248
+ async updateWorkItem(
249
+ id: number,
250
+ updates: UpdateWorkItemRequest
251
+ ): Promise<WorkItem> {
252
+ const url = `/_apis/wit/workitems/${id}?api-version=7.1`;
253
+
254
+ const operations: any[] = [];
255
+
256
+ if (updates.state) {
257
+ operations.push({
258
+ op: 'add',
259
+ path: '/fields/System.State',
260
+ value: updates.state,
261
+ });
262
+ }
263
+
264
+ if (updates.title) {
265
+ operations.push({
266
+ op: 'add',
267
+ path: '/fields/System.Title',
268
+ value: updates.title,
269
+ });
270
+ }
271
+
272
+ if (updates.description) {
273
+ operations.push({
274
+ op: 'add',
275
+ path: '/fields/System.Description',
276
+ value: `<pre>${updates.description}</pre>`,
277
+ });
278
+ }
279
+
280
+ if (updates.tags) {
281
+ operations.push({
282
+ op: 'add',
283
+ path: '/fields/System.Tags',
284
+ value: updates.tags.join('; '),
285
+ });
286
+ }
287
+
288
+ return this.request('PATCH', url, operations, {
289
+ 'Content-Type': 'application/json-patch+json',
290
+ });
291
+ }
292
+
293
+ /**
294
+ * Add comment to work item
295
+ */
296
+ async addComment(workItemId: number, comment: string): Promise<void> {
297
+ const url = `/_apis/wit/workItems/${workItemId}/comments?api-version=7.1-preview.3`;
298
+
299
+ await this.request('POST', url, { text: comment });
300
+ }
301
+
302
+ // ==========================================================================
303
+ // Query & Time Range Filtering
304
+ // ==========================================================================
305
+
306
+ /**
307
+ * Execute WIQL query
308
+ */
309
+ async queryWorkItems(wiql: string): Promise<WorkItem[]> {
310
+ const url = `/_apis/wit/wiql?api-version=7.1`;
311
+
312
+ const queryResult: WorkItemQueryResult = await this.request('POST', url, {
313
+ query: wiql,
314
+ });
315
+
316
+ if (queryResult.workItems.length === 0) {
317
+ return [];
318
+ }
319
+
320
+ // Get full work item details (batch request)
321
+ const ids = queryResult.workItems.map((wi) => wi.id);
322
+ const batchUrl = `/_apis/wit/workitemsbatch?api-version=7.1`;
323
+
324
+ const workItems = await this.request('POST', batchUrl, {
325
+ ids,
326
+ fields: [
327
+ 'System.Id',
328
+ 'System.Title',
329
+ 'System.Description',
330
+ 'System.State',
331
+ 'System.CreatedDate',
332
+ 'System.ChangedDate',
333
+ 'System.WorkItemType',
334
+ 'System.Tags',
335
+ ],
336
+ });
337
+
338
+ return workItems.value || [];
339
+ }
340
+
341
+ /**
342
+ * List work items within time range
343
+ */
344
+ async listWorkItemsInTimeRange(
345
+ timeRange: TimeRangePreset,
346
+ customStart?: string,
347
+ customEnd?: string
348
+ ): Promise<WorkItem[]> {
349
+ const { since, until } = this.calculateTimeRange(
350
+ timeRange,
351
+ customStart,
352
+ customEnd
353
+ );
354
+
355
+ // WIQL query for time range
356
+ const wiql = `
357
+ SELECT [System.Id], [System.Title], [System.State], [System.CreatedDate]
358
+ FROM WorkItems
359
+ WHERE [System.TeamProject] = '${this.project}'
360
+ AND [System.CreatedDate] >= '${since}'
361
+ AND [System.CreatedDate] <= '${until}'
362
+ ORDER BY [System.CreatedDate] DESC
363
+ `;
364
+
365
+ return this.queryWorkItems(wiql);
366
+ }
367
+
368
+ /**
369
+ * Calculate date range from preset
370
+ */
371
+ private calculateTimeRange(
372
+ timeRange: TimeRangePreset,
373
+ customStart?: string,
374
+ customEnd?: string
375
+ ): { since: string; until: string } {
376
+ if (timeRange === 'ALL') {
377
+ return {
378
+ since: '1970-01-01T00:00:00Z',
379
+ until: new Date().toISOString(),
380
+ };
381
+ }
382
+
383
+ if (customStart) {
384
+ return {
385
+ since: customStart,
386
+ until: customEnd || new Date().toISOString(),
387
+ };
388
+ }
389
+
390
+ const now = new Date();
391
+ const since = new Date(now);
392
+
393
+ switch (timeRange) {
394
+ case '1W':
395
+ since.setDate(now.getDate() - 7);
396
+ break;
397
+ case '2W':
398
+ since.setDate(now.getDate() - 14);
399
+ break;
400
+ case '1M':
401
+ since.setMonth(now.getMonth() - 1);
402
+ break;
403
+ case '3M':
404
+ since.setMonth(now.getMonth() - 3);
405
+ break;
406
+ case '6M':
407
+ since.setMonth(now.getMonth() - 6);
408
+ break;
409
+ case '1Y':
410
+ since.setFullYear(now.getFullYear() - 1);
411
+ break;
412
+ }
413
+
414
+ return {
415
+ since: since.toISOString(),
416
+ until: now.toISOString(),
417
+ };
418
+ }
419
+
420
+ // ==========================================================================
421
+ // Batch Operations
422
+ // ==========================================================================
423
+
424
+ /**
425
+ * Batch create work items with rate limit handling
426
+ */
427
+ async batchCreateWorkItems(
428
+ workItems: CreateWorkItemRequest[],
429
+ parentId?: number,
430
+ childType?: string,
431
+ options: { batchSize?: number; delayMs?: number } = {}
432
+ ): Promise<WorkItem[]> {
433
+ const { batchSize = 10, delayMs = 15000 } = options; // 10 items per minute (ADO: 200/5min limit)
434
+ const created: WorkItem[] = [];
435
+
436
+ for (let i = 0; i < workItems.length; i += batchSize) {
437
+ const batch = workItems.slice(i, i + batchSize);
438
+
439
+ console.log(
440
+ `Creating work items ${i + 1}-${Math.min(i + batchSize, workItems.length)} of ${workItems.length}...`
441
+ );
442
+
443
+ for (const item of batch) {
444
+ try {
445
+ const createdItem =
446
+ parentId && childType
447
+ ? await this.createChildWorkItem(item, parentId, childType)
448
+ : await this.createEpic(item);
449
+
450
+ created.push(createdItem);
451
+ } catch (error: any) {
452
+ console.error(
453
+ `Failed to create work item "${item.title}":`,
454
+ error.message
455
+ );
456
+ }
457
+ }
458
+
459
+ // Delay between batches
460
+ if (i + batchSize < workItems.length) {
461
+ console.log(`Waiting ${delayMs / 1000}s to avoid rate limits...`);
462
+ await this.sleep(delayMs);
463
+ }
464
+ }
465
+
466
+ return created;
467
+ }
468
+
469
+ // ==========================================================================
470
+ // HTTP Request Handler
471
+ // ==========================================================================
472
+
473
+ /**
474
+ * Make HTTPS request to ADO API
475
+ */
476
+ private async request<T = any>(
477
+ method: string,
478
+ path: string,
479
+ body?: any,
480
+ customHeaders?: Record<string, string>
481
+ ): Promise<T> {
482
+ return new Promise((resolve, reject) => {
483
+ const url = `${this.baseUrl}${path}`;
484
+ const { hostname, pathname, search } = new URL(url);
485
+
486
+ const headers: Record<string, string> = {
487
+ Authorization: this.authHeader,
488
+ Accept: 'application/json',
489
+ ...customHeaders,
490
+ };
491
+
492
+ if (body && !customHeaders?.['Content-Type']) {
493
+ headers['Content-Type'] = 'application/json';
494
+ }
495
+
496
+ const options = {
497
+ hostname,
498
+ path: pathname + search,
499
+ method,
500
+ headers,
501
+ };
502
+
503
+ const req = https.request(options, (res) => {
504
+ let data = '';
505
+
506
+ res.on('data', (chunk) => {
507
+ data += chunk;
508
+ });
509
+
510
+ res.on('end', () => {
511
+ // Parse response
512
+ let parsed: any;
513
+ try {
514
+ parsed = data ? JSON.parse(data) : {};
515
+ } catch {
516
+ parsed = { raw: data };
517
+ }
518
+
519
+ // Check status code
520
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
521
+ resolve(parsed as T);
522
+ } else {
523
+ const errorMsg =
524
+ parsed.message ||
525
+ `HTTP ${res.statusCode}: ${data}`;
526
+ reject(new Error(errorMsg));
527
+ }
528
+ });
529
+ });
530
+
531
+ req.on('error', (error) => {
532
+ reject(error);
533
+ });
534
+
535
+ // Send body if present
536
+ if (body) {
537
+ req.write(JSON.stringify(body));
538
+ }
539
+
540
+ req.end();
541
+ });
542
+ }
543
+
544
+ private sleep(ms: number): Promise<void> {
545
+ return new Promise((resolve) => setTimeout(resolve, ms));
546
+ }
547
+ }
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: close-issue
2
+ name: specweave-github:close-issue
3
3
  description: Close GitHub issue for completed SpecWeave increment. Posts completion summary with final stats, deliverables, and closes the issue. Links closure in increment metadata.
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: create-issue
2
+ name: specweave-github:create-issue
3
3
  description: Create a GitHub issue for a SpecWeave increment. Generates issue from increment specs with task checklist, labels, and milestone. Links issue to increment metadata.
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: status
2
+ name: specweave-github:status
3
3
  description: Check GitHub sync status for SpecWeave increment. Shows issue number, sync state, progress, last update, and any sync issues. Useful for troubleshooting and monitoring.
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: sync-tasks
2
+ name: specweave-github:sync-tasks
3
3
  description: Sync SpecWeave increment tasks to GitHub issues (task-level granularity). Creates milestone, epic issue, and individual issues for each task.
4
4
  ---
5
5