@link-assistant/hive-mind 0.39.0
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/CHANGELOG.md +20 -0
- package/LICENSE +24 -0
- package/README.md +769 -0
- package/package.json +58 -0
- package/src/agent.lib.mjs +705 -0
- package/src/agent.prompts.lib.mjs +196 -0
- package/src/buildUserMention.lib.mjs +71 -0
- package/src/claude-limits.lib.mjs +389 -0
- package/src/claude.lib.mjs +1445 -0
- package/src/claude.prompts.lib.mjs +203 -0
- package/src/codex.lib.mjs +552 -0
- package/src/codex.prompts.lib.mjs +194 -0
- package/src/config.lib.mjs +207 -0
- package/src/contributing-guidelines.lib.mjs +268 -0
- package/src/exit-handler.lib.mjs +205 -0
- package/src/git.lib.mjs +145 -0
- package/src/github-issue-creator.lib.mjs +246 -0
- package/src/github-linking.lib.mjs +152 -0
- package/src/github.batch.lib.mjs +272 -0
- package/src/github.graphql.lib.mjs +258 -0
- package/src/github.lib.mjs +1479 -0
- package/src/hive.config.lib.mjs +254 -0
- package/src/hive.mjs +1500 -0
- package/src/instrument.mjs +191 -0
- package/src/interactive-mode.lib.mjs +1000 -0
- package/src/lenv-reader.lib.mjs +206 -0
- package/src/lib.mjs +490 -0
- package/src/lino.lib.mjs +176 -0
- package/src/local-ci-checks.lib.mjs +324 -0
- package/src/memory-check.mjs +419 -0
- package/src/model-mapping.lib.mjs +145 -0
- package/src/model-validation.lib.mjs +278 -0
- package/src/opencode.lib.mjs +479 -0
- package/src/opencode.prompts.lib.mjs +194 -0
- package/src/protect-branch.mjs +159 -0
- package/src/review.mjs +433 -0
- package/src/reviewers-hive.mjs +643 -0
- package/src/sentry.lib.mjs +284 -0
- package/src/solve.auto-continue.lib.mjs +568 -0
- package/src/solve.auto-pr.lib.mjs +1374 -0
- package/src/solve.branch-errors.lib.mjs +341 -0
- package/src/solve.branch.lib.mjs +230 -0
- package/src/solve.config.lib.mjs +342 -0
- package/src/solve.error-handlers.lib.mjs +256 -0
- package/src/solve.execution.lib.mjs +291 -0
- package/src/solve.feedback.lib.mjs +436 -0
- package/src/solve.mjs +1128 -0
- package/src/solve.preparation.lib.mjs +210 -0
- package/src/solve.repo-setup.lib.mjs +114 -0
- package/src/solve.repository.lib.mjs +961 -0
- package/src/solve.results.lib.mjs +558 -0
- package/src/solve.session.lib.mjs +135 -0
- package/src/solve.validation.lib.mjs +325 -0
- package/src/solve.watch.lib.mjs +572 -0
- package/src/start-screen.mjs +324 -0
- package/src/task.mjs +308 -0
- package/src/telegram-bot.mjs +1481 -0
- package/src/telegram-markdown.lib.mjs +64 -0
- package/src/usage-limit.lib.mjs +218 -0
- package/src/version.lib.mjs +41 -0
- package/src/youtrack/solve.youtrack.lib.mjs +116 -0
- package/src/youtrack/youtrack-sync.mjs +219 -0
- package/src/youtrack/youtrack.lib.mjs +425 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// YouTrack-related utility functions
|
|
3
|
+
|
|
4
|
+
// Check if use is already defined (when imported from other modules)
|
|
5
|
+
// If not, fetch it (when running standalone)
|
|
6
|
+
if (typeof use === 'undefined') {
|
|
7
|
+
globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Import log and other utilities from general lib
|
|
11
|
+
import { log, cleanErrorMessage } from '../lib.mjs';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* YouTrack API configuration object
|
|
15
|
+
* @typedef {Object} YouTrackConfig
|
|
16
|
+
* @property {string} url - YouTrack instance URL (e.g., https://mycompany.youtrack.cloud)
|
|
17
|
+
* @property {string} apiKey - YouTrack API token/key for authentication
|
|
18
|
+
* @property {string} projectCode - Project code to monitor (e.g., PROJECT-1)
|
|
19
|
+
* @property {string} stage - Stage to monitor for issues (e.g., "Ready for Development")
|
|
20
|
+
* @property {string} nextStage - Stage to move issues to after PR creation (e.g., "In Review")
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* YouTrack issue object
|
|
25
|
+
* @typedef {Object} YouTrackIssue
|
|
26
|
+
* @property {string} id - Issue ID (e.g., PROJECT-123)
|
|
27
|
+
* @property {string} summary - Issue title/summary
|
|
28
|
+
* @property {string} description - Issue description
|
|
29
|
+
* @property {string} stage - Current stage/status
|
|
30
|
+
* @property {string} url - Direct URL to the issue
|
|
31
|
+
* @property {string} reporter - Issue reporter
|
|
32
|
+
* @property {string} assignee - Issue assignee (if any)
|
|
33
|
+
* @property {Date} created - Creation date
|
|
34
|
+
* @property {Date} updated - Last update date
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Validate YouTrack configuration
|
|
39
|
+
* @param {YouTrackConfig} config - YouTrack configuration
|
|
40
|
+
* @throws {Error} If configuration is invalid
|
|
41
|
+
*/
|
|
42
|
+
export function validateYouTrackConfig(config) {
|
|
43
|
+
if (!config) {
|
|
44
|
+
throw new Error('YouTrack configuration is required');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!config.url) {
|
|
48
|
+
throw new Error('YOUTRACK_URL is required');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!config.apiKey) {
|
|
52
|
+
throw new Error('YOUTRACK_API_KEY is required');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!config.projectCode) {
|
|
56
|
+
throw new Error('YOUTRACK_PROJECT_CODE is required');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!config.stage) {
|
|
60
|
+
throw new Error('YOUTRACK_STAGE is required');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Validate URL format
|
|
64
|
+
try {
|
|
65
|
+
new URL(config.url);
|
|
66
|
+
} catch {
|
|
67
|
+
throw new Error(`Invalid YOUTRACK_URL format: ${config.url}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Ensure URL ends with proper format
|
|
71
|
+
if (!config.url.includes('.youtrack.')) {
|
|
72
|
+
throw new Error(`YouTrack URL should contain '.youtrack.': ${config.url}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Make authenticated request to YouTrack API
|
|
78
|
+
* @param {string} endpoint - API endpoint (relative to /api)
|
|
79
|
+
* @param {YouTrackConfig} config - YouTrack configuration
|
|
80
|
+
* @param {Object} options - Additional options (method, body, etc.)
|
|
81
|
+
* @returns {Promise<Object>} API response
|
|
82
|
+
*/
|
|
83
|
+
async function makeYouTrackRequest(endpoint, config, options = {}) {
|
|
84
|
+
const { method = 'GET', body = null, headers = {} } = options;
|
|
85
|
+
|
|
86
|
+
// Construct full API URL
|
|
87
|
+
const baseUrl = config.url.endsWith('/') ? config.url.slice(0, -1) : config.url;
|
|
88
|
+
const fullUrl = `${baseUrl}/api${endpoint.startsWith('/') ? endpoint : '/' + endpoint}`;
|
|
89
|
+
|
|
90
|
+
// Debug logging for API calls
|
|
91
|
+
if (global.verboseMode || process.env.VERBOSE === 'true') {
|
|
92
|
+
await log(` YouTrack API call: ${method} ${fullUrl}`, { verbose: true });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Prepare headers
|
|
96
|
+
const requestHeaders = {
|
|
97
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
98
|
+
'Accept': 'application/json',
|
|
99
|
+
'Content-Type': 'application/json',
|
|
100
|
+
...headers
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Prepare request options
|
|
104
|
+
const requestOptions = {
|
|
105
|
+
method,
|
|
106
|
+
headers: requestHeaders
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
|
110
|
+
requestOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch(fullUrl, requestOptions);
|
|
115
|
+
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
const errorText = await response.text();
|
|
118
|
+
throw new Error(`YouTrack API error (${response.status}): ${errorText}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Handle empty responses (e.g., from POST requests)
|
|
122
|
+
const contentType = response.headers.get('content-type');
|
|
123
|
+
if (!contentType || !contentType.includes('application/json')) {
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return await response.json();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (error.message.includes('fetch')) {
|
|
130
|
+
throw new Error(`Failed to connect to YouTrack at ${config.url}: ${error.message}`);
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Test YouTrack API connection
|
|
138
|
+
* @param {YouTrackConfig} config - YouTrack configuration
|
|
139
|
+
* @returns {Promise<boolean>} True if connection is successful
|
|
140
|
+
*/
|
|
141
|
+
export async function testYouTrackConnection(config) {
|
|
142
|
+
try {
|
|
143
|
+
validateYouTrackConfig(config);
|
|
144
|
+
|
|
145
|
+
// Add debug logging
|
|
146
|
+
await log(`🔍 Testing YouTrack connection to: ${config.url}`);
|
|
147
|
+
await log(` Project: ${config.projectCode}`);
|
|
148
|
+
await log(` Stage: ${config.stage}`);
|
|
149
|
+
|
|
150
|
+
// Test connection by fetching user info
|
|
151
|
+
// Note: YouTrack Cloud uses /users/me, not /admin/users/me
|
|
152
|
+
await makeYouTrackRequest('/users/me', config);
|
|
153
|
+
await log(`✅ YouTrack connection successful: ${config.url}`);
|
|
154
|
+
return true;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
await log(`❌ YouTrack connection failed: ${cleanErrorMessage(error)}`, { level: 'error' });
|
|
157
|
+
await log(` URL: ${config.url}`);
|
|
158
|
+
await log(' Endpoint tested: /api/users/me');
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Fetch issues from YouTrack project by stage
|
|
165
|
+
* @param {YouTrackConfig} config - YouTrack configuration
|
|
166
|
+
* @returns {Promise<YouTrackIssue[]>} Array of matching issues
|
|
167
|
+
*/
|
|
168
|
+
export async function fetchYouTrackIssues(config) {
|
|
169
|
+
try {
|
|
170
|
+
validateYouTrackConfig(config);
|
|
171
|
+
|
|
172
|
+
await log(`🔍 Fetching YouTrack issues from project ${config.projectCode} with stage "${config.stage}"`);
|
|
173
|
+
|
|
174
|
+
// Construct search query
|
|
175
|
+
// YouTrack query syntax: project: {PROJECT} State: {STAGE}
|
|
176
|
+
const query = `project: {${config.projectCode}} State: {${config.stage}}`;
|
|
177
|
+
|
|
178
|
+
// Fetch issues with detailed fields including idReadable
|
|
179
|
+
const endpoint = `/issues?query=${encodeURIComponent(query)}&fields=id,idReadable,summary,description,created,updated,reporter(login,fullName),assignee(login,fullName),customFields(name,value(name))`;
|
|
180
|
+
|
|
181
|
+
const response = await makeYouTrackRequest(endpoint, config);
|
|
182
|
+
|
|
183
|
+
if (!response || !Array.isArray(response)) {
|
|
184
|
+
await log('⚠️ Unexpected response format from YouTrack API', { verbose: true });
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Transform YouTrack issues to our standard format
|
|
189
|
+
const issues = response.map(issue => ({
|
|
190
|
+
id: issue.idReadable || issue.id, // Use readable ID (PAG-45) if available
|
|
191
|
+
summary: issue.summary || 'No title',
|
|
192
|
+
description: issue.description || '',
|
|
193
|
+
stage: config.stage, // Current stage (what we filtered by)
|
|
194
|
+
url: `${config.url}/issue/${issue.idReadable || issue.id}`,
|
|
195
|
+
reporter: issue.reporter ? (issue.reporter.fullName || issue.reporter.login) : 'Unknown',
|
|
196
|
+
assignee: issue.assignee ? (issue.assignee.fullName || issue.assignee.login) : null,
|
|
197
|
+
created: issue.created ? new Date(issue.created) : new Date(),
|
|
198
|
+
updated: issue.updated ? new Date(issue.updated) : new Date()
|
|
199
|
+
}));
|
|
200
|
+
|
|
201
|
+
await log(`📋 Found ${issues.length} YouTrack issue(s) in stage "${config.stage}"`);
|
|
202
|
+
|
|
203
|
+
if (issues.length > 0) {
|
|
204
|
+
await log(' Issues found:');
|
|
205
|
+
for (const issue of issues) {
|
|
206
|
+
await log(` - ${issue.id}: ${issue.summary}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return issues;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
await log(`❌ Error fetching YouTrack issues: ${cleanErrorMessage(error)}`, { level: 'error' });
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get detailed information about a specific YouTrack issue
|
|
219
|
+
* @param {string} issueId - Issue ID (e.g., PROJECT-123)
|
|
220
|
+
* @param {YouTrackConfig} config - YouTrack configuration
|
|
221
|
+
* @returns {Promise<YouTrackIssue|null>} Issue details or null if not found
|
|
222
|
+
*/
|
|
223
|
+
export async function getYouTrackIssue(issueId, config) {
|
|
224
|
+
try {
|
|
225
|
+
validateYouTrackConfig(config);
|
|
226
|
+
|
|
227
|
+
await log(`🔍 Fetching YouTrack issue details: ${issueId}`);
|
|
228
|
+
|
|
229
|
+
// Fetch issue with detailed fields including idReadable
|
|
230
|
+
const endpoint = `/issues/${issueId}?fields=id,idReadable,summary,description,created,updated,reporter(login,fullName),assignee(login,fullName),customFields(name,value(name))`;
|
|
231
|
+
|
|
232
|
+
const issue = await makeYouTrackRequest(endpoint, config);
|
|
233
|
+
|
|
234
|
+
if (!issue || !issue.id) {
|
|
235
|
+
await log(`❌ YouTrack issue not found: ${issueId}`, { level: 'error' });
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Find the State/Stage custom field (check both possible names)
|
|
240
|
+
let currentStage = 'Unknown';
|
|
241
|
+
if (issue.customFields && Array.isArray(issue.customFields)) {
|
|
242
|
+
const stateField = issue.customFields.find(field =>
|
|
243
|
+
field.name === 'State' || field.name === 'Stage'
|
|
244
|
+
);
|
|
245
|
+
if (stateField && stateField.value && stateField.value.name) {
|
|
246
|
+
currentStage = stateField.value.name;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Transform to our standard format
|
|
251
|
+
const transformedIssue = {
|
|
252
|
+
id: issue.idReadable || issue.id, // Use readable ID (PAG-45) as primary ID
|
|
253
|
+
idReadable: issue.idReadable || issue.id, // User-friendly ID like PAG-55
|
|
254
|
+
summary: issue.summary || 'No title',
|
|
255
|
+
description: issue.description || '',
|
|
256
|
+
stage: currentStage,
|
|
257
|
+
url: `${config.url}/issue/${issue.idReadable || issue.id}`,
|
|
258
|
+
reporter: issue.reporter ? (issue.reporter.fullName || issue.reporter.login) : 'Unknown',
|
|
259
|
+
assignee: issue.assignee ? (issue.assignee.fullName || issue.assignee.login) : null,
|
|
260
|
+
created: issue.created ? new Date(issue.created) : new Date(),
|
|
261
|
+
updated: issue.updated ? new Date(issue.updated) : new Date()
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
await log(`✅ Retrieved YouTrack issue: ${transformedIssue.id} - ${transformedIssue.summary}`);
|
|
265
|
+
|
|
266
|
+
return transformedIssue;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
await log(`❌ Error fetching YouTrack issue ${issueId}: ${cleanErrorMessage(error)}`, { level: 'error' });
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Update YouTrack issue stage/status
|
|
275
|
+
* @param {string} issueId - Issue ID (e.g., PROJECT-123)
|
|
276
|
+
* @param {string} newStage - New stage name
|
|
277
|
+
* @param {YouTrackConfig} config - YouTrack configuration
|
|
278
|
+
* @returns {Promise<boolean>} True if update was successful
|
|
279
|
+
*/
|
|
280
|
+
export async function updateYouTrackIssueStage(issueId, newStage, config) {
|
|
281
|
+
try {
|
|
282
|
+
validateYouTrackConfig(config);
|
|
283
|
+
|
|
284
|
+
await log(`🔄 Updating YouTrack issue ${issueId} stage to "${newStage}"`);
|
|
285
|
+
|
|
286
|
+
// Update the Stage custom field (note: might be 'Stage' or 'State' depending on setup)
|
|
287
|
+
const endpoint = `/issues/${issueId}`;
|
|
288
|
+
const updateData = {
|
|
289
|
+
customFields: [
|
|
290
|
+
{
|
|
291
|
+
$type: 'StateIssueCustomField',
|
|
292
|
+
name: 'Stage',
|
|
293
|
+
value: {
|
|
294
|
+
$type: 'StateBundleElement',
|
|
295
|
+
name: newStage
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
]
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
await makeYouTrackRequest(endpoint, config, {
|
|
302
|
+
method: 'POST',
|
|
303
|
+
body: updateData
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
await log(`✅ Updated YouTrack issue ${issueId} stage to "${newStage}"`);
|
|
307
|
+
return true;
|
|
308
|
+
} catch (error) {
|
|
309
|
+
await log(`❌ Error updating YouTrack issue ${issueId} stage: ${cleanErrorMessage(error)}`, { level: 'error' });
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Add comment to YouTrack issue
|
|
316
|
+
* @param {string} issueId - Issue ID (e.g., PROJECT-123)
|
|
317
|
+
* @param {string} comment - Comment text (supports markdown)
|
|
318
|
+
* @param {YouTrackConfig} config - YouTrack configuration
|
|
319
|
+
* @returns {Promise<boolean>} True if comment was added successfully
|
|
320
|
+
*/
|
|
321
|
+
export async function addYouTrackComment(issueId, comment, config) {
|
|
322
|
+
try {
|
|
323
|
+
validateYouTrackConfig(config);
|
|
324
|
+
|
|
325
|
+
await log(`💬 Adding comment to YouTrack issue ${issueId}`);
|
|
326
|
+
|
|
327
|
+
// Add comment using the comments API
|
|
328
|
+
const endpoint = `/issues/${issueId}/comments`;
|
|
329
|
+
const commentData = {
|
|
330
|
+
text: comment,
|
|
331
|
+
usesMarkdown: true
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
await makeYouTrackRequest(endpoint, config, {
|
|
335
|
+
method: 'POST',
|
|
336
|
+
body: commentData
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
await log(`✅ Added comment to YouTrack issue ${issueId}`);
|
|
340
|
+
return true;
|
|
341
|
+
} catch (error) {
|
|
342
|
+
await log(`❌ Error adding comment to YouTrack issue ${issueId}: ${cleanErrorMessage(error)}`, { level: 'error' });
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Create YouTrack configuration from environment variables
|
|
349
|
+
* @returns {YouTrackConfig|null} Configuration object or null if not properly configured
|
|
350
|
+
*/
|
|
351
|
+
export function createYouTrackConfigFromEnv() {
|
|
352
|
+
const config = {
|
|
353
|
+
url: process.env.YOUTRACK_URL,
|
|
354
|
+
apiKey: process.env.YOUTRACK_API_KEY,
|
|
355
|
+
projectCode: process.env.YOUTRACK_PROJECT_CODE,
|
|
356
|
+
stage: process.env.YOUTRACK_STAGE,
|
|
357
|
+
nextStage: process.env.YOUTRACK_NEXT_STAGE
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// Check if basic configuration is available
|
|
361
|
+
if (!config.url || !config.apiKey || !config.projectCode || !config.stage) {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return config;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Parse YouTrack issue ID from URL or text
|
|
370
|
+
* @param {string} input - URL or text containing YouTrack issue ID
|
|
371
|
+
* @returns {string|null} Issue ID or null if not found
|
|
372
|
+
*/
|
|
373
|
+
export function parseYouTrackIssueId(input) {
|
|
374
|
+
if (!input || typeof input !== 'string') {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Pattern to match YouTrack issue IDs (PROJECT-123 or 2-123 format)
|
|
379
|
+
// Some YouTrack instances use numeric project codes
|
|
380
|
+
const patterns = [
|
|
381
|
+
// Direct ID format (allows numeric or alphanumeric project codes)
|
|
382
|
+
/^([A-Z0-9][A-Z0-9]*-\d+)$/i,
|
|
383
|
+
// URL format: https://company.youtrack.cloud/issue/PROJECT-123
|
|
384
|
+
/\/issue\/([A-Z0-9][A-Z0-9]*-\d+)/i,
|
|
385
|
+
// Text containing issue ID
|
|
386
|
+
/\b([A-Z0-9][A-Z0-9]*-\d+)\b/i
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
for (const pattern of patterns) {
|
|
390
|
+
const match = input.match(pattern);
|
|
391
|
+
if (match) {
|
|
392
|
+
return match[1].toUpperCase();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Convert YouTrack issue to GitHub-compatible format for solve.mjs
|
|
401
|
+
* @param {YouTrackIssue} youTrackIssue - YouTrack issue
|
|
402
|
+
* @param {string} githubRepoUrl - Target GitHub repository URL
|
|
403
|
+
* @returns {Object} GitHub-compatible issue format
|
|
404
|
+
*/
|
|
405
|
+
export function convertYouTrackIssueForGitHub(youTrackIssue, githubRepoUrl) {
|
|
406
|
+
return {
|
|
407
|
+
// Use a special URL format that solve.mjs can recognize as YouTrack
|
|
408
|
+
url: `youtrack://${youTrackIssue.id}`,
|
|
409
|
+
title: youTrackIssue.summary,
|
|
410
|
+
body: youTrackIssue.description,
|
|
411
|
+
number: youTrackIssue.id,
|
|
412
|
+
// Store original YouTrack data for later use
|
|
413
|
+
youtrack: {
|
|
414
|
+
id: youTrackIssue.id,
|
|
415
|
+
url: youTrackIssue.url,
|
|
416
|
+
stage: youTrackIssue.stage,
|
|
417
|
+
reporter: youTrackIssue.reporter,
|
|
418
|
+
assignee: youTrackIssue.assignee
|
|
419
|
+
},
|
|
420
|
+
// Store GitHub repo info for PR creation
|
|
421
|
+
github: {
|
|
422
|
+
repoUrl: githubRepoUrl
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
}
|