specweave 0.23.10 → 0.23.12

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 (102) hide show
  1. package/.claude-plugin/marketplace.json +7 -7
  2. package/CLAUDE.md +384 -1449
  3. package/dist/src/cli/commands/cleanup-cache.d.ts +14 -0
  4. package/dist/src/cli/commands/cleanup-cache.d.ts.map +1 -0
  5. package/dist/src/cli/commands/cleanup-cache.js +63 -0
  6. package/dist/src/cli/commands/cleanup-cache.js.map +1 -0
  7. package/dist/src/cli/commands/init.js +40 -0
  8. package/dist/src/cli/commands/init.js.map +1 -1
  9. package/dist/src/cli/helpers/async-project-loader.d.ts +148 -0
  10. package/dist/src/cli/helpers/async-project-loader.d.ts.map +1 -0
  11. package/dist/src/cli/helpers/async-project-loader.js +351 -0
  12. package/dist/src/cli/helpers/async-project-loader.js.map +1 -0
  13. package/dist/src/cli/helpers/cancelation-handler.d.ts +123 -0
  14. package/dist/src/cli/helpers/cancelation-handler.d.ts.map +1 -0
  15. package/dist/src/cli/helpers/cancelation-handler.js +187 -0
  16. package/dist/src/cli/helpers/cancelation-handler.js.map +1 -0
  17. package/dist/src/cli/helpers/import-strategy-prompter.d.ts +43 -0
  18. package/dist/src/cli/helpers/import-strategy-prompter.d.ts.map +1 -0
  19. package/dist/src/cli/helpers/import-strategy-prompter.js +136 -0
  20. package/dist/src/cli/helpers/import-strategy-prompter.js.map +1 -0
  21. package/dist/src/cli/helpers/issue-tracker/ado.d.ts +5 -2
  22. package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
  23. package/dist/src/cli/helpers/issue-tracker/ado.js +90 -40
  24. package/dist/src/cli/helpers/issue-tracker/ado.js.map +1 -1
  25. package/dist/src/cli/helpers/issue-tracker/jira.d.ts +2 -1
  26. package/dist/src/cli/helpers/issue-tracker/jira.d.ts.map +1 -1
  27. package/dist/src/cli/helpers/issue-tracker/jira.js +120 -35
  28. package/dist/src/cli/helpers/issue-tracker/jira.js.map +1 -1
  29. package/dist/src/cli/helpers/progress-tracker.d.ts +121 -0
  30. package/dist/src/cli/helpers/progress-tracker.d.ts.map +1 -0
  31. package/dist/src/cli/helpers/progress-tracker.js +202 -0
  32. package/dist/src/cli/helpers/progress-tracker.js.map +1 -0
  33. package/dist/src/cli/helpers/project-count-fetcher.d.ts +69 -0
  34. package/dist/src/cli/helpers/project-count-fetcher.d.ts.map +1 -0
  35. package/dist/src/cli/helpers/project-count-fetcher.js +173 -0
  36. package/dist/src/cli/helpers/project-count-fetcher.js.map +1 -0
  37. package/dist/src/config/types.d.ts +14 -14
  38. package/dist/src/core/cache/cache-manager.d.ts +119 -0
  39. package/dist/src/core/cache/cache-manager.d.ts.map +1 -0
  40. package/dist/src/core/cache/cache-manager.js +304 -0
  41. package/dist/src/core/cache/cache-manager.js.map +1 -0
  42. package/dist/src/core/cache/rate-limit-checker.d.ts +92 -0
  43. package/dist/src/core/cache/rate-limit-checker.d.ts.map +1 -0
  44. package/dist/src/core/cache/rate-limit-checker.js +160 -0
  45. package/dist/src/core/cache/rate-limit-checker.js.map +1 -0
  46. package/dist/src/core/progress/cancelation-handler.d.ts +79 -0
  47. package/dist/src/core/progress/cancelation-handler.d.ts.map +1 -0
  48. package/dist/src/core/progress/cancelation-handler.js +111 -0
  49. package/dist/src/core/progress/cancelation-handler.js.map +1 -0
  50. package/dist/src/core/progress/import-state.d.ts +71 -0
  51. package/dist/src/core/progress/import-state.d.ts.map +1 -0
  52. package/dist/src/core/progress/import-state.js +96 -0
  53. package/dist/src/core/progress/import-state.js.map +1 -0
  54. package/dist/src/core/progress/progress-tracker.d.ts +139 -0
  55. package/dist/src/core/progress/progress-tracker.d.ts.map +1 -0
  56. package/dist/src/core/progress/progress-tracker.js +223 -0
  57. package/dist/src/core/progress/progress-tracker.js.map +1 -0
  58. package/dist/src/init/architecture/types.d.ts +6 -6
  59. package/dist/src/integrations/ado/ado-client.d.ts +25 -0
  60. package/dist/src/integrations/ado/ado-client.d.ts.map +1 -1
  61. package/dist/src/integrations/ado/ado-client.js +67 -0
  62. package/dist/src/integrations/ado/ado-client.js.map +1 -1
  63. package/dist/src/integrations/ado/ado-dependency-loader.d.ts +99 -0
  64. package/dist/src/integrations/ado/ado-dependency-loader.d.ts.map +1 -0
  65. package/dist/src/integrations/ado/ado-dependency-loader.js +207 -0
  66. package/dist/src/integrations/ado/ado-dependency-loader.js.map +1 -0
  67. package/dist/src/integrations/jira/jira-client.d.ts +32 -0
  68. package/dist/src/integrations/jira/jira-client.d.ts.map +1 -1
  69. package/dist/src/integrations/jira/jira-client.js +81 -0
  70. package/dist/src/integrations/jira/jira-client.js.map +1 -1
  71. package/dist/src/integrations/jira/jira-dependency-loader.d.ts +101 -0
  72. package/dist/src/integrations/jira/jira-dependency-loader.d.ts.map +1 -0
  73. package/dist/src/integrations/jira/jira-dependency-loader.js +200 -0
  74. package/dist/src/integrations/jira/jira-dependency-loader.js.map +1 -0
  75. package/package.json +1 -1
  76. package/plugins/specweave/.claude-plugin/plugin.json +20 -0
  77. package/plugins/specweave/agents/architect/AGENT.md +100 -602
  78. package/plugins/specweave/agents/pm/AGENT.md +96 -597
  79. package/plugins/specweave/agents/pm/AGENT.md.bak +1893 -0
  80. package/plugins/specweave/agents/pm/AGENT.md.bak2 +1754 -0
  81. package/plugins/specweave/commands/check-hooks.md +257 -0
  82. package/plugins/specweave/hooks/post-edit-spec.sh +202 -31
  83. package/plugins/specweave/hooks/post-task-completion.sh +225 -228
  84. package/plugins/specweave/hooks/post-write-spec.sh +207 -31
  85. package/plugins/specweave/hooks/pre-edit-spec.sh +151 -0
  86. package/plugins/specweave/hooks/pre-task-completion.sh +5 -7
  87. package/plugins/specweave/hooks/pre-write-spec.sh +151 -0
  88. package/plugins/specweave/hooks/test-pretooluse-env.sh +72 -0
  89. package/plugins/specweave/skills/compliance-architecture/SKILL.md +374 -0
  90. package/plugins/specweave/skills/external-sync-wizard/SKILL.md +610 -0
  91. package/plugins/specweave/skills/pm-closure-validation/SKILL.md +541 -0
  92. package/plugins/specweave/skills/roadmap-planner/SKILL.md +473 -0
  93. package/plugins/specweave-ado/commands/refresh-cache.js +25 -0
  94. package/plugins/specweave-ado/commands/refresh-cache.ts +40 -0
  95. package/plugins/specweave-ado/hooks/post-task-completion.sh +1 -1
  96. package/plugins/specweave-github/hooks/post-task-completion.sh +1 -1
  97. package/plugins/specweave-jira/commands/refresh-cache.js +25 -0
  98. package/plugins/specweave-jira/commands/refresh-cache.ts +40 -0
  99. package/plugins/specweave-jira/hooks/post-task-completion.sh +1 -1
  100. package/plugins/specweave-kafka-streams/commands/topology.md +437 -0
  101. package/plugins/specweave-n8n/commands/workflow-template.md +262 -0
  102. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +228 -6465
