@link-assistant/hive-mind 1.25.8 → 1.26.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 +11 -0
- package/package.json +1 -1
- package/src/claude.lib.mjs +2 -12
- package/src/github-merge.lib.mjs +171 -0
- package/src/telegram-merge-queue.lib.mjs +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.26.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d96ae3b: feat: /merge command syncs ready tags between linked PRs and issues (Issue #1367)
|
|
8
|
+
|
|
9
|
+
The `/merge` Telegram bot command now syncs the `ready` label between PRs and their linked issues before building the merge queue.
|
|
10
|
+
- If a PR has the `ready` label and its body links to an issue via standard GitHub closing keywords (fixes/closes/resolves #N), the linked issue also gets the `ready` label
|
|
11
|
+
- If an issue has the `ready` label and has a clearly linked open PR (found via body search), the PR also gets the `ready` label
|
|
12
|
+
- Sync happens during `MergeQueueProcessor.initialize()`, before the final list of ready PRs is collected
|
|
13
|
+
|
|
3
14
|
## 1.25.8
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/package.json
CHANGED
package/src/claude.lib.mjs
CHANGED
|
@@ -1475,15 +1475,5 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
|
|
|
1475
1475
|
}
|
|
1476
1476
|
};
|
|
1477
1477
|
// Export all functions as default object too
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
handleClaudeRuntimeSwitch,
|
|
1481
|
-
executeClaude,
|
|
1482
|
-
executeClaudeCommand,
|
|
1483
|
-
checkForUncommittedChanges,
|
|
1484
|
-
calculateSessionTokens,
|
|
1485
|
-
getClaudeVersion,
|
|
1486
|
-
setClaudeVersion,
|
|
1487
|
-
resolveThinkingSettings,
|
|
1488
|
-
checkModelVisionCapability,
|
|
1489
|
-
};
|
|
1478
|
+
// prettier-ignore
|
|
1479
|
+
export default { validateClaudeConnection, handleClaudeRuntimeSwitch, executeClaude, executeClaudeCommand, checkForUncommittedChanges, calculateSessionTokens, getClaudeVersion, setClaudeVersion, resolveThinkingSettings, checkModelVisionCapability };
|
package/src/github-merge.lib.mjs
CHANGED
|
@@ -19,6 +19,9 @@ const exec = promisify(execCallback);
|
|
|
19
19
|
// Import GitHub URL parser
|
|
20
20
|
import { parseGitHubUrl } from './github.lib.mjs';
|
|
21
21
|
|
|
22
|
+
// Import linking utilities
|
|
23
|
+
import { extractLinkedIssueNumber } from './github-linking.lib.mjs';
|
|
24
|
+
|
|
22
25
|
// Default label configuration
|
|
23
26
|
export const READY_LABEL = {
|
|
24
27
|
name: 'ready',
|
|
@@ -251,6 +254,172 @@ export async function fetchReadyIssuesWithPRs(owner, repo, verbose = false) {
|
|
|
251
254
|
}
|
|
252
255
|
}
|
|
253
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Add a label to a GitHub issue or pull request
|
|
259
|
+
* @param {'issue'|'pr'} type - Whether to add to issue or PR
|
|
260
|
+
* @param {string} owner - Repository owner
|
|
261
|
+
* @param {string} repo - Repository name
|
|
262
|
+
* @param {number} number - Issue or PR number
|
|
263
|
+
* @param {string} labelName - Label name to add
|
|
264
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
265
|
+
* @returns {Promise<{success: boolean, error: string|null}>}
|
|
266
|
+
*/
|
|
267
|
+
async function addLabel(type, owner, repo, number, labelName, verbose = false) {
|
|
268
|
+
const cmd = type === 'issue' ? 'issue' : 'pr';
|
|
269
|
+
try {
|
|
270
|
+
await exec(`gh ${cmd} edit ${number} --repo ${owner}/${repo} --add-label "${labelName}"`);
|
|
271
|
+
if (verbose) console.log(`[VERBOSE] /merge: Added '${labelName}' label to ${type} #${number}`);
|
|
272
|
+
return { success: true, error: null };
|
|
273
|
+
} catch (error) {
|
|
274
|
+
if (verbose) console.log(`[VERBOSE] /merge: Failed to add label to ${type} #${number}: ${error.message}`);
|
|
275
|
+
return { success: false, error: error.message };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Sync 'ready' tags between linked pull requests and issues
|
|
281
|
+
*
|
|
282
|
+
* Issue #1367: Before building the merge queue, ensure that:
|
|
283
|
+
* 1. If a PR has 'ready' label and is clearly linked to an issue (via standard GitHub
|
|
284
|
+
* keywords in the PR body/title), the issue also gets 'ready' label.
|
|
285
|
+
* 2. If an issue has 'ready' label and has a clearly linked open PR, the PR also gets
|
|
286
|
+
* 'ready' label.
|
|
287
|
+
*
|
|
288
|
+
* This ensures the final list of ready PRs reflects all ready work, regardless of
|
|
289
|
+
* where the 'ready' label was originally applied.
|
|
290
|
+
*
|
|
291
|
+
* @param {string} owner - Repository owner
|
|
292
|
+
* @param {string} repo - Repository name
|
|
293
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
294
|
+
* @returns {Promise<{synced: number, errors: number, details: Array<Object>}>}
|
|
295
|
+
*/
|
|
296
|
+
export async function syncReadyTags(owner, repo, verbose = false) {
|
|
297
|
+
const synced = [];
|
|
298
|
+
const errors = [];
|
|
299
|
+
|
|
300
|
+
if (verbose) {
|
|
301
|
+
console.log(`[VERBOSE] /merge: Syncing 'ready' tags for ${owner}/${repo}...`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
// Fetch open PRs with 'ready' label (including body for link detection)
|
|
306
|
+
const { stdout: prsJson } = await exec(`gh pr list --repo ${owner}/${repo} --label "${READY_LABEL.name}" --state open --json number,title,body,labels --limit 100`);
|
|
307
|
+
const readyPRs = JSON.parse(prsJson.trim() || '[]');
|
|
308
|
+
|
|
309
|
+
if (verbose) {
|
|
310
|
+
console.log(`[VERBOSE] /merge: Found ${readyPRs.length} open PRs with 'ready' label for tag sync`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Fetch open issues with 'ready' label
|
|
314
|
+
const { stdout: issuesJson } = await exec(`gh issue list --repo ${owner}/${repo} --label "${READY_LABEL.name}" --state open --json number,title --limit 100`);
|
|
315
|
+
const readyIssues = JSON.parse(issuesJson.trim() || '[]');
|
|
316
|
+
|
|
317
|
+
if (verbose) {
|
|
318
|
+
console.log(`[VERBOSE] /merge: Found ${readyIssues.length} open issues with 'ready' label for tag sync`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Build a set of issue numbers that already have 'ready'
|
|
322
|
+
const readyIssueNumbers = new Set(readyIssues.map(i => String(i.number)));
|
|
323
|
+
|
|
324
|
+
// Step 1: For each PR with 'ready', find linked issue and sync label to it
|
|
325
|
+
for (const pr of readyPRs) {
|
|
326
|
+
try {
|
|
327
|
+
const prBody = pr.body || '';
|
|
328
|
+
const linkedIssueNumber = extractLinkedIssueNumber(prBody);
|
|
329
|
+
|
|
330
|
+
if (!linkedIssueNumber) {
|
|
331
|
+
if (verbose) {
|
|
332
|
+
console.log(`[VERBOSE] /merge: PR #${pr.number} has no linked issue (no closing keyword in body)`);
|
|
333
|
+
}
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (readyIssueNumbers.has(String(linkedIssueNumber))) {
|
|
338
|
+
if (verbose) {
|
|
339
|
+
console.log(`[VERBOSE] /merge: Issue #${linkedIssueNumber} already has 'ready' label (linked from PR #${pr.number})`);
|
|
340
|
+
}
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Issue doesn't have 'ready' label yet - add it
|
|
345
|
+
if (verbose) {
|
|
346
|
+
console.log(`[VERBOSE] /merge: PR #${pr.number} has 'ready', adding to linked issue #${linkedIssueNumber}`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const result = await addLabel('issue', owner, repo, linkedIssueNumber, READY_LABEL.name, verbose);
|
|
350
|
+
if (result.success) {
|
|
351
|
+
synced.push({ type: 'pr-to-issue', prNumber: pr.number, issueNumber: Number(linkedIssueNumber) });
|
|
352
|
+
// Mark this issue as now having 'ready' so we don't process it again
|
|
353
|
+
readyIssueNumbers.add(String(linkedIssueNumber));
|
|
354
|
+
} else {
|
|
355
|
+
errors.push({ type: 'pr-to-issue', prNumber: pr.number, issueNumber: Number(linkedIssueNumber), error: result.error });
|
|
356
|
+
}
|
|
357
|
+
} catch (err) {
|
|
358
|
+
if (verbose) {
|
|
359
|
+
console.log(`[VERBOSE] /merge: Error syncing label from PR #${pr.number}: ${err.message}`);
|
|
360
|
+
}
|
|
361
|
+
errors.push({ type: 'pr-to-issue', prNumber: pr.number, error: err.message });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Build a set of PR numbers that already have 'ready'
|
|
366
|
+
const readyPRNumbers = new Set(readyPRs.map(p => String(p.number)));
|
|
367
|
+
|
|
368
|
+
// Step 2: For each issue with 'ready', find linked PRs and sync label to them
|
|
369
|
+
for (const issue of readyIssues) {
|
|
370
|
+
try {
|
|
371
|
+
// Search for open PRs linked to this issue via closing keywords
|
|
372
|
+
const { stdout: linkedPRsJson } = await exec(`gh pr list --repo ${owner}/${repo} --search "in:body closes #${issue.number} OR fixes #${issue.number} OR resolves #${issue.number}" --state open --json number,title,labels --limit 10`);
|
|
373
|
+
const linkedPRs = JSON.parse(linkedPRsJson.trim() || '[]');
|
|
374
|
+
|
|
375
|
+
for (const linkedPR of linkedPRs) {
|
|
376
|
+
if (readyPRNumbers.has(String(linkedPR.number))) {
|
|
377
|
+
if (verbose) {
|
|
378
|
+
console.log(`[VERBOSE] /merge: PR #${linkedPR.number} already has 'ready' label (linked from issue #${issue.number})`);
|
|
379
|
+
}
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// PR doesn't have 'ready' label yet - add it
|
|
384
|
+
if (verbose) {
|
|
385
|
+
console.log(`[VERBOSE] /merge: Issue #${issue.number} has 'ready', adding to linked PR #${linkedPR.number}`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const result = await addLabel('pr', owner, repo, linkedPR.number, READY_LABEL.name, verbose);
|
|
389
|
+
if (result.success) {
|
|
390
|
+
synced.push({ type: 'issue-to-pr', issueNumber: issue.number, prNumber: linkedPR.number });
|
|
391
|
+
// Mark this PR as now having 'ready'
|
|
392
|
+
readyPRNumbers.add(String(linkedPR.number));
|
|
393
|
+
} else {
|
|
394
|
+
errors.push({ type: 'issue-to-pr', issueNumber: issue.number, prNumber: linkedPR.number, error: result.error });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
} catch (err) {
|
|
398
|
+
if (verbose) {
|
|
399
|
+
console.log(`[VERBOSE] /merge: Error syncing label from issue #${issue.number}: ${err.message}`);
|
|
400
|
+
}
|
|
401
|
+
errors.push({ type: 'issue-to-pr', issueNumber: issue.number, error: err.message });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} catch (error) {
|
|
405
|
+
if (verbose) {
|
|
406
|
+
console.log(`[VERBOSE] /merge: Error during tag sync: ${error.message}`);
|
|
407
|
+
}
|
|
408
|
+
errors.push({ type: 'fetch', error: error.message });
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (verbose) {
|
|
412
|
+
console.log(`[VERBOSE] /merge: Tag sync complete. Synced: ${synced.length}, Errors: ${errors.length}`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
synced: synced.length,
|
|
417
|
+
errors: errors.length,
|
|
418
|
+
details: synced,
|
|
419
|
+
errorDetails: errors,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
254
423
|
/**
|
|
255
424
|
* Get combined list of ready PRs (from both direct PR labels and issue labels)
|
|
256
425
|
* @param {string} owner - Repository owner
|
|
@@ -1283,6 +1452,8 @@ export default {
|
|
|
1283
1452
|
fetchReadyPullRequests,
|
|
1284
1453
|
fetchReadyIssuesWithPRs,
|
|
1285
1454
|
getAllReadyPRs,
|
|
1455
|
+
// Issue #1367: Sync 'ready' tags between linked PRs and issues
|
|
1456
|
+
syncReadyTags,
|
|
1286
1457
|
checkPRCIStatus,
|
|
1287
1458
|
checkPRMergeable,
|
|
1288
1459
|
checkMergePermissions,
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* @see https://github.com/link-assistant/hive-mind/issues/1143
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { getAllReadyPRs, checkPRCIStatus, checkPRMergeable, mergePullRequest, waitForCI, ensureReadyLabel, waitForBranchCI, getDefaultBranch, waitForCommitCI, checkBranchCIHealth, getMergeCommitSha } from './github-merge.lib.mjs';
|
|
19
|
+
import { getAllReadyPRs, checkPRCIStatus, checkPRMergeable, mergePullRequest, waitForCI, ensureReadyLabel, waitForBranchCI, getDefaultBranch, waitForCommitCI, checkBranchCIHealth, getMergeCommitSha, syncReadyTags } from './github-merge.lib.mjs';
|
|
20
20
|
import { mergeQueue as mergeQueueConfig } from './config.lib.mjs';
|
|
21
21
|
import { getProgressBar } from './limits.lib.mjs';
|
|
22
22
|
|
|
@@ -197,6 +197,16 @@ export class MergeQueueProcessor {
|
|
|
197
197
|
this.log("Created 'ready' label in repository");
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
// Issue #1367: Sync 'ready' tags between linked PRs and issues before collecting the queue
|
|
201
|
+
// This ensures the final list reflects all ready work regardless of where the tag was applied
|
|
202
|
+
const syncResult = await syncReadyTags(this.owner, this.repo, this.verbose);
|
|
203
|
+
if (syncResult.synced > 0) {
|
|
204
|
+
this.log(`Synced 'ready' tag: ${syncResult.synced} item(s) updated`);
|
|
205
|
+
}
|
|
206
|
+
if (syncResult.errors > 0) {
|
|
207
|
+
this.log(`Tag sync had ${syncResult.errors} error(s) (non-fatal, proceeding)`);
|
|
208
|
+
}
|
|
209
|
+
|
|
200
210
|
// Fetch all ready PRs
|
|
201
211
|
const readyPRs = await getAllReadyPRs(this.owner, this.repo, this.verbose);
|
|
202
212
|
|