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.
- package/README.md +1 -1
- package/dist/plugins/specweave-ado/lib/ado-ac-checkbox-sync.d.ts +13 -5
- package/dist/plugins/specweave-ado/lib/ado-ac-checkbox-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-ac-checkbox-sync.js +28 -26
- package/dist/plugins/specweave-ado/lib/ado-ac-checkbox-sync.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-client.d.ts +10 -0
- package/dist/plugins/specweave-ado/lib/ado-client.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-client.js +14 -0
- package/dist/plugins/specweave-ado/lib/ado-client.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-pull-sync.d.ts +35 -0
- package/dist/plugins/specweave-ado/lib/ado-pull-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-pull-sync.js +53 -0
- package/dist/plugins/specweave-ado/lib/ado-pull-sync.js.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-rate-limiter.d.ts +46 -0
- package/dist/plugins/specweave-ado/lib/ado-rate-limiter.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-rate-limiter.js +65 -0
- package/dist/plugins/specweave-ado/lib/ado-rate-limiter.js.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts +7 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.js +25 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-sync.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts +17 -1
- package/dist/plugins/specweave-ado/lib/ado-status-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-status-sync.js +51 -9
- package/dist/plugins/specweave-ado/lib/ado-status-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-push-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-push-sync.js +15 -3
- package/dist/plugins/specweave-github/lib/github-push-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts +31 -1
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.js +170 -97
- package/dist/plugins/specweave-jira/lib/jira-spec-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +36 -1
- package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js +185 -82
- package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -1
- package/dist/src/adapters/adapter-loader.d.ts.map +1 -1
- package/dist/src/adapters/adapter-loader.js +8 -2
- package/dist/src/adapters/adapter-loader.js.map +1 -1
- package/dist/src/adapters/codex/adapter.d.ts.map +1 -1
- package/dist/src/adapters/codex/adapter.js +1 -0
- package/dist/src/adapters/codex/adapter.js.map +1 -1
- package/dist/src/adapters/cursor/adapter.d.ts.map +1 -1
- package/dist/src/adapters/cursor/adapter.js +1 -0
- package/dist/src/adapters/cursor/adapter.js.map +1 -1
- package/dist/src/adapters/generic/adapter.d.ts +6 -3
- package/dist/src/adapters/generic/adapter.d.ts.map +1 -1
- package/dist/src/adapters/generic/adapter.js +53 -47
- package/dist/src/adapters/generic/adapter.js.map +1 -1
- package/dist/src/adapters/kimi/adapter.d.ts +21 -0
- package/dist/src/adapters/kimi/adapter.d.ts.map +1 -0
- package/dist/src/adapters/kimi/adapter.js +57 -0
- package/dist/src/adapters/kimi/adapter.js.map +1 -0
- package/dist/src/adapters/opencode/adapter.d.ts +24 -0
- package/dist/src/adapters/opencode/adapter.d.ts.map +1 -0
- package/dist/src/adapters/opencode/adapter.js +71 -0
- package/dist/src/adapters/opencode/adapter.js.map +1 -0
- package/dist/src/adapters/registry.yaml +59 -0
- package/dist/src/adapters/trae/adapter.d.ts +21 -0
- package/dist/src/adapters/trae/adapter.d.ts.map +1 -0
- package/dist/src/adapters/trae/adapter.js +64 -0
- package/dist/src/adapters/trae/adapter.js.map +1 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +156 -5
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/update-instructions.d.ts.map +1 -1
- package/dist/src/cli/commands/update-instructions.js +10 -0
- package/dist/src/cli/commands/update-instructions.js.map +1 -1
- package/dist/src/cli/helpers/init/index.d.ts +1 -0
- package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/index.js +2 -0
- package/dist/src/cli/helpers/init/index.js.map +1 -1
- package/dist/src/cli/helpers/init/next-steps.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/next-steps.js +52 -0
- package/dist/src/cli/helpers/init/next-steps.js.map +1 -1
- package/dist/src/cli/helpers/init/skill-creator-installer.d.ts +24 -0
- package/dist/src/cli/helpers/init/skill-creator-installer.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/skill-creator-installer.js +54 -0
- package/dist/src/cli/helpers/init/skill-creator-installer.js.map +1 -0
- package/dist/src/core/ado-description-updater.d.ts +22 -0
- package/dist/src/core/ado-description-updater.d.ts.map +1 -0
- package/dist/src/core/ado-description-updater.js +46 -0
- package/dist/src/core/ado-description-updater.js.map +1 -0
- package/dist/src/core/closure-dispatcher.d.ts +96 -0
- package/dist/src/core/closure-dispatcher.d.ts.map +1 -0
- package/dist/src/core/closure-dispatcher.js +116 -0
- package/dist/src/core/closure-dispatcher.js.map +1 -0
- package/dist/src/core/config/types.d.ts +2 -0
- package/dist/src/core/config/types.d.ts.map +1 -1
- package/dist/src/core/config/types.js.map +1 -1
- package/dist/src/core/errors/sync-error.d.ts +12 -0
- package/dist/src/core/errors/sync-error.d.ts.map +1 -0
- package/dist/src/core/errors/sync-error.js +19 -0
- package/dist/src/core/errors/sync-error.js.map +1 -0
- package/dist/src/core/skill-gen/rule-collector.d.ts +28 -0
- package/dist/src/core/skill-gen/rule-collector.d.ts.map +1 -0
- package/dist/src/core/skill-gen/rule-collector.js +112 -0
- package/dist/src/core/skill-gen/rule-collector.js.map +1 -0
- package/dist/src/core/skill-gen/signal-collector.d.ts +2 -1
- package/dist/src/core/skill-gen/signal-collector.d.ts.map +1 -1
- package/dist/src/core/skill-gen/signal-collector.js +21 -5
- package/dist/src/core/skill-gen/signal-collector.js.map +1 -1
- package/dist/src/core/sync/persistent-circuit-breaker.d.ts +22 -0
- package/dist/src/core/sync/persistent-circuit-breaker.d.ts.map +1 -0
- package/dist/src/core/sync/persistent-circuit-breaker.js +65 -0
- package/dist/src/core/sync/persistent-circuit-breaker.js.map +1 -0
- package/dist/src/core/sync/retry-wrapper.d.ts +13 -0
- package/dist/src/core/sync/retry-wrapper.d.ts.map +1 -0
- package/dist/src/core/sync/retry-wrapper.js +37 -0
- package/dist/src/core/sync/retry-wrapper.js.map +1 -0
- package/dist/src/importers/ac-parser.d.ts +27 -0
- package/dist/src/importers/ac-parser.d.ts.map +1 -0
- package/dist/src/importers/ac-parser.js +47 -0
- package/dist/src/importers/ac-parser.js.map +1 -0
- package/dist/src/sync/types.d.ts +8 -0
- package/dist/src/sync/types.d.ts.map +1 -1
- package/dist/src/sync/types.js +12 -0
- package/dist/src/sync/types.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/skills/code-reviewer/agents/reviewer-silent-failures.md +65 -0
- package/plugins/specweave/skills/code-reviewer/agents/reviewer-spec-compliance.md +83 -0
- package/plugins/specweave/skills/code-reviewer/agents/reviewer-types.md +68 -0
- package/plugins/specweave/skills/skill-gen/SKILL.md +20 -3
- package/plugins/specweave/skills/team-lead/agents/architect.md +52 -0
- package/plugins/specweave/skills/team-lead/agents/pm.md +50 -0
- package/plugins/specweave/skills/team-lead/agents/researcher.md +64 -0
- package/plugins/specweave-ado/lib/ado-ac-checkbox-sync.js +23 -21
- package/plugins/specweave-ado/lib/ado-ac-checkbox-sync.ts +37 -29
- package/plugins/specweave-ado/lib/ado-client.js +14 -0
- package/plugins/specweave-ado/lib/ado-client.ts +18 -0
- package/plugins/specweave-ado/lib/ado-pull-sync.js +35 -0
- package/plugins/specweave-ado/lib/ado-pull-sync.ts +74 -0
- package/plugins/specweave-ado/lib/ado-rate-limiter.js +56 -0
- package/plugins/specweave-ado/lib/ado-rate-limiter.ts +86 -0
- package/plugins/specweave-ado/lib/ado-spec-sync.js +25 -1
- package/plugins/specweave-ado/lib/ado-spec-sync.ts +32 -2
- package/plugins/specweave-ado/lib/ado-status-sync.js +52 -14
- package/plugins/specweave-ado/lib/ado-status-sync.ts +64 -16
- package/plugins/specweave-github/lib/github-client-v2.ts +1 -1
- package/plugins/specweave-github/lib/github-push-sync.js +11 -3
- package/plugins/specweave-github/lib/github-push-sync.ts +16 -3
- package/plugins/specweave-jira/lib/jira-spec-sync.js +60 -1
- package/plugins/specweave-jira/lib/jira-spec-sync.ts +93 -1
- package/plugins/specweave-jira/lib/jira-status-sync.js +151 -109
- 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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
270
|
+
const body = toCommentBody(rawBody, this.domain);
|
|
158
271
|
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|