@link-assistant/hive-mind 0.46.1 → 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.
Files changed (63) hide show
  1. package/CHANGELOG.md +10 -15
  2. package/README.md +42 -8
  3. package/package.json +16 -3
  4. package/src/agent.lib.mjs +49 -70
  5. package/src/agent.prompts.lib.mjs +6 -20
  6. package/src/buildUserMention.lib.mjs +4 -17
  7. package/src/claude-limits.lib.mjs +15 -15
  8. package/src/claude.lib.mjs +617 -626
  9. package/src/claude.prompts.lib.mjs +7 -22
  10. package/src/codex.lib.mjs +39 -71
  11. package/src/codex.prompts.lib.mjs +6 -20
  12. package/src/config.lib.mjs +3 -16
  13. package/src/contributing-guidelines.lib.mjs +5 -18
  14. package/src/exit-handler.lib.mjs +4 -4
  15. package/src/git.lib.mjs +7 -7
  16. package/src/github-issue-creator.lib.mjs +17 -17
  17. package/src/github-linking.lib.mjs +8 -33
  18. package/src/github.batch.lib.mjs +20 -16
  19. package/src/github.graphql.lib.mjs +18 -18
  20. package/src/github.lib.mjs +89 -91
  21. package/src/hive.config.lib.mjs +50 -50
  22. package/src/hive.mjs +1293 -1296
  23. package/src/instrument.mjs +7 -11
  24. package/src/interactive-mode.lib.mjs +112 -138
  25. package/src/lenv-reader.lib.mjs +1 -6
  26. package/src/lib.mjs +36 -45
  27. package/src/lino.lib.mjs +2 -2
  28. package/src/local-ci-checks.lib.mjs +15 -14
  29. package/src/memory-check.mjs +52 -60
  30. package/src/model-mapping.lib.mjs +25 -32
  31. package/src/model-validation.lib.mjs +31 -31
  32. package/src/opencode.lib.mjs +37 -62
  33. package/src/opencode.prompts.lib.mjs +7 -21
  34. package/src/protect-branch.mjs +14 -15
  35. package/src/review.mjs +28 -27
  36. package/src/reviewers-hive.mjs +64 -69
  37. package/src/sentry.lib.mjs +13 -10
  38. package/src/solve.auto-continue.lib.mjs +48 -38
  39. package/src/solve.auto-pr.lib.mjs +111 -69
  40. package/src/solve.branch-errors.lib.mjs +17 -46
  41. package/src/solve.branch.lib.mjs +16 -23
  42. package/src/solve.config.lib.mjs +263 -261
  43. package/src/solve.error-handlers.lib.mjs +21 -79
  44. package/src/solve.execution.lib.mjs +10 -18
  45. package/src/solve.feedback.lib.mjs +25 -46
  46. package/src/solve.mjs +59 -60
  47. package/src/solve.preparation.lib.mjs +10 -36
  48. package/src/solve.repo-setup.lib.mjs +4 -19
  49. package/src/solve.repository.lib.mjs +37 -37
  50. package/src/solve.results.lib.mjs +32 -46
  51. package/src/solve.session.lib.mjs +7 -22
  52. package/src/solve.validation.lib.mjs +19 -17
  53. package/src/solve.watch.lib.mjs +20 -33
  54. package/src/start-screen.mjs +24 -24
  55. package/src/task.mjs +38 -44
  56. package/src/telegram-bot.mjs +125 -121
  57. package/src/telegram-top-command.lib.mjs +32 -48
  58. package/src/usage-limit.lib.mjs +9 -13
  59. package/src/version-info.lib.mjs +1 -1
  60. package/src/version.lib.mjs +1 -1
  61. package/src/youtrack/solve.youtrack.lib.mjs +3 -8
  62. package/src/youtrack/youtrack-sync.mjs +8 -14
  63. package/src/youtrack/youtrack.lib.mjs +26 -28
@@ -23,11 +23,7 @@
23
23
  * @returns {string[]} Array of valid linking keywords
24
24
  */
25
25
  export function getGitHubLinkingKeywords() {
26
- return [
27
- 'close', 'closes', 'closed',
28
- 'fix', 'fixes', 'fixed',
29
- 'resolve', 'resolves', 'resolved'
30
- ];
26
+ return ['close', 'closes', 'closed', 'fix', 'fixes', 'fixed', 'resolve', 'resolves', 'resolved'];
31
27
  }
