@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +24 -0
  3. package/README.md +769 -0
  4. package/package.json +58 -0
  5. package/src/agent.lib.mjs +705 -0
  6. package/src/agent.prompts.lib.mjs +196 -0
  7. package/src/buildUserMention.lib.mjs +71 -0
  8. package/src/claude-limits.lib.mjs +389 -0
  9. package/src/claude.lib.mjs +1445 -0
  10. package/src/claude.prompts.lib.mjs +203 -0
  11. package/src/codex.lib.mjs +552 -0
  12. package/src/codex.prompts.lib.mjs +194 -0
  13. package/src/config.lib.mjs +207 -0
  14. package/src/contributing-guidelines.lib.mjs +268 -0
  15. package/src/exit-handler.lib.mjs +205 -0
  16. package/src/git.lib.mjs +145 -0
  17. package/src/github-issue-creator.lib.mjs +246 -0
  18. package/src/github-linking.lib.mjs +152 -0
  19. package/src/github.batch.lib.mjs +272 -0
  20. package/src/github.graphql.lib.mjs +258 -0
  21. package/src/github.lib.mjs +1479 -0
  22. package/src/hive.config.lib.mjs +254 -0
  23. package/src/hive.mjs +1500 -0
  24. package/src/instrument.mjs +191 -0
  25. package/src/interactive-mode.lib.mjs +1000 -0
  26. package/src/lenv-reader.lib.mjs +206 -0
  27. package/src/lib.mjs +490 -0
  28. package/src/lino.lib.mjs +176 -0
  29. package/src/local-ci-checks.lib.mjs +324 -0
  30. package/src/memory-check.mjs +419 -0
  31. package/src/model-mapping.lib.mjs +145 -0
  32. package/src/model-validation.lib.mjs +278 -0
  33. package/src/opencode.lib.mjs +479 -0
  34. package/src/opencode.prompts.lib.mjs +194 -0
  35. package/src/protect-branch.mjs +159 -0
  36. package/src/review.mjs +433 -0
  37. package/src/reviewers-hive.mjs +643 -0
  38. package/src/sentry.lib.mjs +284 -0
  39. package/src/solve.auto-continue.lib.mjs +568 -0
  40. package/src/solve.auto-pr.lib.mjs +1374 -0
  41. package/src/solve.branch-errors.lib.mjs +341 -0
  42. package/src/solve.branch.lib.mjs +230 -0
  43. package/src/solve.config.lib.mjs +342 -0
  44. package/src/solve.error-handlers.lib.mjs +256 -0
  45. package/src/solve.execution.lib.mjs +291 -0
  46. package/src/solve.feedback.lib.mjs +436 -0
  47. package/src/solve.mjs +1128 -0
  48. package/src/solve.preparation.lib.mjs +210 -0
  49. package/src/solve.repo-setup.lib.mjs +114 -0
  50. package/src/solve.repository.lib.mjs +961 -0
  51. package/src/solve.results.lib.mjs +558 -0
  52. package/src/solve.session.lib.mjs +135 -0
  53. package/src/solve.validation.lib.mjs +325 -0
  54. package/src/solve.watch.lib.mjs +572 -0
  55. package/src/start-screen.mjs +324 -0
  56. package/src/task.mjs +308 -0
  57. package/src/telegram-bot.mjs +1481 -0
  58. package/src/telegram-markdown.lib.mjs +64 -0
  59. package/src/usage-limit.lib.mjs +218 -0
  60. package/src/version.lib.mjs +41 -0
  61. package/src/youtrack/solve.youtrack.lib.mjs +116 -0
  62. package/src/youtrack/youtrack-sync.mjs +219 -0
  63. 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
+ }