@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
|
@@ -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];
|
package/src/github.batch.lib.mjs
CHANGED
|
@@ -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
|
|
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
|
-
}`
|
|
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
|
|
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
|
-
}`
|
|
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)}`, {
|
|
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})`, {
|
|
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}`, {
|
|
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 });
|