32
28
 
33
29
  /**
@@ -55,10 +51,7 @@ export function hasGitHubLinkingKeyword(prBody, issueNumber, owner = null, repo
55
51
  for (const keyword of keywords) {
56
52
  // Pattern 1: KEYWORD #123
57
53
  // Must have word boundary before keyword and # immediately before number
58
- const pattern1 = new RegExp(
59
- `\\b${keyword}\\s+#${issueNumStr}\\b`,
60
- 'i'
61
- );
54
+ const pattern1 = new RegExp(`\\b${keyword}\\s+#${issueNumStr}\\b`, 'i');
62
55
 
63
56
  if (pattern1.test(prBody)) {
64
57
  return true;
@@ -66,10 +59,7 @@ export function hasGitHubLinkingKeyword(prBody, issueNumber, owner = null, repo
66
59
 
67
60
  // Pattern 2: KEYWORD owner/repo#123 (for cross-repo or fork references)
68
61
  if (owner && repo) {
69
- const pattern2 = new RegExp(
70
- `\\b${keyword}\\s+${owner}/${repo}#${issueNumStr}\\b`,
71
- 'i'
72
- );
62
+ const pattern2 = new RegExp(`\\b${keyword}\\s+${owner}/${repo}#${issueNumStr}\\b`, 'i');
73
63
 
74
64
  if (pattern2.test(prBody)) {
75
65
  return true;
@@ -78,10 +68,7 @@ export function hasGitHubLinkingKeyword(prBody, issueNumber, owner = null, repo
78
68
 
79
69
  // Pattern 3: KEYWORD https://github.com/owner/repo/issues/123
80
70
  if (owner && repo) {
81
- const pattern3 = new RegExp(
82
- `\\b${keyword}\\s+https://github\\.com/${owner}/${repo}/issues/${issueNumStr}\\b`,
83
- 'i'
84
- );
71
+ const pattern3 = new RegExp(`\\b${keyword}\\s+https://github\\.com/${owner}/${repo}/issues/${issueNumStr}\\b`, 'i');
85
72
 
86
73
  if (pattern3.test(prBody)) {
87
74
  return true;
@@ -89,10 +76,7 @@ export function hasGitHubLinkingKeyword(prBody, issueNumber, owner = null, repo
89
76
  }
90
77
 
91
78
  // Pattern 4: Also check for any URL format (generic)
92
- const pattern4 = new RegExp(
93
- `\\b${keyword}\\s+https://github\\.com/[^/]+/[^/]+/issues/${issueNumStr}\\b`,
94
- 'i'
95
- );
79
+ const pattern4 = new RegExp(`\\b${keyword}\\s+https://github\\.com/[^/]+/[^/]+/issues/${issueNumStr}\\b`, 'i');
96
80
 
97
81
  if (pattern4.test(prBody)) {
98
82
  return true;
@@ -118,30 +102,21 @@ export function extractLinkedIssueNumber(prBody) {
118
102
 
119
103
  for (const keyword of keywords) {
120
104
  // Try to match: KEYWORD #123
121
- const pattern1 = new RegExp(
122
- `\\b${keyword}\\s+#(\\d+)\\b`,
123
- 'i'
124
- );
105
+ const pattern1 = new RegExp(`\\b${keyword}\\s+#(\\d+)\\b`, 'i');
125
106
  const match1 = prBody.match(pattern1);
126
107
  if (match1) {
127
108
  return match1[1];
128
109
  }
129
110
 
130
111
  // Try to match: KEYWORD owner/repo#123
131
- const pattern2 = new RegExp(
132
- `\\b${keyword}\\s+[^/\\s]+/[^/\\s]+#(\\d+)\\b`,
133
- 'i'
134
- );
112
+ const pattern2 = new RegExp(`\\b${keyword}\\s+[^/\\s]+/[^/\\s]+#(\\d+)\\b`, 'i');
135
113
  const match2 = prBody.match(pattern2);
136
114
  if (match2) {
137
115
  return match2[1];
138
116
  }
139
117
 
140
118
  // Try to match: KEYWORD https://github.com/owner/repo/issues/123
141
- const pattern3 = new RegExp(
142
- `\\b${keyword}\\s+https://github\\.com/[^/]+/[^/]+/issues/(\\d+)\\b`,
143
- 'i'
144
- );
119
+ const pattern3 = new RegExp(`\\b${keyword}\\s+https://github\\.com/[^/]+/[^/]+/issues/(\\d+)\\b`, 'i');
145
120
  const match3 = prBody.match(pattern3);
146
121
  if (match3) {
147
122
  return match3[1];
@@ -37,7 +37,9 @@ export async function batchCheckPullRequestsForIssues(owner, repo, issueNumbers)
37
37
  const query = `
38
38
  query GetPullRequestsForIssues {
39
39
  repository(owner: "${owner}", name: "${repo}") {
40
- ${batch.map(num => `
40
+ ${batch
41
+ .map(
42
+ num => `
41
43
  issue${num}: issue(number: ${num}) {
42
44
  number
43
45
  title
@@ -57,7 +59,9 @@ export async function batchCheckPullRequestsForIssues(owner, repo, issueNumbers)
57
59
  }
58
60
  }
59
61
  }
60
- }`).join('\n')}
62
+ }`
63
+ )
64
+ .join('\n')}
61
65
  }
62
66
  }
63
67
  `;
@@ -76,7 +80,7 @@ export async function batchCheckPullRequestsForIssues(owner, repo, issueNumbers)
76
80
  const { stdout } = await execAsync(`gh api graphql -f query='${query}'`, {
77
81
  encoding: 'utf8',
78
82
  maxBuffer: githubLimits.bufferMaxSize,
79
- env: process.env
83
+ env: process.env,
80
84
  });
81
85
 
82
86
  const data = JSON.parse(stdout);
@@ -94,7 +98,7 @@ export async function batchCheckPullRequestsForIssues(owner, repo, issueNumbers)
94
98
  number: item.source.number,
95
99
  title: item.source.title,
96
100
  state: item.source.state,
97
- url: item.source.url
101
+ url: item.source.url,
98
102
  });
99
103
  }
100
104
  }
@@ -103,20 +107,19 @@ export async function batchCheckPullRequestsForIssues(owner, repo, issueNumbers)
103
107
  title: issueData.title,
104
108
  state: issueData.state,
105
109
  openPRCount: linkedPRs.length,
106
- linkedPRs: linkedPRs
110
+ linkedPRs: linkedPRs,
107
111
  };
108
112
  } else {
109
113
  // Issue not found or error
110
114
  results[issueNum] = {
111
115
  openPRCount: 0,
112
116
  linkedPRs: [],
113
- error: 'Issue not found'
117
+ error: 'Issue not found',
114
118
  };
115
119
  }
116
120
  }
117
121
 
118
122
  await log(` ✅ Batch ${Math.floor(i / BATCH_SIZE) + 1}/${Math.ceil(issueNumbers.length / BATCH_SIZE)} processed (${batch.length} issues)`, { verbose: true });
119
-
120
123
  } catch (batchError) {
121
124
  await log(` ⚠️ GraphQL batch query failed: ${cleanErrorMessage(batchError)}`, { level: 'warning' });
122
125
 
@@ -135,13 +138,13 @@ export async function batchCheckPullRequestsForIssues(owner, repo, issueNumbers)
135
138
 
136
139
  results[issueNum] = {
137
140
  openPRCount: openPrCount,
138
- linkedPRs: [] // REST API doesn't give us PR details easily
141
+ linkedPRs: [], // REST API doesn't give us PR details easily
139
142
  };
140
143
  } catch (restError) {
141
144
  results[issueNum] = {
142
145
  openPRCount: 0,
143
146
  linkedPRs: [],
144
- error: cleanErrorMessage(restError)
147
+ error: cleanErrorMessage(restError),
145
148
  };
146
149
  }
147
150
  }
@@ -154,7 +157,6 @@ export async function batchCheckPullRequestsForIssues(owner, repo, issueNumbers)
154
157
  await log(` 📊 Batch PR check complete: ${issuesWithPRs}/${totalIssues} issues have open PRs`, { verbose: true });
155
158
 
156
159
  return results;
157
-
158
160
  } catch (error) {
159
161
  await log(` ❌ Batch PR check failed: ${cleanErrorMessage(error)}`, { level: 'error' });
160
162
  return {};
@@ -183,11 +185,15 @@ export async function batchCheckArchivedRepositories(repositories) {
183
185
  const batch = repositories.slice(i, i + BATCH_SIZE);
184
186
 
185
187
  // Build GraphQL query for this batch
186
- const queryFields = batch.map((repo, index) => `
188
+ const queryFields = batch
189
+ .map(
190
+ (repo, index) => `
187
191
  repo${index}: repository(owner: "${repo.owner}", name: "${repo.name}") {
188
192
  nameWithOwner
189
193
  isArchived
190
- }`).join('\n');
194
+ }`
195
+ )
196
+ .join('\n');
191
197
 
192
198
  const query = `
193
199
  query CheckArchivedStatus {
@@ -209,7 +215,7 @@ export async function batchCheckArchivedRepositories(repositories) {
209
215
  const { stdout } = await execAsync(`gh api graphql -f query='${query}'`, {
210
216
  encoding: 'utf8',
211
217
  maxBuffer: githubLimits.bufferMaxSize,
212
- env: process.env
218
+ env: process.env,
213
219
  });
214
220
 
215
221
  const data = JSON.parse(stdout);
@@ -224,7 +230,6 @@ export async function batchCheckArchivedRepositories(repositories) {
224
230
  });
225
231
 
226
232
  await log(` ✅ Batch ${Math.floor(i / BATCH_SIZE) + 1}/${Math.ceil(repositories.length / BATCH_SIZE)} processed (${batch.length} repositories)`, { verbose: true });
227
-
228
233
  } catch (batchError) {
229
234
  await log(` ⚠️ GraphQL batch query failed: ${cleanErrorMessage(batchError)}`, { level: 'warning' });
230
235
 
@@ -258,7 +263,6 @@ export async function batchCheckArchivedRepositories(repositories) {
258
263
  await log(` 📊 Batch archived check complete: ${archivedCount}/${repositories.length} repositories are archived`, { verbose: true });
259
264
 
260
265
  return results;
261
-
262
266
  } catch (error) {
263
267
  await log(` ❌ Batch archived check failed: ${cleanErrorMessage(error)}`, { level: 'error' });
264
268
  return {};
@@ -268,5 +272,5 @@ export async function batchCheckArchivedRepositories(repositories) {
268
272
  // Export all functions as default object too
269
273
  export default {
270
274
  batchCheckPullRequestsForIssues,
271
- batchCheckArchivedRepositories
275
+ batchCheckArchivedRepositories,
272
276
  };
@@ -47,7 +47,7 @@ async function fetchRepositoryIssuesWithPagination(owner, repoName, log, cleanEr
47
47
  `;
48
48
 
49
49
  // Execute GraphQL query
50
- const escapedQuery = graphqlQuery.replace(/'/g, '\'\\\'\'');
50
+ const escapedQuery = graphqlQuery.replace(/'/g, "'\\''");
51
51
  let graphqlCmd = `gh api graphql -f query='${escapedQuery}' -f owner='${owner}' -f repo='${repoName}' -F issueLimit=${issueLimit}`;
52
52
 
53
53
  if (cursor) {
@@ -74,9 +74,10 @@ async function fetchRepositoryIssuesWithPagination(owner, repoName, log, cleanEr
74
74
  }
75
75
 
76
76
  return allIssues;
77
-
78
77
  } catch (error) {
79
- await log(` ❌ Failed to fetch issues from ${owner}/${repoName}: ${cleanErrorMessage(error)}`, { verbose: true });
78
+ await log(` ❌ Failed to fetch issues from ${owner}/${repoName}: ${cleanErrorMessage(error)}`, {
79
+ verbose: true,
80
+ });
80
81
  // Return what we have so far
81
82
  return allIssues;
82
83
  }
@@ -112,7 +113,8 @@ export async function tryFetchIssuesWithGraphQL(owner, scope, log, cleanErrorMes
112
113
  repoPageNum++;
113
114
 
114
115
  // Build GraphQL query to fetch repos
115
- const graphqlQuery = isOrg ? `
116
+ const graphqlQuery = isOrg
117
+ ? `
116
118
  query($owner: String!, $repoLimit: Int!, $cursor: String) {
117
119
  organization(login: $owner) {
118
120
  repositories(first: $repoLimit, orderBy: {field: UPDATED_AT, direction: DESC}, after: $cursor) {
@@ -134,7 +136,8 @@ export async function tryFetchIssuesWithGraphQL(owner, scope, log, cleanErrorMes
134
136
  }
135
137
  }
136
138
  }
137
- ` : `
139
+ `
140
+ : `
138
141
  query($owner: String!, $repoLimit: Int!, $cursor: String) {
139
142
  user(login: $owner) {
140
143
  repositories(first: $repoLimit, orderBy: {field: UPDATED_AT, direction: DESC}, after: $cursor) {
@@ -159,7 +162,7 @@ export async function tryFetchIssuesWithGraphQL(owner, scope, log, cleanErrorMes
159
162
  `;
160
163
 
161
164
  // Execute GraphQL query
162
- const escapedQuery = graphqlQuery.replace(/'/g, '\'\\\'\'');
165
+ const escapedQuery = graphqlQuery.replace(/'/g, "'\\''");
163
166
  let graphqlCmd = `gh api graphql -f query='${escapedQuery}' -f owner='${owner}' -F repoLimit=${repoLimit}`;
164
167
 
165
168
  if (repoCursor) {
@@ -183,7 +186,9 @@ export async function tryFetchIssuesWithGraphQL(owner, scope, log, cleanErrorMes
183
186
  repoCursor = repos.pageInfo.endCursor;
184
187
 
185
188
  const totalRepos = repos.totalCount;
186
- await log(` ✅ Fetched ${repos.nodes.length} repositories (total so far: ${allRepos.length}/${totalRepos})`, { verbose: true });
189
+ await log(` ✅ Fetched ${repos.nodes.length} repositories (total so far: ${allRepos.length}/${totalRepos})`, {
190
+ verbose: true,
191
+ });
187
192
  }
188
193
 
189
194
  await log(` 📊 Fetched all ${allRepos.length} repositories`, { verbose: true });
@@ -221,13 +226,7 @@ export async function tryFetchIssuesWithGraphQL(owner, scope, log, cleanErrorMes
221
226
  await log(` 🔍 Fetching ${issueCount} issue(s) from ${repo.owner.login}/${repo.name}...`, { verbose: true });
222
227
 
223
228
  // Fetch all issues from this repository with pagination
224
- const repoIssues = await fetchRepositoryIssuesWithPagination(
225
- repo.owner.login,
226
- repo.name,
227
- log,
228
- cleanErrorMessage,
229
- issueLimit
230
- );
229
+ const repoIssues = await fetchRepositoryIssuesWithPagination(repo.owner.login, repo.name, log, cleanErrorMessage, issueLimit);
231
230
 
232
231
  // Add repository information to each issue
233
232
  for (const issue of repoIssues) {
@@ -235,21 +234,22 @@ export async function tryFetchIssuesWithGraphQL(owner, scope, log, cleanErrorMes
235
234
  ...issue,
236
235
  repository: {
237
236
  name: repo.name,
238
- owner: repo.owner
239
- }
237
+ owner: repo.owner,
238
+ },
240
239
  });
241
240
  }
242
241
 
243
242
  if (repoIssues.length > 0) {
244
243
  reposWithIssues++;
245
- await log(` ✅ Collected ${repoIssues.length} issue(s) from ${repo.owner.login}/${repo.name}`, { verbose: true });
244
+ await log(` ✅ Collected ${repoIssues.length} issue(s) from ${repo.owner.login}/${repo.name}`, {
245
+ verbose: true,
246
+ });
246
247
  }
247
248
  }
248
249
 
249
250
  await log(` ✅ GraphQL pagination complete: ${nonArchivedRepos.length} non-archived repos, ${allIssues.length} issues from ${reposWithIssues} repos with issues`, { verbose: true });
250
251
 
251
252
  return { success: true, issues: allIssues, repoCount: nonArchivedRepos.length };
252
-
253
253
  } catch (error) {
254
254
  await log(` ❌ GraphQL approach failed: ${cleanErrorMessage(error)}`, { verbose: true });
255
255
  await log(' 💡 Falling back to gh api --paginate approach...', { verbose: true });