specweave 0.7.0 → 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 (133) 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.d.ts.map +1 -1
  12. package/dist/cli/commands/init.js +7 -3
  13. package/dist/cli/commands/init.js.map +1 -1
  14. package/dist/cli/commands/migrate-to-multiproject.d.ts +37 -0
  15. package/dist/cli/commands/migrate-to-multiproject.d.ts.map +1 -0
  16. package/dist/cli/commands/migrate-to-multiproject.js +189 -0
  17. package/dist/cli/commands/migrate-to-multiproject.js.map +1 -0
  18. package/dist/cli/commands/migrate-to-profiles.d.ts +25 -0
  19. package/dist/cli/commands/migrate-to-profiles.d.ts.map +1 -0
  20. package/dist/cli/commands/migrate-to-profiles.js +350 -0
  21. package/dist/cli/commands/migrate-to-profiles.js.map +1 -0
  22. package/dist/cli/commands/switch-project.d.ts +13 -0
  23. package/dist/cli/commands/switch-project.d.ts.map +1 -0
  24. package/dist/cli/commands/switch-project.js +91 -0
  25. package/dist/cli/commands/switch-project.js.map +1 -0
  26. package/dist/cli/helpers/issue-tracker/index.js +4 -4
  27. package/dist/cli/helpers/issue-tracker/index.js.map +1 -1
  28. package/dist/cli/helpers/issue-tracker/utils.d.ts +6 -3
  29. package/dist/cli/helpers/issue-tracker/utils.d.ts.map +1 -1
  30. package/dist/cli/helpers/issue-tracker/utils.js +9 -7
  31. package/dist/cli/helpers/issue-tracker/utils.js.map +1 -1
  32. package/dist/core/brownfield/analyzer.d.ts +86 -0
  33. package/dist/core/brownfield/analyzer.d.ts.map +1 -0
  34. package/dist/core/brownfield/analyzer.js +365 -0
  35. package/dist/core/brownfield/analyzer.js.map +1 -0
  36. package/dist/core/brownfield/importer.d.ts +76 -0
  37. package/dist/core/brownfield/importer.d.ts.map +1 -0
  38. package/dist/core/brownfield/importer.js +287 -0
  39. package/dist/core/brownfield/importer.js.map +1 -0
  40. package/dist/core/config-manager.d.ts +47 -0
  41. package/dist/core/config-manager.d.ts.map +1 -0
  42. package/dist/core/config-manager.js +136 -0
  43. package/dist/core/config-manager.js.map +1 -0
  44. package/dist/core/project-manager.d.ts +127 -0
  45. package/dist/core/project-manager.d.ts.map +1 -0
  46. package/dist/core/project-manager.js +524 -0
  47. package/dist/core/project-manager.js.map +1 -0
  48. package/dist/core/sync/profile-manager.d.ts +72 -0
  49. package/dist/core/sync/profile-manager.d.ts.map +1 -0
  50. package/dist/core/sync/profile-manager.js +338 -0
  51. package/dist/core/sync/profile-manager.js.map +1 -0
  52. package/dist/core/sync/profile-selector.d.ts +52 -0
  53. package/dist/core/sync/profile-selector.d.ts.map +1 -0
  54. package/dist/core/sync/profile-selector.js +179 -0
  55. package/dist/core/sync/profile-selector.js.map +1 -0
  56. package/dist/core/sync/project-context.d.ts +81 -0
  57. package/dist/core/sync/project-context.d.ts.map +1 -0
  58. package/dist/core/sync/project-context.js +354 -0
  59. package/dist/core/sync/project-context.js.map +1 -0
  60. package/dist/core/sync/rate-limiter.d.ts +116 -0
  61. package/dist/core/sync/rate-limiter.d.ts.map +1 -0
  62. package/dist/core/sync/rate-limiter.js +308 -0
  63. package/dist/core/sync/rate-limiter.js.map +1 -0
  64. package/dist/core/sync/time-range-selector.d.ts +48 -0
  65. package/dist/core/sync/time-range-selector.d.ts.map +1 -0
  66. package/dist/core/sync/time-range-selector.js +224 -0
  67. package/dist/core/sync/time-range-selector.js.map +1 -0
  68. package/dist/core/types/config.d.ts +4 -0
  69. package/dist/core/types/config.d.ts.map +1 -1
  70. package/dist/core/types/config.js.map +1 -1
  71. package/dist/core/types/sync-profile.d.ts +205 -0
  72. package/dist/core/types/sync-profile.d.ts.map +1 -0
  73. package/dist/core/types/sync-profile.js +8 -0
  74. package/dist/core/types/sync-profile.js.map +1 -0
  75. package/dist/utils/project-detection.d.ts +141 -0
  76. package/dist/utils/project-detection.d.ts.map +1 -0
  77. package/dist/utils/project-detection.js +321 -0
  78. package/dist/utils/project-detection.js.map +1 -0
  79. package/package.json +2 -1
  80. package/plugins/specweave/agents/pm/AGENT.md +7 -4
  81. package/plugins/specweave/commands/specweave-abandon.md +17 -17
  82. package/plugins/specweave/commands/specweave-check-tests.md +14 -14
  83. package/plugins/specweave/commands/specweave-costs.md +1 -1
  84. package/plugins/specweave/commands/specweave-do.md +12 -12
  85. package/plugins/specweave/commands/specweave-done.md +28 -15
  86. package/plugins/specweave/commands/specweave-import-docs.md +212 -0
  87. package/plugins/specweave/commands/specweave-increment.md +10 -10
  88. package/plugins/specweave/commands/specweave-init-multiproject.md +146 -0
  89. package/plugins/specweave/commands/specweave-next.md +16 -16
  90. package/plugins/specweave/commands/specweave-pause.md +17 -17
  91. package/plugins/specweave/commands/specweave-progress.md +10 -10
  92. package/plugins/specweave/commands/specweave-qa.md +11 -11
  93. package/plugins/specweave/commands/specweave-resume.md +22 -22
  94. package/plugins/specweave/commands/specweave-status.md +18 -18
  95. package/plugins/specweave/commands/specweave-switch-project.md +168 -0
  96. package/plugins/specweave/commands/specweave-sync-docs.md +1 -1
  97. package/plugins/specweave/commands/specweave-sync-tasks.md +9 -9
  98. package/plugins/specweave/commands/specweave-tdd-cycle.md +7 -0
  99. package/plugins/specweave/commands/specweave-tdd-green.md +7 -0
  100. package/plugins/specweave/commands/specweave-tdd-red.md +7 -0
  101. package/plugins/specweave/commands/specweave-tdd-refactor.md +7 -0
  102. package/plugins/specweave/commands/specweave-translate.md +1 -1
  103. package/plugins/specweave/commands/specweave-update-scope.md +8 -8
  104. package/plugins/specweave/commands/specweave-validate.md +18 -20
  105. package/plugins/specweave/commands/specweave.md +5 -5
  106. package/plugins/specweave/skills/SKILLS-INDEX.md +1 -1
  107. package/plugins/specweave/skills/increment-planner/SKILL.md +40 -4
  108. package/plugins/specweave/skills/increment-quality-judge/SKILL.md +5 -5
  109. package/plugins/specweave/skills/increment-quality-judge-v2/SKILL.md +5 -5
  110. package/plugins/specweave/skills/specweave-detector/SKILL.md +3 -3
  111. package/plugins/specweave-ado/commands/{close-workitem.md → specweave-ado-close-workitem.md} +1 -1
  112. package/plugins/specweave-ado/commands/{create-workitem.md → specweave-ado-create-workitem.md} +1 -1
  113. package/plugins/specweave-ado/commands/{status.md → specweave-ado-status.md} +1 -1
  114. package/plugins/specweave-ado/commands/{sync.md → specweave-ado-sync.md} +1 -1
  115. package/plugins/specweave-ado/lib/ado-client-v2.ts +547 -0
  116. package/plugins/specweave-github/commands/{close-issue.md → specweave-github-close-issue.md} +1 -1
  117. package/plugins/specweave-github/commands/{create-issue.md → specweave-github-create-issue.md} +1 -1
  118. package/plugins/specweave-github/commands/{status.md → specweave-github-status.md} +1 -1
  119. package/plugins/specweave-github/commands/{sync-tasks.md → specweave-github-sync-tasks.md} +1 -1
  120. package/plugins/specweave-github/commands/specweave-github-sync.md +568 -0
  121. package/plugins/specweave-github/lib/github-client-v2.ts +555 -0
  122. package/plugins/specweave-infrastructure/commands/{monitor-setup.md → specweave-infrastructure-monitor-setup.md} +1 -1
  123. package/plugins/specweave-infrastructure/commands/{slo-implement.md → specweave-infrastructure-slo-implement.md} +1 -1
  124. package/plugins/specweave-jira/commands/{sync.md → specweave-jira-sync.md} +1 -1
  125. package/plugins/specweave-jira/lib/jira-client-v2.ts +529 -0
  126. package/plugins/specweave-ml/commands/{ml-deploy.md → specweave-ml-deploy.md} +1 -1
  127. package/plugins/specweave-ml/commands/{ml-evaluate.md → specweave-ml-evaluate.md} +1 -1
  128. package/plugins/specweave-ml/commands/{ml-explain.md → specweave-ml-explain.md} +1 -1
  129. package/plugins/specweave-ml/commands/{ml-pipeline.md → specweave-ml-pipeline.md} +1 -1
  130. package/src/templates/AGENTS.md.template +1 -0
  131. package/src/templates/CLAUDE.md.template +1 -0
  132. package/plugins/specweave-github/commands/sync.md +0 -443
  133. /package/plugins/specweave/{commands/README.md → COMMANDS.md} +0 -0
