specweave 1.0.488 → 1.0.489

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 (147) hide show
  1. package/README.md +1 -1
  2. package/dist/plugins/specweave-ado/lib/ado-ac-checkbox-sync.d.ts +13 -5
  3. package/dist/plugins/specweave-ado/lib/ado-ac-checkbox-sync.d.ts.map +1 -1
  4. package/dist/plugins/specweave-ado/lib/ado-ac-checkbox-sync.js +28 -26
  5. package/dist/plugins/specweave-ado/lib/ado-ac-checkbox-sync.js.map +1 -1
  6. package/dist/plugins/specweave-ado/lib/ado-client.d.ts +10 -0
  7. package/dist/plugins/specweave-ado/lib/ado-client.d.ts.map +1 -1
  8. package/dist/plugins/specweave-ado/lib/ado-client.js +14 -0
  9. package/dist/plugins/specweave-ado/lib/ado-client.js.map +1 -1
  10. package/dist/plugins/specweave-ado/lib/ado-pull-sync.d.ts +35 -0
  11. package/dist/plugins/specweave-ado/lib/ado-pull-sync.d.ts.map +1 -0
  12. package/dist/plugins/specweave-ado/lib/ado-pull-sync.js +53 -0
  13. package/dist/plugins/specweave-ado/lib/ado-pull-sync.js.map +1 -0
  14. package/dist/plugins/specweave-ado/lib/ado-rate-limiter.d.ts +46 -0
  15. package/dist/plugins/specweave-ado/lib/ado-rate-limiter.d.ts.map +1 -0
  16. package/dist/plugins/specweave-ado/lib/ado-rate-limiter.js +65 -0
  17. package/dist/plugins/specweave-ado/lib/ado-rate-limiter.js.map +1 -0
  18. package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts +7 -1
  19. package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts.map +1 -1
  20. package/dist/plugins/specweave-ado/lib/ado-spec-sync.js +25 -1
  21. package/dist/plugins/specweave-ado/lib/ado-spec-sync.js.map +1 -1
  22. package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts +17 -1
  23. package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts.map +1 -1
  24. package/dist/plugins/specweave-ado/lib/ado-status-sync.js +51 -9
  25. package/dist/plugins/specweave-ado/lib/ado-status-sync.js.map +1 -1
  26. package/dist/plugins/specweave-github/lib/github-client-v2.js +1 -1
  27. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  28. package/dist/plugins/specweave-github/lib/github-push-sync.d.ts.map +1 -1
  29. package/dist/plugins/specweave-github/lib/github-push-sync.js +15 -3
  30. package/dist/plugins/specweave-github/lib/github-push-sync.js.map +1 -1
  31. package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts +31 -1
  32. package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts.map +1 -1
  33. package/dist/plugins/specweave-jira/lib/jira-spec-sync.js +170 -97
  34. package/dist/plugins/specweave-jira/lib/jira-spec-sync.js.map +1 -1
  35. package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +36 -1
  36. package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -1
  37. package/dist/plugins/specweave-jira/lib/jira-status-sync.js +185 -82
  38. package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -1
  39. package/dist/src/adapters/adapter-loader.d.ts.map +1 -1
  40. package/dist/src/adapters/adapter-loader.js +8 -2
  41. package/dist/src/adapters/adapter-loader.js.map +1 -1
  42. package/dist/src/adapters/codex/adapter.d.ts.map +1 -1
  43. package/dist/src/adapters/codex/adapter.js +1 -0
  44. package/dist/src/adapters/codex/adapter.js.map +1 -1
  45. package/dist/src/adapters/cursor/adapter.d.ts.map +1 -1
  46. package/dist/src/adapters/cursor/adapter.js +1 -0
  47. package/dist/src/adapters/cursor/adapter.js.map +1 -1
  48. package/dist/src/adapters/generic/adapter.d.ts +6 -3
  49. package/dist/src/adapters/generic/adapter.d.ts.map +1 -1
  50. package/dist/src/adapters/generic/adapter.js +53 -47
  51. package/dist/src/adapters/generic/adapter.js.map +1 -1
  52. package/dist/src/adapters/kimi/adapter.d.ts +21 -0
  53. package/dist/src/adapters/kimi/adapter.d.ts.map +1 -0
  54. package/dist/src/adapters/kimi/adapter.js +57 -0
  55. package/dist/src/adapters/kimi/adapter.js.map +1 -0
  56. package/dist/src/adapters/opencode/adapter.d.ts +24 -0
  57. package/dist/src/adapters/opencode/adapter.d.ts.map +1 -0
  58. package/dist/src/adapters/opencode/adapter.js +71 -0
  59. package/dist/src/adapters/opencode/adapter.js.map +1 -0
  60. package/dist/src/adapters/registry.yaml +59 -0
  61. package/dist/src/adapters/trae/adapter.d.ts +21 -0
  62. package/dist/src/adapters/trae/adapter.d.ts.map +1 -0
  63. package/dist/src/adapters/trae/adapter.js +64 -0
  64. package/dist/src/adapters/trae/adapter.js.map +1 -0
  65. package/dist/src/cli/commands/init.d.ts.map +1 -1
  66. package/dist/src/cli/commands/init.js +156 -5
  67. package/dist/src/cli/commands/init.js.map +1 -1
  68. package/dist/src/cli/commands/update-instructions.d.ts.map +1 -1
  69. package/dist/src/cli/commands/update-instructions.js +10 -0
  70. package/dist/src/cli/commands/update-instructions.js.map +1 -1
  71. package/dist/src/cli/helpers/init/index.d.ts +1 -0
  72. package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
  73. package/dist/src/cli/helpers/init/index.js +2 -0
  74. package/dist/src/cli/helpers/init/index.js.map +1 -1
  75. package/dist/src/cli/helpers/init/next-steps.d.ts.map +1 -1
  76. package/dist/src/cli/helpers/init/next-steps.js +52 -0
  77. package/dist/src/cli/helpers/init/next-steps.js.map +1 -1
  78. package/dist/src/cli/helpers/init/skill-creator-installer.d.ts +24 -0
  79. package/dist/src/cli/helpers/init/skill-creator-installer.d.ts.map +1 -0
  80. package/dist/src/cli/helpers/init/skill-creator-installer.js +54 -0
  81. package/dist/src/cli/helpers/init/skill-creator-installer.js.map +1 -0
  82. package/dist/src/core/ado-description-updater.d.ts +22 -0
  83. package/dist/src/core/ado-description-updater.d.ts.map +1 -0
  84. package/dist/src/core/ado-description-updater.js +46 -0
  85. package/dist/src/core/ado-description-updater.js.map +1 -0
  86. package/dist/src/core/closure-dispatcher.d.ts +96 -0
  87. package/dist/src/core/closure-dispatcher.d.ts.map +1 -0
  88. package/dist/src/core/closure-dispatcher.js +116 -0
  89. package/dist/src/core/closure-dispatcher.js.map +1 -0
  90. package/dist/src/core/config/types.d.ts +2 -0
  91. package/dist/src/core/config/types.d.ts.map +1 -1
  92. package/dist/src/core/config/types.js.map +1 -1
  93. package/dist/src/core/errors/sync-error.d.ts +12 -0
  94. package/dist/src/core/errors/sync-error.d.ts.map +1 -0
  95. package/dist/src/core/errors/sync-error.js +19 -0
  96. package/dist/src/core/errors/sync-error.js.map +1 -0
  97. package/dist/src/core/skill-gen/rule-collector.d.ts +28 -0
  98. package/dist/src/core/skill-gen/rule-collector.d.ts.map +1 -0
  99. package/dist/src/core/skill-gen/rule-collector.js +112 -0
  100. package/dist/src/core/skill-gen/rule-collector.js.map +1 -0
  101. package/dist/src/core/skill-gen/signal-collector.d.ts +2 -1
  102. package/dist/src/core/skill-gen/signal-collector.d.ts.map +1 -1
  103. package/dist/src/core/skill-gen/signal-collector.js +21 -5
  104. package/dist/src/core/skill-gen/signal-collector.js.map +1 -1
  105. package/dist/src/core/sync/persistent-circuit-breaker.d.ts +22 -0
  106. package/dist/src/core/sync/persistent-circuit-breaker.d.ts.map +1 -0
  107. package/dist/src/core/sync/persistent-circuit-breaker.js +65 -0
  108. package/dist/src/core/sync/persistent-circuit-breaker.js.map +1 -0
  109. package/dist/src/core/sync/retry-wrapper.d.ts +13 -0
  110. package/dist/src/core/sync/retry-wrapper.d.ts.map +1 -0
  111. package/dist/src/core/sync/retry-wrapper.js +37 -0
  112. package/dist/src/core/sync/retry-wrapper.js.map +1 -0
  113. package/dist/src/importers/ac-parser.d.ts +27 -0
  114. package/dist/src/importers/ac-parser.d.ts.map +1 -0
  115. package/dist/src/importers/ac-parser.js +47 -0
  116. package/dist/src/importers/ac-parser.js.map +1 -0
  117. package/dist/src/sync/types.d.ts +8 -0
  118. package/dist/src/sync/types.d.ts.map +1 -1
  119. package/dist/src/sync/types.js +12 -0
  120. package/dist/src/sync/types.js.map +1 -1
  121. package/package.json +1 -1
  122. package/plugins/specweave/skills/code-reviewer/agents/reviewer-silent-failures.md +65 -0
  123. package/plugins/specweave/skills/code-reviewer/agents/reviewer-spec-compliance.md +83 -0
  124. package/plugins/specweave/skills/code-reviewer/agents/reviewer-types.md +68 -0
  125. package/plugins/specweave/skills/skill-gen/SKILL.md +20 -3
  126. package/plugins/specweave/skills/team-lead/agents/architect.md +52 -0
  127. package/plugins/specweave/skills/team-lead/agents/pm.md +50 -0
  128. package/plugins/specweave/skills/team-lead/agents/researcher.md +64 -0
  129. package/plugins/specweave-ado/lib/ado-ac-checkbox-sync.js +23 -21
  130. package/plugins/specweave-ado/lib/ado-ac-checkbox-sync.ts +37 -29
  131. package/plugins/specweave-ado/lib/ado-client.js +14 -0
  132. package/plugins/specweave-ado/lib/ado-client.ts +18 -0
  133. package/plugins/specweave-ado/lib/ado-pull-sync.js +35 -0
  134. package/plugins/specweave-ado/lib/ado-pull-sync.ts +74 -0
  135. package/plugins/specweave-ado/lib/ado-rate-limiter.js +56 -0
  136. package/plugins/specweave-ado/lib/ado-rate-limiter.ts +86 -0
  137. package/plugins/specweave-ado/lib/ado-spec-sync.js +25 -1
  138. package/plugins/specweave-ado/lib/ado-spec-sync.ts +32 -2
  139. package/plugins/specweave-ado/lib/ado-status-sync.js +52 -14
  140. package/plugins/specweave-ado/lib/ado-status-sync.ts +64 -16
  141. package/plugins/specweave-github/lib/github-client-v2.ts +1 -1
  142. package/plugins/specweave-github/lib/github-push-sync.js +11 -3
  143. package/plugins/specweave-github/lib/github-push-sync.ts +16 -3
  144. package/plugins/specweave-jira/lib/jira-spec-sync.js +60 -1
  145. package/plugins/specweave-jira/lib/jira-spec-sync.ts +93 -1
  146. package/plugins/specweave-jira/lib/jira-status-sync.js +151 -109
  147. package/plugins/specweave-jira/lib/jira-status-sync.ts +161 -39
