@link-assistant/hive-mind 0.46.0 → 0.47.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 +26 -13
- package/README.md +42 -8
- package/package.json +16 -3
- package/src/agent.lib.mjs +49 -70
- package/src/agent.prompts.lib.mjs +6 -20
- package/src/buildUserMention.lib.mjs +4 -17
- package/src/claude-limits.lib.mjs +15 -15
- package/src/claude.lib.mjs +617 -626
- package/src/claude.prompts.lib.mjs +7 -22
- package/src/codex.lib.mjs +39 -71
- package/src/codex.prompts.lib.mjs +6 -20
- package/src/config.lib.mjs +3 -16
- package/src/contributing-guidelines.lib.mjs +5 -18
- package/src/exit-handler.lib.mjs +4 -4
- package/src/git.lib.mjs +7 -7
- package/src/github-issue-creator.lib.mjs +17 -17
- package/src/github-linking.lib.mjs +8 -33
- package/src/github.batch.lib.mjs +20 -16
- package/src/github.graphql.lib.mjs +18 -18
- package/src/github.lib.mjs +89 -91
- package/src/hive.config.lib.mjs +50 -50
- package/src/hive.mjs +1293 -1296
- package/src/instrument.mjs +7 -11
- package/src/interactive-mode.lib.mjs +112 -138
- package/src/lenv-reader.lib.mjs +1 -6
- package/src/lib.mjs +36 -45
- package/src/lino.lib.mjs +2 -2
- package/src/local-ci-checks.lib.mjs +15 -14
- package/src/memory-check.mjs +52 -60
- package/src/model-mapping.lib.mjs +25 -32
- package/src/model-validation.lib.mjs +31 -31
- package/src/opencode.lib.mjs +37 -62
- package/src/opencode.prompts.lib.mjs +7 -21
- package/src/protect-branch.mjs +14 -15
- package/src/review.mjs +28 -27
- package/src/reviewers-hive.mjs +64 -69
- package/src/sentry.lib.mjs +13 -10
- package/src/solve.auto-continue.lib.mjs +48 -38
- package/src/solve.auto-pr.lib.mjs +111 -69
- package/src/solve.branch-errors.lib.mjs +17 -46
- package/src/solve.branch.lib.mjs +16 -23
- package/src/solve.config.lib.mjs +263 -261
- package/src/solve.error-handlers.lib.mjs +21 -79
- package/src/solve.execution.lib.mjs +10 -18
- package/src/solve.feedback.lib.mjs +25 -46
- package/src/solve.mjs +59 -60
- package/src/solve.preparation.lib.mjs +10 -36
- package/src/solve.repo-setup.lib.mjs +4 -19
- package/src/solve.repository.lib.mjs +37 -37
- package/src/solve.results.lib.mjs +32 -46
- package/src/solve.session.lib.mjs +7 -22
- package/src/solve.validation.lib.mjs +19 -17
- package/src/solve.watch.lib.mjs +20 -33
- package/src/start-screen.mjs +24 -24
- package/src/task.mjs +38 -44
- package/src/telegram-bot.mjs +125 -121
- package/src/telegram-top-command.lib.mjs +32 -48
- package/src/usage-limit.lib.mjs +9 -13
- package/src/version-info.lib.mjs +1 -1
- package/src/version.lib.mjs +1 -1
- package/src/youtrack/solve.youtrack.lib.mjs +3 -8
- package/src/youtrack/youtrack-sync.mjs +8 -14
- package/src/youtrack/youtrack.lib.mjs +26 -28
|
@@ -53,18 +53,12 @@ async function captureTopOutput(chatId) {
|
|
|
53
53
|
* @param {Function} options.isChatAuthorized - Function to check if chat is authorized
|
|
54
54
|
*/
|
|
55
55
|
export function registerTopCommand(bot, options) {
|
|
56
|
-
const {
|
|
57
|
-
VERBOSE = false,
|
|
58
|
-
isOldMessage,
|
|
59
|
-
isForwardedOrReply,
|
|
60
|
-
isGroupChat,
|
|
61
|
-
isChatAuthorized
|
|
62
|
-
} = options;
|
|
56
|
+
const { VERBOSE = false, isOldMessage, isForwardedOrReply, isGroupChat, isChatAuthorized } = options;
|
|
63
57
|
|
|
64
58
|
// /top command - show system top output in an auto-updating message (EXPERIMENTAL)
|
|
65
59
|
// Only accessible by chat owner
|
|
66
60
|
// Not documented in /help as requested in issue #500
|
|
67
|
-
bot.command('top', async
|
|
61
|
+
bot.command('top', async ctx => {
|
|
68
62
|
if (VERBOSE) {
|
|
69
63
|
console.log('[VERBOSE] /top command received');
|
|
70
64
|
}
|
|
@@ -89,7 +83,9 @@ export function registerTopCommand(bot, options) {
|
|
|
89
83
|
if (VERBOSE) {
|
|
90
84
|
console.log('[VERBOSE] /top ignored: not a group chat');
|
|
91
85
|
}
|
|
92
|
-
await ctx.reply('❌ The /top command only works in group chats.', {
|
|
86
|
+
await ctx.reply('❌ The /top command only works in group chats.', {
|
|
87
|
+
reply_to_message_id: ctx.message.message_id,
|
|
88
|
+
});
|
|
93
89
|
return;
|
|
94
90
|
}
|
|
95
91
|
|
|
@@ -98,7 +94,9 @@ export function registerTopCommand(bot, options) {
|
|
|
98
94
|
if (VERBOSE) {
|
|
99
95
|
console.log('[VERBOSE] /top ignored: chat not authorized');
|
|
100
96
|
}
|
|
101
|
-
await ctx.reply(`❌ This chat (ID: ${chatId}) is not authorized to use this bot.`, {
|
|
97
|
+
await ctx.reply(`❌ This chat (ID: ${chatId}) is not authorized to use this bot.`, {
|
|
98
|
+
reply_to_message_id: ctx.message.message_id,
|
|
99
|
+
});
|
|
102
100
|
return;
|
|
103
101
|
}
|
|
104
102
|
|
|
@@ -109,7 +107,9 @@ export function registerTopCommand(bot, options) {
|
|
|
109
107
|
if (VERBOSE) {
|
|
110
108
|
console.log('[VERBOSE] /top ignored: user is not chat owner');
|
|
111
109
|
}
|
|
112
|
-
await ctx.reply('❌ This command is only available to the chat owner.', {
|
|
110
|
+
await ctx.reply('❌ This command is only available to the chat owner.', {
|
|
111
|
+
reply_to_message_id: ctx.message.message_id,
|
|
112
|
+
});
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
115
|
} catch (error) {
|
|
@@ -125,12 +125,14 @@ export function registerTopCommand(bot, options) {
|
|
|
125
125
|
// Show experimental feature warning
|
|
126
126
|
await ctx.reply('🧪 *EXPERIMENTAL FEATURE*\n\nThis command is experimental and may have issues. Use with caution.', {
|
|
127
127
|
parse_mode: 'Markdown',
|
|
128
|
-
reply_to_message_id: ctx.message.message_id
|
|
128
|
+
reply_to_message_id: ctx.message.message_id,
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
// Check if there's already an active top session for this chat
|
|
132
132
|
if (activeTopSessions.has(chatId)) {
|
|
133
|
-
await ctx.reply('❌ A top session is already running for this chat. Stop it first using the button.', {
|
|
133
|
+
await ctx.reply('❌ A top session is already running for this chat. Stop it first using the button.', {
|
|
134
|
+
reply_to_message_id: ctx.message.message_id,
|
|
135
|
+
});
|
|
134
136
|
return;
|
|
135
137
|
}
|
|
136
138
|
|
|
@@ -173,30 +175,20 @@ export function registerTopCommand(bot, options) {
|
|
|
173
175
|
const initialMessage = await ctx.reply('🧪 📊 Loading system monitor... (EXPERIMENTAL)', {
|
|
174
176
|
reply_to_message_id: ctx.message.message_id,
|
|
175
177
|
reply_markup: {
|
|
176
|
-
inline_keyboard: [[
|
|
177
|
-
|
|
178
|
-
]]
|
|
179
|
-
}
|
|
178
|
+
inline_keyboard: [[{ text: '🛑 Stop', callback_data: `stop_top_${chatId}` }]],
|
|
179
|
+
},
|
|
180
180
|
});
|
|
181
181
|
|
|
182
182
|
// Capture and display first output
|
|
183
183
|
const firstOutput = await captureTopOutput(chatId);
|
|
184
184
|
if (firstOutput) {
|
|
185
185
|
try {
|
|
186
|
-
await ctx.telegram.editMessageText(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
parse_mode: 'Markdown',
|
|
193
|
-
reply_markup: {
|
|
194
|
-
inline_keyboard: [[
|
|
195
|
-
{ text: '🛑 Stop', callback_data: `stop_top_${chatId}` }
|
|
196
|
-
]]
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
);
|
|
186
|
+
await ctx.telegram.editMessageText(chatId, initialMessage.message_id, undefined, `\`\`\`\n${firstOutput}\n\`\`\``, {
|
|
187
|
+
parse_mode: 'Markdown',
|
|
188
|
+
reply_markup: {
|
|
189
|
+
inline_keyboard: [[{ text: '🛑 Stop', callback_data: `stop_top_${chatId}` }]],
|
|
190
|
+
},
|
|
191
|
+
});
|
|
200
192
|
} catch (error) {
|
|
201
193
|
console.error('[ERROR] Failed to update message:', error);
|
|
202
194
|
}
|
|
@@ -207,20 +199,12 @@ export function registerTopCommand(bot, options) {
|
|
|
207
199
|
const output = await captureTopOutput(chatId);
|
|
208
200
|
if (output) {
|
|
209
201
|
try {
|
|
210
|
-
await ctx.telegram.editMessageText(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
parse_mode: 'Markdown',
|
|
217
|
-
reply_markup: {
|
|
218
|
-
inline_keyboard: [[
|
|
219
|
-
{ text: '🛑 Stop', callback_data: `stop_top_${chatId}` }
|
|
220
|
-
]]
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
);
|
|
202
|
+
await ctx.telegram.editMessageText(chatId, initialMessage.message_id, undefined, `\`\`\`\n${output}\n\`\`\``, {
|
|
203
|
+
parse_mode: 'Markdown',
|
|
204
|
+
reply_markup: {
|
|
205
|
+
inline_keyboard: [[{ text: '🛑 Stop', callback_data: `stop_top_${chatId}` }]],
|
|
206
|
+
},
|
|
207
|
+
});
|
|
224
208
|
} catch (error) {
|
|
225
209
|
// Ignore "message is not modified" errors
|
|
226
210
|
if (!error.message?.includes('message is not modified')) {
|
|
@@ -234,7 +218,7 @@ export function registerTopCommand(bot, options) {
|
|
|
234
218
|
activeTopSessions.set(chatId, {
|
|
235
219
|
messageId: initialMessage.message_id,
|
|
236
220
|
screenName,
|
|
237
|
-
intervalId
|
|
221
|
+
intervalId,
|
|
238
222
|
});
|
|
239
223
|
|
|
240
224
|
if (VERBOSE) {
|
|
@@ -243,7 +227,7 @@ export function registerTopCommand(bot, options) {
|
|
|
243
227
|
});
|
|
244
228
|
|
|
245
229
|
// Handle stop button callback
|
|
246
|
-
bot.action(/^stop_top_(.+)$/, async
|
|
230
|
+
bot.action(/^stop_top_(.+)$/, async ctx => {
|
|
247
231
|
const chatId = parseInt(ctx.match[1]);
|
|
248
232
|
|
|
249
233
|
if (VERBOSE) {
|
|
@@ -302,7 +286,7 @@ export function registerTopCommand(bot, options) {
|
|
|
302
286
|
// Update the message to show it's stopped
|
|
303
287
|
try {
|
|
304
288
|
await ctx.editMessageText('🛑 Top session stopped.', {
|
|
305
|
-
parse_mode: 'Markdown'
|
|
289
|
+
parse_mode: 'Markdown',
|
|
306
290
|
});
|
|
307
291
|
} catch (error) {
|
|
308
292
|
console.error('[ERROR] Failed to edit message:', error);
|
package/src/usage-limit.lib.mjs
CHANGED
|
@@ -34,13 +34,13 @@ export function isUsageLimitError(message) {
|
|
|
34
34
|
'limit has been reached',
|
|
35
35
|
// Provider-specific phrasings we’ve seen in the wild
|
|
36
36
|
'session limit reached', // Claude
|
|
37
|
-
'weekly limit reached',
|
|
37
|
+
'weekly limit reached', // Claude
|
|
38
38
|
'daily limit reached',
|
|
39
39
|
'monthly limit reached',
|
|
40
40
|
'billing hard limit',
|
|
41
|
-
'please try again at',
|
|
41
|
+
'please try again at', // Codex/OpenCode style
|
|
42
42
|
'available again at',
|
|
43
|
-
'resets'
|
|
43
|
+
'resets', // Claude shows: “∙ resets 5am”
|
|
44
44
|
];
|
|
45
45
|
|
|
46
46
|
return patterns.some(pattern => lowerMessage.includes(pattern));
|
|
@@ -100,7 +100,8 @@ export function extractResetTime(message) {
|
|
|
100
100
|
let hour = parseInt(resets24h[1], 10);
|
|
101
101
|
const minute = resets24h[2];
|
|
102
102
|
const ampm = hour >= 12 ? 'PM' : 'AM';
|
|
103
|
-
if (hour === 0)
|
|
103
|
+
if (hour === 0)
|
|
104
|
+
hour = 12; // 0 -> 12 AM
|
|
104
105
|
else if (hour > 12) hour -= 12; // 13-23 -> 1-11 PM
|
|
105
106
|
return `${hour}:${minute} ${ampm}`;
|
|
106
107
|
}
|
|
@@ -137,7 +138,7 @@ export function detectUsageLimit(message) {
|
|
|
137
138
|
|
|
138
139
|
return {
|
|
139
140
|
isUsageLimit,
|
|
140
|
-
resetTime
|
|
141
|
+
resetTime,
|
|
141
142
|
};
|
|
142
143
|
}
|
|
143
144
|
|
|
@@ -152,12 +153,7 @@ export function detectUsageLimit(message) {
|
|
|
152
153
|
* @returns {string[]} - Array of formatted message lines
|
|
153
154
|
*/
|
|
154
155
|
export function formatUsageLimitMessage({ tool, resetTime, sessionId, resumeCommand }) {
|
|
155
|
-
const lines = [
|
|
156
|
-
'',
|
|
157
|
-
'⏳ Usage Limit Reached!',
|
|
158
|
-
'',
|
|
159
|
-
`Your ${tool || 'AI tool'} usage limit has been reached.`
|
|
160
|
-
];
|
|
156
|
+
const lines = ['', '⏳ Usage Limit Reached!', '', `Your ${tool || 'AI tool'} usage limit has been reached.`];
|
|
161
157
|
|
|
162
158
|
if (resetTime) {
|
|
163
159
|
lines.push(`The limit will reset at: ${resetTime}`);
|
|
@@ -195,7 +191,7 @@ export function parseUsageLimitJson(line) {
|
|
|
195
191
|
return {
|
|
196
192
|
type: 'error',
|
|
197
193
|
message: data.message,
|
|
198
|
-
limitInfo: detectUsageLimit(data.message)
|
|
194
|
+
limitInfo: detectUsageLimit(data.message),
|
|
199
195
|
};
|
|
200
196
|
}
|
|
201
197
|
}
|
|
@@ -206,7 +202,7 @@ export function parseUsageLimitJson(line) {
|
|
|
206
202
|
return {
|
|
207
203
|
type: 'turn.failed',
|
|
208
204
|
message: data.error.message,
|
|
209
|
-
limitInfo: detectUsageLimit(data.error.message)
|
|
205
|
+
limitInfo: detectUsageLimit(data.error.message),
|
|
210
206
|
};
|
|
211
207
|
}
|
|
212
208
|
}
|
package/src/version-info.lib.mjs
CHANGED
|
@@ -61,7 +61,7 @@ export async function getVersionInfo(verbose = false) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
// Playwright MCP (check if installed via npm)
|
|
64
|
-
const playwrightMcpVersion = execCommand(
|
|
64
|
+
const playwrightMcpVersion = execCommand("npm list -g @playwright/mcp --depth=0 2>&1 | grep @playwright/mcp | awk '{print $2}'", verbose);
|
|
65
65
|
if (verbose && playwrightMcpVersion) {
|
|
66
66
|
console.log(`[VERBOSE] Playwright MCP version: ${playwrightMcpVersion}`);
|
|
67
67
|
}
|
package/src/version.lib.mjs
CHANGED
|
@@ -7,12 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
// Import YouTrack-related functions
|
|
9
9
|
const youTrackLib = await import('./youtrack.lib.mjs');
|
|
10
|
-
const {
|
|
11
|
-
parseYouTrackIssueId,
|
|
12
|
-
updateYouTrackIssueStage,
|
|
13
|
-
addYouTrackComment,
|
|
14
|
-
createYouTrackConfigFromEnv
|
|
15
|
-
} = youTrackLib;
|
|
10
|
+
const { parseYouTrackIssueId, updateYouTrackIssueStage, addYouTrackComment, createYouTrackConfigFromEnv } = youTrackLib;
|
|
16
11
|
|
|
17
12
|
/**
|
|
18
13
|
* Validates YouTrack URLs and extracts issue information
|
|
@@ -60,7 +55,7 @@ export async function validateYouTrackUrl(issueUrl) {
|
|
|
60
55
|
return {
|
|
61
56
|
isYouTrackUrl: !!isYouTrackUrl,
|
|
62
57
|
youTrackIssueId,
|
|
63
|
-
youTrackConfig
|
|
58
|
+
youTrackConfig,
|
|
64
59
|
};
|
|
65
60
|
}
|
|
66
61
|
|
|
@@ -113,4 +108,4 @@ export function isYouTrackFormat(url) {
|
|
|
113
108
|
if (url.match(/^[A-Z0-9]+-\d+$/i)) return true;
|
|
114
109
|
|
|
115
110
|
return false;
|
|
116
|
-
}
|
|
111
|
+
}
|
|
@@ -11,9 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
// Import YouTrack functions
|
|
13
13
|
const youTrackLib = await import('./youtrack.lib.mjs');
|
|
14
|
-
const {
|
|
15
|
-
fetchYouTrackIssues
|
|
16
|
-
} = youTrackLib;
|
|
14
|
+
const { fetchYouTrackIssues } = youTrackLib;
|
|
17
15
|
|
|
18
16
|
/**
|
|
19
17
|
* Find existing GitHub issue for a YouTrack issue
|
|
@@ -37,16 +35,12 @@ export async function findGitHubIssueForYouTrack(youTrackId, owner, repo, $) {
|
|
|
37
35
|
|
|
38
36
|
// Find exact match (YouTrack ID should be in brackets or at start)
|
|
39
37
|
// Return the first matching issue (prefer open issues)
|
|
40
|
-
const openIssue = issues.find(issue =>
|
|
41
|
-
issue.state === 'open' && (issue.title.includes(`[${youTrackId}]`) || issue.title.startsWith(`${youTrackId}:`))
|
|
42
|
-
);
|
|
38
|
+
const openIssue = issues.find(issue => issue.state === 'open' && (issue.title.includes(`[${youTrackId}]`) || issue.title.startsWith(`${youTrackId}:`)));
|
|
43
39
|
|
|
44
40
|
if (openIssue) return openIssue;
|
|
45
41
|
|
|
46
42
|
// If no open issue, check for closed issues to prevent duplicates
|
|
47
|
-
const closedIssue = issues.find(issue =>
|
|
48
|
-
issue.state === 'closed' && (issue.title.includes(`[${youTrackId}]`) || issue.title.startsWith(`${youTrackId}:`))
|
|
49
|
-
);
|
|
43
|
+
const closedIssue = issues.find(issue => issue.state === 'closed' && (issue.title.includes(`[${youTrackId}]`) || issue.title.startsWith(`${youTrackId}:`)));
|
|
50
44
|
|
|
51
45
|
return closedIssue || null;
|
|
52
46
|
} catch {
|
|
@@ -123,7 +117,7 @@ ${youTrackIssue.description || 'No description provided.'}
|
|
|
123
117
|
await log(` 🏷️ Added 'help wanted' label to #${existingIssue.number}`);
|
|
124
118
|
} catch {
|
|
125
119
|
// Silently skip if label doesn't exist
|
|
126
|
-
await log(
|
|
120
|
+
await log(" ⚠️ Could not add 'help wanted' label (may not exist in repo)", { verbose: true });
|
|
127
121
|
}
|
|
128
122
|
}
|
|
129
123
|
|
|
@@ -145,7 +139,7 @@ ${youTrackIssue.description || 'No description provided.'}
|
|
|
145
139
|
number: issueNumber,
|
|
146
140
|
title: ghTitle,
|
|
147
141
|
body: ghBody,
|
|
148
|
-
html_url: issueUrl
|
|
142
|
+
html_url: issueUrl,
|
|
149
143
|
};
|
|
150
144
|
} else {
|
|
151
145
|
await log(` ❌ Failed to create issue for ${youTrackId}`, { level: 'error' });
|
|
@@ -192,7 +186,7 @@ export async function syncYouTrackToGitHub(youTrackConfig, owner, repo, $, log)
|
|
|
192
186
|
githubIssues.push({
|
|
193
187
|
...ghIssue,
|
|
194
188
|
youtrackId: ytIssue.id,
|
|
195
|
-
youtrackUrl: `${youTrackConfig.url}/issue/${ytIssue.idReadable}
|
|
189
|
+
youtrackUrl: `${youTrackConfig.url}/issue/${ytIssue.idReadable}`,
|
|
196
190
|
});
|
|
197
191
|
}
|
|
198
192
|
}
|
|
@@ -214,6 +208,6 @@ export function formatIssuesForHive(githubIssues) {
|
|
|
214
208
|
html_url: issue.html_url,
|
|
215
209
|
labels: issue.labels || [{ name: 'help-wanted' }],
|
|
216
210
|
youtrackId: issue.youtrackId,
|
|
217
|
-
youtrackUrl: issue.youtrackUrl
|
|
211
|
+
youtrackUrl: issue.youtrackUrl,
|
|
218
212
|
}));
|
|
219
|
-
}
|
|
213
|
+
}
|
|
@@ -94,16 +94,16 @@ async function makeYouTrackRequest(endpoint, config, options = {}) {
|
|
|
94
94
|
|
|
95
95
|
// Prepare headers
|
|
96
96
|
const requestHeaders = {
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
98
|
+
Accept: 'application/json',
|
|
99
99
|
'Content-Type': 'application/json',
|
|
100
|
-
...headers
|
|
100
|
+
...headers,
|
|
101
101
|
};
|
|
102
102
|
|
|
103
103
|
// Prepare request options
|
|
104
104
|
const requestOptions = {
|
|
105
105
|
method,
|
|
106
|
-
headers: requestHeaders
|
|
106
|
+
headers: requestHeaders,
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
|
@@ -187,15 +187,15 @@ export async function fetchYouTrackIssues(config) {
|
|
|
187
187
|
|
|
188
188
|
// Transform YouTrack issues to our standard format
|
|
189
189
|
const issues = response.map(issue => ({
|
|
190
|
-
id: issue.idReadable || issue.id,
|
|
190
|
+
id: issue.idReadable || issue.id, // Use readable ID (PAG-45) if available
|
|
191
191
|
summary: issue.summary || 'No title',
|
|
192
192
|
description: issue.description || '',
|
|
193
193
|
stage: config.stage, // Current stage (what we filtered by)
|
|
194
194
|
url: `${config.url}/issue/${issue.idReadable || issue.id}`,
|
|
195
|
-
reporter: issue.reporter ?
|
|
196
|
-
assignee: issue.assignee ?
|
|
195
|
+
reporter: issue.reporter ? issue.reporter.fullName || issue.reporter.login : 'Unknown',
|
|
196
|
+
assignee: issue.assignee ? issue.assignee.fullName || issue.assignee.login : null,
|
|
197
197
|
created: issue.created ? new Date(issue.created) : new Date(),
|
|
198
|
-
updated: issue.updated ? new Date(issue.updated) : new Date()
|
|
198
|
+
updated: issue.updated ? new Date(issue.updated) : new Date(),
|
|
199
199
|
}));
|
|
200
200
|
|
|
201
201
|
await log(`📋 Found ${issues.length} YouTrack issue(s) in stage "${config.stage}"`);
|
|
@@ -239,9 +239,7 @@ export async function getYouTrackIssue(issueId, config) {
|
|
|
239
239
|
// Find the State/Stage custom field (check both possible names)
|
|
240
240
|
let currentStage = 'Unknown';
|
|
241
241
|
if (issue.customFields && Array.isArray(issue.customFields)) {
|
|
242
|
-
const stateField = issue.customFields.find(field =>
|
|
243
|
-
field.name === 'State' || field.name === 'Stage'
|
|
244
|
-
);
|
|
242
|
+
const stateField = issue.customFields.find(field => field.name === 'State' || field.name === 'Stage');
|
|
245
243
|
if (stateField && stateField.value && stateField.value.name) {
|
|
246
244
|
currentStage = stateField.value.name;
|
|
247
245
|
}
|
|
@@ -249,16 +247,16 @@ export async function getYouTrackIssue(issueId, config) {
|
|
|
249
247
|
|
|
250
248
|
// Transform to our standard format
|
|
251
249
|
const transformedIssue = {
|
|
252
|
-
id: issue.idReadable || issue.id,
|
|
250
|
+
id: issue.idReadable || issue.id, // Use readable ID (PAG-45) as primary ID
|
|
253
251
|
idReadable: issue.idReadable || issue.id, // User-friendly ID like PAG-55
|
|
254
252
|
summary: issue.summary || 'No title',
|
|
255
253
|
description: issue.description || '',
|
|
256
254
|
stage: currentStage,
|
|
257
255
|
url: `${config.url}/issue/${issue.idReadable || issue.id}`,
|
|
258
|
-
reporter: issue.reporter ?
|
|
259
|
-
assignee: issue.assignee ?
|
|
256
|
+
reporter: issue.reporter ? issue.reporter.fullName || issue.reporter.login : 'Unknown',
|
|
257
|
+
assignee: issue.assignee ? issue.assignee.fullName || issue.assignee.login : null,
|
|
260
258
|
created: issue.created ? new Date(issue.created) : new Date(),
|
|
261
|
-
updated: issue.updated ? new Date(issue.updated) : new Date()
|
|
259
|
+
updated: issue.updated ? new Date(issue.updated) : new Date(),
|
|
262
260
|
};
|
|
263
261
|
|
|
264
262
|
await log(`✅ Retrieved YouTrack issue: ${transformedIssue.id} - ${transformedIssue.summary}`);
|
|
@@ -292,15 +290,15 @@ export async function updateYouTrackIssueStage(issueId, newStage, config) {
|
|
|
292
290
|
name: 'Stage',
|
|
293
291
|
value: {
|
|
294
292
|
$type: 'StateBundleElement',
|
|
295
|
-
name: newStage
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
]
|
|
293
|
+
name: newStage,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
],
|
|
299
297
|
};
|
|
300
298
|
|
|
301
299
|
await makeYouTrackRequest(endpoint, config, {
|
|
302
300
|
method: 'POST',
|
|
303
|
-
body: updateData
|
|
301
|
+
body: updateData,
|
|
304
302
|
});
|
|
305
303
|
|
|
306
304
|
await log(`✅ Updated YouTrack issue ${issueId} stage to "${newStage}"`);
|
|
@@ -328,12 +326,12 @@ export async function addYouTrackComment(issueId, comment, config) {
|
|
|
328
326
|
const endpoint = `/issues/${issueId}/comments`;
|
|
329
327
|
const commentData = {
|
|
330
328
|
text: comment,
|
|
331
|
-
usesMarkdown: true
|
|
329
|
+
usesMarkdown: true,
|
|
332
330
|
};
|
|
333
331
|
|
|
334
332
|
await makeYouTrackRequest(endpoint, config, {
|
|
335
333
|
method: 'POST',
|
|
336
|
-
body: commentData
|
|
334
|
+
body: commentData,
|
|
337
335
|
});
|
|
338
336
|
|
|
339
337
|
await log(`✅ Added comment to YouTrack issue ${issueId}`);
|
|
@@ -354,7 +352,7 @@ export function createYouTrackConfigFromEnv() {
|
|
|
354
352
|
apiKey: process.env.YOUTRACK_API_KEY,
|
|
355
353
|
projectCode: process.env.YOUTRACK_PROJECT_CODE,
|
|
356
354
|
stage: process.env.YOUTRACK_STAGE,
|
|
357
|
-
nextStage: process.env.YOUTRACK_NEXT_STAGE
|
|
355
|
+
nextStage: process.env.YOUTRACK_NEXT_STAGE,
|
|
358
356
|
};
|
|
359
357
|
|
|
360
358
|
// Check if basic configuration is available
|
|
@@ -383,7 +381,7 @@ export function parseYouTrackIssueId(input) {
|
|
|
383
381
|
// URL format: https://company.youtrack.cloud/issue/PROJECT-123
|
|
384
382
|
/\/issue\/([A-Z0-9][A-Z0-9]*-\d+)/i,
|
|
385
383
|
// Text containing issue ID
|
|
386
|
-
/\b([A-Z0-9][A-Z0-9]*-\d+)\b/i
|
|
384
|
+
/\b([A-Z0-9][A-Z0-9]*-\d+)\b/i,
|
|
387
385
|
];
|
|
388
386
|
|
|
389
387
|
for (const pattern of patterns) {
|
|
@@ -415,11 +413,11 @@ export function convertYouTrackIssueForGitHub(youTrackIssue, githubRepoUrl) {
|
|
|
415
413
|
url: youTrackIssue.url,
|
|
416
414
|
stage: youTrackIssue.stage,
|
|
417
415
|
reporter: youTrackIssue.reporter,
|
|
418
|
-
assignee: youTrackIssue.assignee
|
|
416
|
+
assignee: youTrackIssue.assignee,
|
|
419
417
|
},
|
|
420
418
|
// Store GitHub repo info for PR creation
|
|
421
419
|
github: {
|
|
422
|
-
repoUrl: githubRepoUrl
|
|
423
|
-
}
|
|
420
|
+
repoUrl: githubRepoUrl,
|
|
421
|
+
},
|
|
424
422
|
};
|
|
425
|
-
}
|
|
423
|
+
}
|