@@ -0,0 +1,555 @@
1
+ /**
2
+ * GitHub CLI Wrapper for SpecWeave (Multi-Project Support)
3
+ *
4
+ * Profile-based GitHub client that supports:
5
+ * - Multiple repositories via sync profiles
6
+ * - Time range filtering for syncs
7
+ * - Rate limiting protection
8
+ * - Secure command execution (no shell injection)
9
+ */
10
+
11
+ import { execFileNoThrow } from '../../../src/utils/execFileNoThrow.js';
12
+ import { GitHubIssue, GitHubMilestone } from './types';
13
+ import { SyncProfile, GitHubConfig, TimeRangePreset } from '../../../src/core/types/sync-profile';
14
+
15
+ export class GitHubClientV2 {
16
+ private owner: string;
17
+ private repo: string;
18
+ private fullRepo: string;
19
+
20
+ /**
21
+ * Create GitHub client from sync profile
22
+ */
23
+ constructor(profile: SyncProfile) {
24
+ if (profile.provider !== 'github') {
25
+ throw new Error(`Expected GitHub profile, got ${profile.provider}`);
26
+ }
27
+
28
+ const config = profile.config as GitHubConfig;
29
+ this.owner = config.owner;
30
+ this.repo = config.repo;
31
+ this.fullRepo = `${this.owner}/${this.repo}`;
32
+ }
33
+
34
+ /**
35
+ * Create client from owner/repo directly
36
+ */
37
+ static fromRepo(owner: string, repo: string): GitHubClientV2 {
38
+ const profile: SyncProfile = {
39
+ provider: 'github',
40
+ displayName: `${owner}/${repo}`,
41
+ config: { owner, repo },
42
+ timeRange: { default: '1M', max: '6M' },
43
+ };
44
+ return new GitHubClientV2(profile);
45
+ }
46
+
47
+ // ==========================================================================
48
+ // Authentication & Setup
49
+ // ==========================================================================
50
+
51
+ /**
52
+ * Check if GitHub CLI is installed and authenticated
53
+ */
54
+ static async checkCLI(): Promise<{
55
+ installed: boolean;
56
+ authenticated: boolean;
57
+ error?: string;
58
+ }> {
59
+ // Check installation
60
+ const versionCheck = await execFileNoThrow('gh', ['--version']);
61
+ if (versionCheck.status !== 0) {
62
+ return {
63
+ installed: false,
64
+ authenticated: false,
65
+ error: 'GitHub CLI (gh) not installed. Install from: https://cli.github.com/',
66
+ };
67
+ }
68
+
69
+ // Check authentication
70
+ const authCheck = await execFileNoThrow('gh', ['auth', 'status']);
71
+ if (authCheck.status !== 0) {
72
+ return {
73
+ installed: true,
74
+ authenticated: false,
75
+ error: 'GitHub CLI not authenticated. Run: gh auth login',
76
+ };
77
+ }
78
+
79
+ return { installed: true, authenticated: true };
80
+ }
81
+
82
+ /**
83
+ * Auto-detect repository from git remote
84
+ */
85
+ static async detectRepo(cwd?: string): Promise<{owner: string; repo: string} | null> {
86
+ const result = await execFileNoThrow('git', [
87
+ 'remote',
88
+ 'get-url',
89
+ 'origin',
90
+ ], { cwd });
91
+
92
+ if (result.status !== 0) {
93
+ return null;
94
+ }
95
+
96
+ const remote = result.stdout.trim();
97
+ const match = remote.match(/github\.com[:/](.+)\/(.+?)(?:\.git)?$/);
98
+
99
+ if (!match) {
100
+ return null;
101
+ }
102
+
103
+ return {
104
+ owner: match[1],
105
+ repo: match[2],
106
+ };
107
+ }
108
+
109
+ // ==========================================================================
110
+ // Milestones
111
+ // ==========================================================================
112
+
113
+ /**
114
+ * Create or get existing milestone
115
+ */
116
+ async createOrGetMilestone(
117
+ title: string,
118
+ description?: string,
119
+ daysFromNow: number = 2
120
+ ): Promise<GitHubMilestone> {
121
+ // Check if milestone already exists
122
+ const existing = await this.getMilestoneByTitle(title);
123
+ if (existing) {
124
+ return existing;
125
+ }
126
+
127
+ // Calculate due date
128
+ const dueDate = new Date();
129
+ dueDate.setDate(dueDate.getDate() + daysFromNow);
130
+ const dueDateISO = dueDate.toISOString();
131
+
132
+ // Build API request
133
+ const args = [
134
+ 'api',
135
+ `repos/${this.fullRepo}/milestones`,
136
+ '-f',
137
+ `title=${title}`,
138
+ '-f',
139
+ `due_on=${dueDateISO}`,
140
+ '--jq',
141
+ '{number: .number, title: .title, description: .description, state: .state, due_on: .due_on}',
142
+ ];
143
+
144
+ if (description) {
145
+ args.splice(4, 0, '-f', `description=${description}`);
146
+ }
147
+
148
+ const result = await execFileNoThrow('gh', args);
149
+
150
+ if (result.status !== 0) {
151
+ throw new Error(`Failed to create milestone: ${result.stderr || result.stdout}`);
152
+ }
153
+
154
+ return JSON.parse(result.stdout);
155
+ }
156
+
157
+ /**
158
+ * Get milestone by title
159
+ */
160
+ private async getMilestoneByTitle(
161
+ title: string
162
+ ): Promise<GitHubMilestone | null> {
163
+ const result = await execFileNoThrow('gh', [
164
+ 'api',
165
+ `repos/${this.fullRepo}/milestones`,
166
+ '--jq',
167
+ `.[] | select(.title=="${title}") | {number: .number, title: .title, description: .description, state: .state}`,
168
+ ]);
169
+
170
+ if (result.status !== 0 || !result.stdout.trim()) {
171
+ return null;
172
+ }
173
+
174
+ return JSON.parse(result.stdout);
175
+ }
176
+
177
+ // ==========================================================================
178
+ // Issues
179
+ // ==========================================================================
180
+
181
+ /**
182
+ * Create epic issue (increment-level)
183
+ */
184
+ async createEpicIssue(
185
+ title: string,
186
+ body: string,
187
+ milestone?: number | string,
188
+ labels: string[] = []
189
+ ): Promise<GitHubIssue> {
190
+ const args = [
191
+ 'issue',
192
+ 'create',
193
+ '--repo',
194
+ this.fullRepo,
195
+ '--title',
196
+ title,
197
+ '--body',
198
+ body,
199
+ ];
200
+
201
+ // Add labels
202
+ for (const label of labels) {
203
+ args.push('--label', label);
204
+ }
205
+
206
+ // Add milestone
207
+ if (milestone !== undefined) {
208
+ args.push('--milestone', String(milestone));
209
+ }
210
+
211
+ // Create issue (returns URL)
212
+ const createResult = await execFileNoThrow('gh', args);
213
+
214
+ if (createResult.status !== 0) {
215
+ throw new Error(
216
+ `Failed to create epic issue: ${createResult.stderr || createResult.stdout}`
217
+ );
218
+ }
219
+
220
+ const issueUrl = createResult.stdout.trim();
221
+ const issueNumber = parseInt(issueUrl.split('/').pop() || '0', 10);
222
+
223
+ if (!issueNumber) {
224
+ throw new Error(`Failed to extract issue number from URL: ${issueUrl}`);
225
+ }
226
+
227
+ // Fetch issue details
228
+ return await this.getIssue(issueNumber);
229
+ }
230
+
231
+ /**
232
+ * Create task issue (linked to epic)
233
+ */
234
+ async createTaskIssue(
235
+ title: string,
236
+ body: string,
237
+ epicNumber: number,
238
+ milestone?: number | string,
239
+ labels: string[] = []
240
+ ): Promise<GitHubIssue> {
241
+ // Add epic reference to body
242
+ const enhancedBody = `**Part of**: #${epicNumber}\n\n${body}`;
243
+
244
+ return await this.createEpicIssue(title, enhancedBody, milestone, labels);
245
+ }
246
+
247
+ /**
248
+ * Get issue details
249
+ */
250
+ async getIssue(issueNumber: number): Promise<GitHubIssue> {
251
+ const result = await execFileNoThrow('gh', [
252
+ 'issue',
253
+ 'view',
254
+ String(issueNumber),
255
+ '--repo',
256
+ this.fullRepo,
257
+ '--json',
258
+ 'number,title,body,state,url,labels,milestone',
259
+ ]);
260
+
261
+ if (result.status !== 0) {
262
+ throw new Error(
263
+ `Failed to get issue #${issueNumber}: ${result.stderr || result.stdout}`
264
+ );
265
+ }
266
+
267
+ const issue = JSON.parse(result.stdout);
268
+ return {
269
+ ...issue,
270
+ html_url: issue.url,
271
+ labels: issue.labels?.map((l: any) => l.name) || [],
272
+ };
273
+ }
274
+
275
+ /**
276
+ * Update issue body
277
+ */
278
+ async updateIssueBody(issueNumber: number, newBody: string): Promise<void> {
279
+ const result = await execFileNoThrow('gh', [
280
+ 'issue',
281
+ 'edit',
282
+ String(issueNumber),
283
+ '--repo',
284
+ this.fullRepo,
285
+ '--body',
286
+ newBody,
287
+ ]);
288
+
289
+ if (result.status !== 0) {
290
+ throw new Error(
291
+ `Failed to update issue #${issueNumber}: ${result.stderr || result.stdout}`
292
+ );
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Close issue
298
+ */
299
+ async closeIssue(issueNumber: number, comment?: string): Promise<void> {
300
+ if (comment) {
301
+ await this.addComment(issueNumber, comment);
302
+ }
303
+
304
+ const result = await execFileNoThrow('gh', [
305
+ 'issue',
306
+ 'close',
307
+ String(issueNumber),
308
+ '--repo',
309
+ this.fullRepo,
310
+ ]);
311
+
312
+ if (result.status !== 0) {
313
+ throw new Error(
314
+ `Failed to close issue #${issueNumber}: ${result.stderr || result.stdout}`
315
+ );
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Add comment to issue
321
+ */
322
+ async addComment(issueNumber: number, comment: string): Promise<void> {
323
+ const result = await execFileNoThrow('gh', [
324
+ 'issue',
325
+ 'comment',
326
+ String(issueNumber),
327
+ '--repo',
328
+ this.fullRepo,
329
+ '--body',
330
+ comment,
331
+ ]);
332
+
333
+ if (result.status !== 0) {
334
+ throw new Error(
335
+ `Failed to add comment to issue #${issueNumber}: ${result.stderr || result.stdout}`
336
+ );
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Add labels to issue
342
+ */
343
+ async addLabels(issueNumber: number, labels: string[]): Promise<void> {
344
+ if (labels.length === 0) return;
345
+
346
+ const args = [
347
+ 'issue',
348
+ 'edit',
349
+ String(issueNumber),
350
+ '--repo',
351
+ this.fullRepo,
352
+ ];
353
+
354
+ for (const label of labels) {
355
+ args.push('--add-label', label);
356
+ }
357
+
358
+ const result = await execFileNoThrow('gh', args);
359
+
360
+ if (result.status !== 0) {
361
+ throw new Error(
362
+ `Failed to add labels to issue #${issueNumber}: ${result.stderr || result.stdout}`
363
+ );
364
+ }
365
+ }
366
+
367
+ // ==========================================================================
368
+ // Time Range Filtering
369
+ // ==========================================================================
370
+
371
+ /**
372
+ * List issues within a time range
373
+ */
374
+ async listIssuesInTimeRange(
375
+ timeRange: TimeRangePreset,
376
+ customStart?: string,
377
+ customEnd?: string
378
+ ): Promise<GitHubIssue[]> {
379
+ const { since, until } = this.calculateTimeRange(timeRange, customStart, customEnd);
380
+
381
+ // GitHub search query
382
+ const query = `repo:${this.fullRepo} is:issue created:${since}..${until}`;
383
+
384
+ const result = await execFileNoThrow('gh', [
385
+ 'search',
386
+ 'issues',
387
+ query,
388
+ '--json',
389
+ 'number,title,body,state,url,labels,milestone',
390
+ '--limit',
391
+ '1000', // Max results
392
+ ]);
393
+
394
+ if (result.status !== 0) {
395
+ throw new Error(
396
+ `Failed to list issues: ${result.stderr || result.stdout}`
397
+ );
398
+ }
399
+
400
+ const issues = JSON.parse(result.stdout);
401
+ return issues.map((issue: any) => ({
402
+ ...issue,
403
+ html_url: issue.url,
404
+ labels: issue.labels?.map((l: any) => l.name) || [],
405
+ }));
406
+ }
407
+
408
+ /**
409
+ * Calculate date range from time range preset
410
+ */
411
+ private calculateTimeRange(
412
+ timeRange: TimeRangePreset,
413
+ customStart?: string,
414
+ customEnd?: string
415
+ ): { since: string; until: string } {
416
+ if (timeRange === 'ALL') {
417
+ return {
418
+ since: '1970-01-01',
419
+ until: new Date().toISOString().split('T')[0],
420
+ };
421
+ }
422
+
423
+ if (customStart) {
424
+ return {
425
+ since: customStart,
426
+ until: customEnd || new Date().toISOString().split('T')[0],
427
+ };
428
+ }
429
+
430
+ const now = new Date();
431
+ const since = new Date(now);
432
+
433
+ // Calculate date based on preset
434
+ switch (timeRange) {
435
+ case '1W':
436
+ since.setDate(now.getDate() - 7);
437
+ break;
438
+ case '2W':
439
+ since.setDate(now.getDate() - 14);
440
+ break;
441
+ case '1M':
442
+ since.setMonth(now.getMonth() - 1);
443
+ break;
444
+ case '3M':
445
+ since.setMonth(now.getMonth() - 3);
446
+ break;
447
+ case '6M':
448
+ since.setMonth(now.getMonth() - 6);
449
+ break;
450
+ case '1Y':
451
+ since.setFullYear(now.getFullYear() - 1);
452
+ break;
453
+ }
454
+
455
+ return {
456
+ since: since.toISOString().split('T')[0],
457
+ until: now.toISOString().split('T')[0],
458
+ };
459
+ }
460
+
461
+ // ==========================================================================
462
+ // Rate Limiting
463
+ // ==========================================================================
464
+
465
+ /**
466
+ * Check rate limit status
467
+ */
468
+ async checkRateLimit(): Promise<{
469
+ remaining: number;
470
+ limit: number;
471
+ reset: Date;
472
+ }> {
473
+ const result = await execFileNoThrow('gh', [
474
+ 'api',
475
+ 'rate_limit',
476
+ '--jq',
477
+ '.rate | {remaining: .remaining, limit: .limit, reset: .reset}',
478
+ ]);
479
+
480
+ if (result.status !== 0) {
481
+ throw new Error(
482
+ `Failed to check rate limit: ${result.stderr || result.stdout}`
483
+ );
484
+ }
485
+
486
+ const data = JSON.parse(result.stdout);
487
+ return {
488
+ ...data,
489
+ reset: new Date(data.reset * 1000),
490
+ };
491
+ }
492
+
493
+ // ==========================================================================
494
+ // Batch Operations
495
+ // ==========================================================================
496
+
497
+ /**
498
+ * Batch create issues with rate limit handling
499
+ */
500
+ async batchCreateIssues(
501
+ issues: Array<{ title: string; body: string; labels?: string[] }>,
502
+ milestone?: number | string,
503
+ epicNumber?: number,
504
+ options: { batchSize?: number; delayMs?: number } = {}
505
+ ): Promise<GitHubIssue[]> {
506
+ const { batchSize = 10, delayMs = 6000 } = options;
507
+ const createdIssues: GitHubIssue[] = [];
508
+
509
+ for (let i = 0; i < issues.length; i += batchSize) {
510
+ const batch = issues.slice(i, i + batchSize);
511
+
512
+ console.log(
513
+ `Creating issues ${i + 1}-${Math.min(i + batchSize, issues.length)} of ${issues.length}...`
514
+ );
515
+
516
+ for (const issue of batch) {
517
+ try {
518
+ const created = epicNumber
519
+ ? await this.createTaskIssue(
520
+ issue.title,
521
+ issue.body,
522
+ epicNumber,
523
+ milestone,
524
+ issue.labels
525
+ )
526
+ : await this.createEpicIssue(
527
+ issue.title,
528
+ issue.body,
529
+ milestone,
530
+ issue.labels
531
+ );
532
+
533
+ createdIssues.push(created);
534
+ } catch (error: any) {
535
+ console.error(
536
+ `Failed to create issue "${issue.title}":`,
537
+ error.message
538
+ );
539
+ }
540
+ }
541
+
542
+ // Delay between batches
543
+ if (i + batchSize < issues.length) {
544
+ console.log(`Waiting ${delayMs / 1000}s to avoid rate limits...`);
545
+ await this.sleep(delayMs);
546
+ }
547
+ }
548
+
549
+ return createdIssues;
550
+ }
551
+
552
+ private sleep(ms: number): Promise<void> {
553
+ return new Promise((resolve) => setTimeout(resolve, ms));
554
+ }
555
+ }
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: monitor-setup
2
+ name: specweave-infrastructure:monitor-setup
3
3
  description: Set up comprehensive monitoring and observability with Prometheus, Grafana, distributed tracing, and log aggregation
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: slo-implement
2
+ name: specweave-infrastructure:slo-implement
3
3
  description: Implement Service Level Objectives (SLOs) with reliability standards, SLIs, and error budget-based engineering practices
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: sync
2
+ name: specweave-jira:sync
3
3
  description: Sync SpecWeave increments with JIRA epics/stories. Supports import, export, bidirectional sync, and granular item operations
4
4
  ---
5
5