@@ -14,6 +14,10 @@
14
14
  import axios, { AxiosInstance } from 'axios';
15
15
  import { detectDeploymentType, getApiBaseUrl } from './jira-deployment-detector.js';
16
16
  import { toCommentBody, type AdfDocument, type AdfNode } from './content-format-adapter.js';
17
+ import { CircuitBreakerRegistry } from '../../../src/core/sync/circuit-breaker-registry.js';
18
+ import { SyncRetryQueue } from '../../../src/core/sync/sync-retry-queue.js';
19
+ import { SyncError } from '../../../src/core/errors/sync-error.js';
20
+ import { LockManager } from '../../../src/utils/lock-manager.js';
17
21
 
18
22
  /**
19
23
  * External status representation (JIRA-specific)
@@ -34,6 +38,16 @@ interface JiraTransition {
34
38
  };
35
39
  }
36
40
 
41
+ export interface JiraStatusSyncOptions {
42
+ circuitBreakerRegistry?: CircuitBreakerRegistry;
43
+ retryQueue?: SyncRetryQueue;
44
+ incrementId?: string;
45
+ featureId?: string;
46
+ projectPath?: string;
47
+ projectName?: string;
48
+ lockDir?: string;
49
+ }
50
+
37
51
  /**
38
52
  * JIRA Status Sync
39
53
  *
@@ -43,15 +57,32 @@ export class JiraStatusSync {
43
57
  private client: AxiosInstance;
44
58
  private domain: string;
45
59
  private projectKey: string;
60
+ private circuitBreakerRegistry?: CircuitBreakerRegistry;
61
+ private retryQueue?: SyncRetryQueue;
62
+ private lockManager?: LockManager;
63
+ private incrementId: string;
64
+ private featureId: string;
65
+ private projectPath: string;
66
+ private projectName: string;
46
67
 
47
68
  constructor(
48
69
  domain: string,
49
70
  email: string,
50
71
  apiToken: string,
51
- projectKey: string
72
+ projectKey: string,
73
+ options?: JiraStatusSyncOptions,
52
74
  ) {
53
75
  this.domain = domain;
54
76
  this.projectKey = projectKey;
77
+ this.circuitBreakerRegistry = options?.circuitBreakerRegistry;
78
+ this.retryQueue = options?.retryQueue;
79
+ this.incrementId = options?.incrementId ?? '';
80
+ this.featureId = options?.featureId ?? '';
81
+ this.projectPath = options?.projectPath ?? '';
82
+ this.projectName = options?.projectName ?? '';
83
+ if (options?.lockDir) {
84
+ this.lockManager = new LockManager(options.lockDir);
85
+ }
55
86
 
56
87
  // Create JIRA API client — baseURL set dynamically via init()
57
88
  this.client = axios.create({
@@ -67,6 +98,70 @@ export class JiraStatusSync {
67
98
  });
68
99
  }
69
100
 
101
+ /**
102
+ * Execute fn under file lock (if lockManager configured).
103
+ */
104
+ private async withLock<T>(fn: () => Promise<T>): Promise<T> {
105
+ if (!this.lockManager) return fn();
106
+ const acquired = await this.lockManager.acquire();
107
+ if (!acquired) {
108
+ throw new SyncError('jira', 0, '', 'Failed to acquire JIRA sync lock');
109
+ }
110
+ try {
111
+ return await fn();
112
+ } finally {
113
+ await this.lockManager.release();
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Check circuit breaker before making API calls.
119
+ * Throws if breaker is open.
120
+ */
121
+ private checkCircuitBreaker(): void {
122
+ if (!this.circuitBreakerRegistry) return;
123
+ const breaker = this.circuitBreakerRegistry.get('jira');
124
+ if (!breaker.canSync()) {
125
+ throw new SyncError('jira', 0, '', 'Circuit breaker open for jira');
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Record success on circuit breaker.
131
+ */
132
+ private recordSuccess(): void {
133
+ if (!this.circuitBreakerRegistry) return;
134
+ this.circuitBreakerRegistry.get('jira').recordSuccess();
135
+ }
136
+
137
+ /**
138
+ * Handle API error: record failure on circuit breaker, enqueue retry, throw SyncError.
139
+ */
140
+ private async handleApiError(error: unknown, operation: string): Promise<never> {
141
+ const httpStatus = (error as any)?.response?.status ?? 0;
142
+ const responseBody = JSON.stringify((error as any)?.response?.data ?? '');
143
+ const detail = (error as Error)?.message ?? String(error);
144
+
145
+ // Record failure on circuit breaker
146
+ if (this.circuitBreakerRegistry) {
147
+ this.circuitBreakerRegistry.get('jira').recordFailure();
148
+ }
149
+
150
+ // Enqueue for retry on server errors (5xx)
151
+ if (this.retryQueue && httpStatus >= 500) {
152
+ await this.retryQueue.enqueue({
153
+ incrementId: this.incrementId,
154
+ provider: 'jira',
155
+ featureId: this.featureId,
156
+ projectPath: this.projectPath,
157
+ projectName: this.projectName,
158
+ error: `${httpStatus} ${operation}: ${detail}`,
159
+ });
160
+ }
161
+
162
+ throw new SyncError('jira', httpStatus, responseBody, detail);
163
+ }
164
+
70
165
  /**
71
166
  * Initialize: detect deployment type and update client baseURL
72
167
  */
@@ -85,11 +180,18 @@ export class JiraStatusSync {
85
180
  * @returns Current issue status
86
181
  */
87
182
  async getStatus(issueKey: string): Promise<ExternalStatus> {
88
- const response = await this.client.get(`/issue/${issueKey}`);
89
-
90
- return {
91
- state: response.data.fields.status.name
92
- };
183
+ return this.withLock(async () => {
184
+ this.checkCircuitBreaker();
185
+ try {
186
+ const response = await this.client.get(`/issue/${issueKey}`);
187
+ this.recordSuccess();
188
+ return {
189
+ state: response.data.fields.status.name
190
+ };
191
+ } catch (error) {
192
+ return this.handleApiError(error, 'getStatus');
193
+ }
194
+ });
93
195
  }
94
196
 
95
197
  /**
@@ -106,33 +208,41 @@ export class JiraStatusSync {
106
208
  * @returns true if transition succeeded, false if not available
107
209
  */
108
210
  async updateStatus(issueKey: string, status: ExternalStatus): Promise<boolean> {
109
- // 1. Get available transitions for this issue
110
- const transitionsResponse = await this.client.get(`/issue/${issueKey}/transitions`);
111
- const transitions: JiraTransition[] = transitionsResponse.data.transitions;
112
-
113
- // 2. Find transition that leads to desired status (case-insensitive)
114
- const targetTransition = transitions.find(
115
- (t) => t.to.name.toLowerCase() === status.state.toLowerCase()
116
- );
117
-
118
- if (!targetTransition) {
119
- // Log warning instead of throwing - workflow may not support this transition
120
- console.warn(
121
- `⚠️ Cannot transition ${issueKey} to "${status.state}". ` +
122
- `Available transitions: ${transitions.map(t => t.to.name).join(', ')}. ` +
123
- `This may be expected if your JIRA workflow doesn't support this status.`
124
- );
125
- return false;
126
- }
211
+ return this.withLock(async () => {
212
+ this.checkCircuitBreaker();
213
+ try {
214
+ // 1. Get available transitions for this issue
215
+ const transitionsResponse = await this.client.get(`/issue/${issueKey}/transitions`);
216
+ const transitions: JiraTransition[] = transitionsResponse.data.transitions;
217
+
218
+ // 2. Find transition that leads to desired status (case-insensitive)
219
+ const targetTransition = transitions.find(
220
+ (t) => t.to.name.toLowerCase() === status.state.toLowerCase()
221
+ );
222
+
223
+ if (!targetTransition) {
224
+ // Log warning instead of throwing - workflow may not support this transition
225
+ console.warn(
226
+ `⚠️ Cannot transition ${issueKey} to "${status.state}". ` +
227
+ `Available transitions: ${transitions.map(t => t.to.name).join(', ')}. ` +
228
+ `This may be expected if your JIRA workflow doesn't support this status.`
229
+ );
230
+ return false;
231
+ }
127
232
 
128
- // 3. Execute transition
129
- await this.client.post(`/issue/${issueKey}/transitions`, {
130
- transition: {
131
- id: targetTransition.id
233
+ // 3. Execute transition
234
+ await this.client.post(`/issue/${issueKey}/transitions`, {
235
+ transition: {
236
+ id: targetTransition.id
237
+ }
238
+ });
239
+
240
+ this.recordSuccess();
241
+ return true;
242
+ } catch (error) {
243
+ return this.handleApiError(error, 'updateStatus');
132
244
  }
133
245
  });
134
-
135
- return true;
136
246
  }
137
247
 
138
248
  /**
@@ -147,17 +257,25 @@ export class JiraStatusSync {
147
257
  oldStatus: string,
148
258
  newStatus: string
149
259
  ): Promise<void> {
150
- const rawBody = `*Status Update*\n\n` +
151
- `SpecWeave status changed:\n` +
152
- `* *From*: ${oldStatus}\n` +
153
- `* *To*: ${newStatus}\n` +
154
- `* *When*: ${new Date().toISOString()}\n\n` +
155
- `_Synced from SpecWeave_`;
260
+ return this.withLock(async () => {
261
+ this.checkCircuitBreaker();
262
+ try {
263
+ const rawBody = `*Status Update*\n\n` +
264
+ `SpecWeave status changed:\n` +
265
+ `* *From*: ${oldStatus}\n` +
266
+ `* *To*: ${newStatus}\n` +
267
+ `* *When*: ${new Date().toISOString()}\n\n` +
268
+ `_Synced from SpecWeave_`;
156
269
 
157
- const body = toCommentBody(rawBody, this.domain);
270
+ const body = toCommentBody(rawBody, this.domain);
158
271
 
159
- await this.client.post(`/issue/${issueKey}/comment`, {
160
- body
272
+ await this.client.post(`/issue/${issueKey}/comment`, {
273
+ body
274
+ });
275
+ this.recordSuccess();
276
+ } catch (error) {
277
+ return this.handleApiError(error, 'postStatusComment');
278
+ }
161
279
  });
162
280
  }
163
281
 
@@ -177,6 +295,8 @@ export class JiraStatusSync {
177
295
  issueKey: string,
178
296
  acStates: Array<{ id: string; description: string; completed: boolean }>,
179
297
  ): Promise<boolean> {
298
+ return this.withLock(async () => {
299
+ this.checkCircuitBreaker();
180
300
  const total = acStates.length;
181
301
  const completed = acStates.filter(ac => ac.completed).length;
182
302
  const percentage = Math.round((completed / total) * 100);
@@ -232,7 +352,9 @@ export class JiraStatusSync {
232
352
  };
233
353
 
234
354
  await this.client.post(`/issue/${issueKey}/comment`, { body });
355
+ this.recordSuccess();
235
356
  return true;
357
+ });
236
358
  }
237
359
  }
238
360