@@ -0,0 +1,304 @@
1
+ import { promises as fs } from 'fs';
2
+ import { existsSync } from 'fs';
3
+ import path from 'path';
4
+ import { consoleLogger } from '../../utils/logger.js';
5
+ /**
6
+ * CacheManager - Manages file-based caching with TTL validation
7
+ *
8
+ * Features:
9
+ * - 24-hour TTL by default (configurable)
10
+ * - Atomic writes (temp file + rename)
11
+ * - Corruption detection and auto-recovery
12
+ * - Per-project cache separation
13
+ * - Thread-safe operations
14
+ *
15
+ * Cache file format:
16
+ * {
17
+ * "data": <T>,
18
+ * "timestamp": 1700000000000,
19
+ * "ttl": 86400000
20
+ * }
21
+ */
22
+ export class CacheManager {
23
+ constructor(projectRoot, options = {}) {
24
+ this.defaultTTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
25
+ this.cacheDir = path.join(projectRoot, '.specweave', 'cache');
26
+ this.logger = options.logger ?? consoleLogger;
27
+ if (options.ttl) {
28
+ this.defaultTTL = options.ttl;
29
+ }
30
+ }
31
+ /**
32
+ * Get cached data if valid (within TTL)
33
+ *
34
+ * @param key Cache key (e.g., "jira-projects", "jira-BACKEND-deps")
35
+ * @returns Cached data or null if cache miss/expired/corrupted
36
+ */
37
+ async get(key) {
38
+ const filePath = this.getCachePath(key);
39
+ if (!existsSync(filePath)) {
40
+ this.logger.log(`Cache miss: ${key}`);
41
+ return null;
42
+ }
43
+ try {
44
+ const content = await fs.readFile(filePath, 'utf-8');
45
+ const cached = JSON.parse(content);
46
+ // Validate cache structure
47
+ if (!this.isValidCacheStructure(cached)) {
48
+ this.logger.error(`Invalid cache structure for ${key}, deleting...`);
49
+ await this.delete(key);
50
+ return null;
51
+ }
52
+ // Check TTL
53
+ if (!this.isValid(cached)) {
54
+ const age = Date.now() - cached.timestamp;
55
+ const ageHours = (age / (1000 * 60 * 60)).toFixed(1);
56
+ this.logger.log(`Cache expired: ${key} (age: ${ageHours}h, TTL: ${cached.ttl / (1000 * 60 * 60)}h)`);
57
+ // Don't delete expired cache immediately - might be used as stale fallback
58
+ return null;
59
+ }
60
+ const remaining = cached.timestamp + cached.ttl - Date.now();
61
+ const remainingHours = (remaining / (1000 * 60 * 60)).toFixed(1);
62
+ this.logger.log(`Cache hit: ${key} (TTL remaining: ${remainingHours}h)`);
63
+ return cached.data;
64
+ }
65
+ catch (error) {
66
+ // Corruption detected
67
+ this.logger.error(`Cache corruption detected for ${key}: ${error.message}`);
68
+ await this.logCacheError(key, error);
69
+ await this.delete(key);
70
+ return null;
71
+ }
72
+ }
73
+ /**
74
+ * Get cached data even if expired (stale cache fallback)
75
+ *
76
+ * Used for rate limit fallback: when API rate limit hit,
77
+ * use stale cache instead of failing
78
+ *
79
+ * @param key Cache key
80
+ * @returns Cached data or null if missing/corrupted (ignores TTL)
81
+ */
82
+ async getStale(key) {
83
+ const filePath = this.getCachePath(key);
84
+ if (!existsSync(filePath)) {
85
+ this.logger.log(`No stale cache available: ${key}`);
86
+ return null;
87
+ }
88
+ try {
89
+ const content = await fs.readFile(filePath, 'utf-8');
90
+ const cached = JSON.parse(content);
91
+ // Validate structure only (ignore TTL)
92
+ if (!this.isValidCacheStructure(cached)) {
93
+ this.logger.error(`Invalid cache structure for ${key}, deleting...`);
94
+ await this.delete(key);
95
+ return null;
96
+ }
97
+ const age = Date.now() - cached.timestamp;
98
+ const ageHours = (age / (1000 * 60 * 60)).toFixed(1);
99
+ const ttlHours = cached.ttl / (1000 * 60 * 60);
100
+ const expiredHoursAgo = (parseFloat(ageHours) - ttlHours).toFixed(1);
101
+ this.logger.warn(`Using stale cache: ${key} (age: ${ageHours}h, expired ${expiredHoursAgo}h ago)`);
102
+ return cached.data;
103
+ }
104
+ catch (error) {
105
+ this.logger.error(`Failed to read stale cache for ${key}: ${error.message}`);
106
+ return null;
107
+ }
108
+ }
109
+ /**
110
+ * Set cached data with current timestamp and TTL
111
+ *
112
+ * Uses atomic write pattern (temp file + rename) to prevent corruption
113
+ *
114
+ * @param key Cache key
115
+ * @param data Data to cache
116
+ * @param ttl Optional custom TTL (overrides default)
117
+ */
118
+ async set(key, data, ttl) {
119
+ await this.ensureCacheDir();
120
+ const filePath = this.getCachePath(key);
121
+ const tempPath = `${filePath}.tmp`;
122
+ const cached = {
123
+ data,
124
+ timestamp: Date.now(),
125
+ ttl: ttl ?? this.defaultTTL,
126
+ };
127
+ try {
128
+ // Write to temp file first (atomic write pattern)
129
+ await fs.writeFile(tempPath, JSON.stringify(cached, null, 2), 'utf-8');
130
+ // Rename temp file to final name (atomic operation)
131
+ await fs.rename(tempPath, filePath);
132
+ this.logger.log(`Cache set: ${key} (TTL: ${cached.ttl / (1000 * 60 * 60)}h)`);
133
+ }
134
+ catch (error) {
135
+ this.logger.error(`Failed to write cache for ${key}: ${error.message}`);
136
+ // Cleanup temp file if exists
137
+ if (existsSync(tempPath)) {
138
+ await fs.unlink(tempPath).catch(() => { });
139
+ }
140
+ throw error;
141
+ }
142
+ }
143
+ /**
144
+ * Delete cached data
145
+ *
146
+ * @param key Cache key
147
+ */
148
+ async delete(key) {
149
+ const filePath = this.getCachePath(key);
150
+ if (existsSync(filePath)) {
151
+ await fs.unlink(filePath);
152
+ this.logger.log(`Cache deleted: ${key}`);
153
+ }
154
+ }
155
+ /**
156
+ * Clear all cached data
157
+ */
158
+ async clearAll() {
159
+ if (!existsSync(this.cacheDir)) {
160
+ return;
161
+ }
162
+ const files = await fs.readdir(this.cacheDir);
163
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
164
+ for (const file of jsonFiles) {
165
+ const filePath = path.join(this.cacheDir, file);
166
+ await fs.unlink(filePath);
167
+ }
168
+ this.logger.log(`Cleared ${jsonFiles.length} cache files`);
169
+ }
170
+ /**
171
+ * Get cache statistics for monitoring
172
+ */
173
+ async getStats() {
174
+ const stats = {
175
+ totalFiles: 0,
176
+ totalSize: 0,
177
+ oldestCache: null,
178
+ oldestCacheAge: null,
179
+ providers: {},
180
+ };
181
+ if (!existsSync(this.cacheDir)) {
182
+ return stats;
183
+ }
184
+ const files = await fs.readdir(this.cacheDir);
185
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
186
+ let oldestTimestamp = Date.now();
187
+ let oldestFile = null;
188
+ for (const file of jsonFiles) {
189
+ const filePath = path.join(this.cacheDir, file);
190
+ const fileStats = await fs.stat(filePath);
191
+ stats.totalFiles++;
192
+ stats.totalSize += fileStats.size;
193
+ // Determine provider from filename (e.g., "jira-projects.json" → "jira")
194
+ const provider = file.split('-')[0];
195
+ if (!stats.providers[provider]) {
196
+ stats.providers[provider] = { files: 0, size: 0 };
197
+ }
198
+ stats.providers[provider].files++;
199
+ stats.providers[provider].size += fileStats.size;
200
+ // Find oldest cache
201
+ try {
202
+ const content = await fs.readFile(filePath, 'utf-8');
203
+ const cached = JSON.parse(content);
204
+ if (cached.timestamp < oldestTimestamp) {
205
+ oldestTimestamp = cached.timestamp;
206
+ oldestFile = file;
207
+ }
208
+ }
209
+ catch {
210
+ // Ignore corrupted files
211
+ }
212
+ }
213
+ if (oldestFile) {
214
+ stats.oldestCache = oldestFile;
215
+ stats.oldestCacheAge = (Date.now() - oldestTimestamp) / (1000 * 60 * 60); // hours
216
+ }
217
+ return stats;
218
+ }
219
+ /**
220
+ * Delete caches older than specified age
221
+ *
222
+ * @param maxAgeMs Maximum age in milliseconds
223
+ * @returns Count of deleted caches
224
+ */
225
+ async deleteOlderThan(maxAgeMs) {
226
+ if (!existsSync(this.cacheDir)) {
227
+ return 0;
228
+ }
229
+ const files = await fs.readdir(this.cacheDir);
230
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
231
+ let deletedCount = 0;
232
+ const cutoffTime = Date.now() - maxAgeMs;
233
+ for (const file of jsonFiles) {
234
+ const filePath = path.join(this.cacheDir, file);
235
+ try {
236
+ const content = await fs.readFile(filePath, 'utf-8');
237
+ const cached = JSON.parse(content);
238
+ if (cached.timestamp < cutoffTime) {
239
+ await fs.unlink(filePath);
240
+ deletedCount++;
241
+ this.logger.log(`Deleted old cache: ${file}`);
242
+ }
243
+ }
244
+ catch {
245
+ // Delete corrupted files too
246
+ await fs.unlink(filePath);
247
+ deletedCount++;
248
+ this.logger.log(`Deleted corrupted cache: ${file}`);
249
+ }
250
+ }
251
+ return deletedCount;
252
+ }
253
+ /**
254
+ * Check if cached data is still valid (within TTL)
255
+ */
256
+ isValid(cached) {
257
+ const now = Date.now();
258
+ const age = now - cached.timestamp;
259
+ return age < cached.ttl;
260
+ }
261
+ /**
262
+ * Validate cache structure has required fields
263
+ */
264
+ isValidCacheStructure(cached) {
265
+ return (cached &&
266
+ typeof cached === 'object' &&
267
+ 'data' in cached &&
268
+ 'timestamp' in cached &&
269
+ 'ttl' in cached &&
270
+ typeof cached.timestamp === 'number' &&
271
+ typeof cached.ttl === 'number');
272
+ }
273
+ /**
274
+ * Get full cache file path for a key
275
+ */
276
+ getCachePath(key) {
277
+ return path.join(this.cacheDir, `${key}.json`);
278
+ }
279
+ /**
280
+ * Ensure cache directory exists
281
+ */
282
+ async ensureCacheDir() {
283
+ if (!existsSync(this.cacheDir)) {
284
+ await fs.mkdir(this.cacheDir, { recursive: true });
285
+ }
286
+ }
287
+ /**
288
+ * Log cache errors to dedicated error log
289
+ */
290
+ async logCacheError(key, error) {
291
+ const logDir = path.join(path.dirname(this.cacheDir), 'logs');
292
+ const logPath = path.join(logDir, 'cache-errors.log');
293
+ try {
294
+ await fs.mkdir(logDir, { recursive: true });
295
+ const timestamp = new Date().toISOString();
296
+ const logEntry = `[${timestamp}] Cache error for ${key}: ${error.message}\n`;
297
+ await fs.appendFile(logPath, logEntry, 'utf-8');
298
+ }
299
+ catch {
300
+ // Ignore logging errors
301
+ }
302
+ }
303
+ }
304
+ //# sourceMappingURL=cache-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-manager.js","sourceRoot":"","sources":["../../../../src/core/cache/cache-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ import { Logger } from '../../utils/logger.js';
2
+ /**
3
+ * Rate limit response headers (JIRA, GitHub, ADO)
4
+ */
5
+ export interface RateLimitHeaders {
6
+ remaining?: number;
7
+ reset?: number;
8
+ retryAfter?: number;
9
+ }
10
+ /**
11
+ * Rate limit check result
12
+ */
13
+ export interface RateLimitCheckResult {
14
+ canProceed: boolean;
15
+ reason?: string;
16
+ retryAfter?: number;
17
+ remaining?: number;
18
+ }
19
+ /**
20
+ * RateLimitChecker - Detects API rate limits and prevents hitting limits
21
+ *
22
+ * Features:
23
+ * - Parses rate limit headers from JIRA, GitHub, ADO
24
+ * - Handles 429 (Too Many Requests) errors
25
+ * - Suggests using stale cache when rate limit low
26
+ * - Supports exponential backoff
27
+ *
28
+ * Supported headers:
29
+ * - X-RateLimit-Remaining (JIRA Cloud, GitHub)
30
+ * - X-RateLimit-Reset (JIRA Cloud, GitHub)
31
+ * - Retry-After (Standard HTTP, JIRA Server, ADO)
32
+ * - x-ms-ratelimit-remaining (Azure DevOps)
33
+ *
34
+ * Threshold: 10 requests remaining (configurable)
35
+ */
36
+ export declare class RateLimitChecker {
37
+ private logger;
38
+ private threshold;
39
+ constructor(options?: {
40
+ logger?: Logger;
41
+ threshold?: number;
42
+ });
43
+ /**
44
+ * Check if safe to proceed with API call based on response headers
45
+ *
46
+ * @param headers Response headers from previous API call
47
+ * @returns RateLimitCheckResult with canProceed flag
48
+ */
49
+ shouldProceed(headers: RateLimitHeaders): RateLimitCheckResult;
50
+ /**
51
+ * Handle 429 (Too Many Requests) error
52
+ *
53
+ * @param error Error object with response headers
54
+ * @returns Promise<void>
55
+ */
56
+ handleRateLimitError(error: any): Promise<void>;
57
+ /**
58
+ * Extract rate limit headers from HTTP response
59
+ *
60
+ * Supports multiple header formats:
61
+ * - JIRA Cloud: X-RateLimit-Remaining, X-RateLimit-Reset
62
+ * - GitHub: X-RateLimit-Remaining, X-RateLimit-Reset
63
+ * - ADO: x-ms-ratelimit-remaining-resource, Retry-After
64
+ * - Standard: Retry-After
65
+ *
66
+ * @param response HTTP response or response-like object
67
+ * @returns RateLimitHeaders object
68
+ */
69
+ extractHeaders(response: any): RateLimitHeaders;
70
+ /**
71
+ * Extract Retry-After from error response
72
+ *
73
+ * @param error Error object
74
+ * @returns Retry-After in seconds, or null
75
+ */
76
+ private extractRetryAfter;
77
+ /**
78
+ * Extract X-RateLimit-Reset from error response
79
+ *
80
+ * @param error Error object
81
+ * @returns Reset timestamp (Unix seconds), or null
82
+ */
83
+ private extractReset;
84
+ /**
85
+ * Check if error is a rate limit error (429 status)
86
+ *
87
+ * @param error Error object
88
+ * @returns True if 429 error
89
+ */
90
+ isRateLimitError(error: any): boolean;
91
+ }
92
+ //# sourceMappingURL=rate-limit-checker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit-checker.d.ts","sourceRoot":"","sources":["../../../../src/core/cache/rate-limit-checker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAiB,MAAM,uBAAuB,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;gBAEd,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO;IAKjE;;;;;OAKG;IACH,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,oBAAoB;IA4B9D;;;;;OAKG;IACG,oBAAoB,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBrD;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,QAAQ,EAAE,GAAG,GAAG,gBAAgB;IAsC/C;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAYpB;;;;;OAKG;IACH,gBAAgB,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO;CAGtC"}
@@ -0,0 +1,160 @@
1
+ import { consoleLogger } from '../../utils/logger.js';
2
+ /**
3
+ * RateLimitChecker - Detects API rate limits and prevents hitting limits
4
+ *
5
+ * Features:
6
+ * - Parses rate limit headers from JIRA, GitHub, ADO
7
+ * - Handles 429 (Too Many Requests) errors
8
+ * - Suggests using stale cache when rate limit low
9
+ * - Supports exponential backoff
10
+ *
11
+ * Supported headers:
12
+ * - X-RateLimit-Remaining (JIRA Cloud, GitHub)
13
+ * - X-RateLimit-Reset (JIRA Cloud, GitHub)
14
+ * - Retry-After (Standard HTTP, JIRA Server, ADO)
15
+ * - x-ms-ratelimit-remaining (Azure DevOps)
16
+ *
17
+ * Threshold: 10 requests remaining (configurable)
18
+ */
19
+ export class RateLimitChecker {
20
+ constructor(options = {}) {
21
+ this.logger = options.logger ?? consoleLogger;
22
+ this.threshold = options.threshold ?? 10; // Default: warn when < 10 requests remaining
23
+ }
24
+ /**
25
+ * Check if safe to proceed with API call based on response headers
26
+ *
27
+ * @param headers Response headers from previous API call
28
+ * @returns RateLimitCheckResult with canProceed flag
29
+ */
30
+ shouldProceed(headers) {
31
+ const remaining = headers.remaining;
32
+ // No rate limit headers = assume safe to proceed
33
+ if (remaining === undefined) {
34
+ return { canProceed: true };
35
+ }
36
+ // Low rate limit = use stale cache instead
37
+ if (remaining < this.threshold) {
38
+ const reason = `Rate limit low (${remaining} requests remaining, threshold: ${this.threshold})`;
39
+ this.logger.warn(reason);
40
+ this.logger.warn('Suggestion: Use stale cache to avoid hitting rate limit');
41
+ return {
42
+ canProceed: false,
43
+ reason,
44
+ remaining,
45
+ };
46
+ }
47
+ // Safe to proceed
48
+ return {
49
+ canProceed: true,
50
+ remaining,
51
+ };
52
+ }
53
+ /**
54
+ * Handle 429 (Too Many Requests) error
55
+ *
56
+ * @param error Error object with response headers
57
+ * @returns Promise<void>
58
+ */
59
+ async handleRateLimitError(error) {
60
+ const retryAfter = this.extractRetryAfter(error);
61
+ const reset = this.extractReset(error);
62
+ if (retryAfter) {
63
+ this.logger.error(`Rate limit exceeded. Retry after ${retryAfter} seconds.`);
64
+ this.logger.warn('Suggestion: Use stale cache while waiting for rate limit reset');
65
+ }
66
+ else if (reset) {
67
+ const waitTime = Math.max(0, reset - Date.now() / 1000);
68
+ this.logger.error(`Rate limit exceeded. Reset in ${Math.ceil(waitTime)} seconds.`);
69
+ this.logger.warn('Suggestion: Use stale cache while waiting for rate limit reset');
70
+ }
71
+ else {
72
+ this.logger.error('Rate limit exceeded (no retry time provided)');
73
+ this.logger.warn('Suggestion: Use stale cache and retry later');
74
+ }
75
+ }
76
+ /**
77
+ * Extract rate limit headers from HTTP response
78
+ *
79
+ * Supports multiple header formats:
80
+ * - JIRA Cloud: X-RateLimit-Remaining, X-RateLimit-Reset
81
+ * - GitHub: X-RateLimit-Remaining, X-RateLimit-Reset
82
+ * - ADO: x-ms-ratelimit-remaining-resource, Retry-After
83
+ * - Standard: Retry-After
84
+ *
85
+ * @param response HTTP response or response-like object
86
+ * @returns RateLimitHeaders object
87
+ */
88
+ extractHeaders(response) {
89
+ const headers = {};
90
+ // Handle different response formats (axios, fetch, etc.)
91
+ const getHeader = (name) => {
92
+ if (response.headers) {
93
+ // Axios-style headers object
94
+ if (typeof response.headers.get === 'function') {
95
+ return response.headers.get(name) ?? undefined;
96
+ }
97
+ // Plain object headers
98
+ return response.headers[name] ?? response.headers[name.toLowerCase()] ?? undefined;
99
+ }
100
+ return undefined;
101
+ };
102
+ // X-RateLimit-Remaining (JIRA Cloud, GitHub)
103
+ const remaining = getHeader('X-RateLimit-Remaining') || getHeader('x-ms-ratelimit-remaining-resource');
104
+ if (remaining) {
105
+ headers.remaining = parseInt(remaining, 10);
106
+ }
107
+ // X-RateLimit-Reset (JIRA Cloud, GitHub)
108
+ const reset = getHeader('X-RateLimit-Reset');
109
+ if (reset) {
110
+ headers.reset = parseInt(reset, 10);
111
+ }
112
+ // Retry-After (Standard HTTP, JIRA Server, ADO)
113
+ const retryAfter = getHeader('Retry-After');
114
+ if (retryAfter) {
115
+ headers.retryAfter = parseInt(retryAfter, 10);
116
+ }
117
+ return headers;
118
+ }
119
+ /**
120
+ * Extract Retry-After from error response
121
+ *
122
+ * @param error Error object
123
+ * @returns Retry-After in seconds, or null
124
+ */
125
+ extractRetryAfter(error) {
126
+ if (error.response?.headers) {
127
+ const retryAfter = error.response.headers['Retry-After'] || error.response.headers['retry-after'];
128
+ if (retryAfter) {
129
+ return parseInt(retryAfter, 10);
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+ /**
135
+ * Extract X-RateLimit-Reset from error response
136
+ *
137
+ * @param error Error object
138
+ * @returns Reset timestamp (Unix seconds), or null
139
+ */
140
+ extractReset(error) {
141
+ if (error.response?.headers) {
142
+ const reset = error.response.headers['X-RateLimit-Reset'] ||
143
+ error.response.headers['x-ratelimit-reset'];
144
+ if (reset) {
145
+ return parseInt(reset, 10);
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+ /**
151
+ * Check if error is a rate limit error (429 status)
152
+ *
153
+ * @param error Error object
154
+ * @returns True if 429 error
155
+ */
156
+ isRateLimitError(error) {
157
+ return error.response?.status === 429 || error.status === 429;
158
+ }
159
+ }
160
+ //# sourceMappingURL=rate-limit-checker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit-checker.js","sourceRoot":"","sources":["../../../../src/core/cache/rate-limit-checker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * CancelationHandler - Graceful Ctrl+C (SIGINT) handling with state persistence
3
+ *
4
+ * Features:
5
+ * - Single Ctrl+C: Save state and exit gracefully
6
+ * - Double Ctrl+C (within 2s): Force exit immediately
7
+ * - Polling mechanism for cooperative cancelation
8
+ * - State persistence to resume later
9
+ *
10
+ * @module core/progress/cancelation-handler
11
+ */
12
+ import type { Logger } from '../../utils/logger.js';
13
+ /**
14
+ * Cancelation handler options
15
+ */
16
+ export interface CancelationOptions {
17
+ /** State save callback - called when Ctrl+C is pressed */
18
+ onSaveState?: () => Promise<void>;
19
+ /** Logger instance */
20
+ logger?: Logger;
21
+ /** Force exit timeout in milliseconds (default: 2000ms) */
22
+ forceExitTimeout?: number;
23
+ }
24
+ /**
25
+ * CancelationHandler - Handles SIGINT gracefully with double Ctrl+C force exit
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const handler = new CancelationHandler({
30
+ * onSaveState: async () => {
31
+ * await saveImportState();
32
+ * }
33
+ * });
34
+ *
35
+ * handler.register();
36
+ *
37
+ * // In import loop
38
+ * for (const project of projects) {
39
+ * if (handler.shouldCancel()) {
40
+ * break;
41
+ * }
42
+ * await importProject(project);
43
+ * }
44
+ *
45
+ * handler.unregister();
46
+ * ```
47
+ */
48
+ export declare class CancelationHandler {
49
+ private logger;
50
+ private onSaveState?;
51
+ private forceExitTimeout;
52
+ private cancelRequested;
53
+ private firstCtrlCTime;
54
+ private sigintHandler;
55
+ constructor(options?: CancelationOptions);
56
+ /**
57
+ * Register SIGINT listener
58
+ */
59
+ register(): void;
60
+ /**
61
+ * Unregister SIGINT listener
62
+ */
63
+ unregister(): void;
64
+ /**
65
+ * Check if cancelation was requested (polling mechanism)
66
+ *
67
+ * @returns true if Ctrl+C was pressed
68
+ */
69
+ shouldCancel(): boolean;
70
+ /**
71
+ * Handle SIGINT signal (Ctrl+C)
72
+ */
73
+ private handleSigint;
74
+ /**
75
+ * Reset cancelation state (useful for testing)
76
+ */
77
+ reset(): void;
78
+ }
79
+ //# sourceMappingURL=cancelation-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cancelation-handler.d.ts","sourceRoot":"","sources":["../../../../src/core/progress/cancelation-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAGpD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAAC,CAAsB;IAC1C,OAAO,CAAC,gBAAgB,CAAS;IAEjC,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,aAAa,CAA6B;gBAEtC,OAAO,GAAE,kBAAuB;IAM5C;;OAEG;IACH,QAAQ,IAAI,IAAI;IAQhB;;OAEG;IACH,UAAU,IAAI,IAAI;IAOlB;;;;OAIG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;YACW,YAAY;IAiC1B;;OAEG;IACH,KAAK,IAAI,IAAI;CAId"}