@link-assistant/hive-mind 2.11.13 → 2.12.1
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 +18 -0
- package/package.json +4 -1
- package/src/agent-command.lib.mjs +74 -0
- package/src/agent.lib.mjs +59 -34
- package/src/agentic-cli-updater.lib.mjs +241 -0
- package/src/claude.connection.lib.mjs +209 -0
- package/src/claude.lib.mjs +6 -202
- package/src/codex.lib.mjs +0 -128
- package/src/formal-ai-isolation.lib.mjs +62 -0
- package/src/formal-ai-maintenance.lib.mjs +106 -0
- package/src/formal-ai-model.lib.mjs +25 -0
- package/src/formal-ai-runtime.lib.mjs +10 -0
- package/src/formal-ai-sidecar.lib.mjs +565 -0
- package/src/formal-ai-updater.lib.mjs +294 -0
- package/src/formal-ai-version.lib.mjs +100 -0
- package/src/formal-ai.lib.mjs +11 -16
- package/src/github-rate-limit.lib.mjs +3 -0
- package/src/github-url-parser.lib.mjs +255 -0
- package/src/github.lib.mjs +22 -343
- package/src/hive.mjs +0 -152
- package/src/interactive-mode.lib.mjs +0 -43
- package/src/isolation-runner.lib.mjs +44 -173
- package/src/limits.lib.mjs +0 -89
- package/src/model-args.lib.mjs +32 -0
- package/src/models/index.mjs +5 -19
- package/src/session-monitor.lib.mjs +14 -172
- package/src/solve.auto-merge.lib.mjs +70 -164
- package/src/solve.mjs +31 -193
- package/src/solve.repository.lib.mjs +0 -83
- package/src/solve.results.lib.mjs +2 -92
- package/src/solve.session.lib.mjs +52 -19
- package/src/solve.tool-uncommitted.lib.mjs +22 -0
- package/src/state-lock.lib.mjs +82 -0
- package/src/telegram-bot.mjs +17 -65
- package/src/telegram-fix-command.lib.mjs +1 -8
- package/src/telegram-merge-queue.lib.mjs +3 -155
- package/src/telegram-solve-queue.lib.mjs +9 -168
- package/src/telegram-task-command.lib.mjs +1 -8
- package/src/use-m-bootstrap.lib.mjs +6 -5
- package/src/use-with-retry.lib.mjs +128 -2
- package/src/working-session-summary.lib.mjs +47 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { reportError } from './sentry.lib.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Universal GitHub URL parser that handles various formats
|
|
5
|
+
* @param {string} url - The GitHub URL to parse
|
|
6
|
+
* @returns {Object} Parsed URL information including:
|
|
7
|
+
* - valid: boolean indicating if the URL is valid
|
|
8
|
+
* - normalized: the normalized URL (https://github.com/...)
|
|
9
|
+
* - type: 'user', 'repo', 'issue', 'pull', 'gist', 'actions', etc.
|
|
10
|
+
* - owner: repository owner/organization
|
|
11
|
+
* - repo: repository name (if applicable)
|
|
12
|
+
* - number: issue/PR number (if applicable)
|
|
13
|
+
* - path: additional path components
|
|
14
|
+
* - error: error message if invalid
|
|
15
|
+
*/
|
|
16
|
+
export function parseGitHubUrl(url) {
|
|
17
|
+
if (!url || typeof url !== 'string') {
|
|
18
|
+
return {
|
|
19
|
+
valid: false,
|
|
20
|
+
error: 'Invalid input: URL must be a non-empty string',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// Trim whitespace and remove trailing slashes
|
|
24
|
+
let normalizedUrl = url.trim().replace(/\/+$/, '');
|
|
25
|
+
// Check if this looks like a valid GitHub-related input Reject clearly invalid inputs (spaces in the URL, special chars at the start, etc.)
|
|
26
|
+
if (/\s/.test(normalizedUrl) || /^[!@#$%^&*()[\]{}|\\:;"'<>,?`~]/.test(normalizedUrl)) {
|
|
27
|
+
return {
|
|
28
|
+
valid: false,
|
|
29
|
+
error: 'Invalid GitHub URL format',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// Handle protocol normalization
|
|
33
|
+
if (!normalizedUrl.startsWith('http://') && !normalizedUrl.startsWith('https://')) {
|
|
34
|
+
// Check if it starts with github.com
|
|
35
|
+
if (normalizedUrl.startsWith('github.com/')) {
|
|
36
|
+
normalizedUrl = 'https://' + normalizedUrl;
|
|
37
|
+
} else if (!normalizedUrl.includes('github.com')) {
|
|
38
|
+
// Assume it's a shorthand format (owner, owner/repo, owner/repo/issues/123, etc.)
|
|
39
|
+
normalizedUrl = 'https://github.com/' + normalizedUrl;
|
|
40
|
+
} else {
|
|
41
|
+
// Has github.com somewhere but not at the start - likely malformed
|
|
42
|
+
return {
|
|
43
|
+
valid: false,
|
|
44
|
+
error: 'Invalid GitHub URL format',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Convert http to https
|
|
49
|
+
if (normalizedUrl.startsWith('http://')) {
|
|
50
|
+
normalizedUrl = normalizedUrl.replace(/^http:\/\//, 'https://');
|
|
51
|
+
}
|
|
52
|
+
// Check for backslashes in the URL path (excluding query params and hash) According to RFC 3986, backslash is not a valid character in URL paths
|
|
53
|
+
const urlBeforeQueryAndHash = normalizedUrl.split('?')[0].split('#')[0];
|
|
54
|
+
if (urlBeforeQueryAndHash.includes('\\')) {
|
|
55
|
+
// Generate suggested URL by replacing backslashes with forward slashes
|
|
56
|
+
const suggestedUrl = urlBeforeQueryAndHash.replace(/\\/g, '/');
|
|
57
|
+
const urlAfterPath = normalizedUrl.substring(urlBeforeQueryAndHash.length);
|
|
58
|
+
return {
|
|
59
|
+
valid: false,
|
|
60
|
+
error: 'Invalid character in URL: backslash (\\) is not allowed in URL paths',
|
|
61
|
+
suggestion: suggestedUrl + urlAfterPath,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// Parse the URL
|
|
65
|
+
let urlObj;
|
|
66
|
+
try {
|
|
67
|
+
urlObj = new globalThis.URL(normalizedUrl);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
if (global.verboseMode) {
|
|
70
|
+
reportError(e, {
|
|
71
|
+
context: 'github.lib.mjs - URL parsing',
|
|
72
|
+
level: 'debug',
|
|
73
|
+
url: normalizedUrl,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
valid: false,
|
|
78
|
+
error: 'Invalid URL format',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Ensure it's a GitHub URL
|
|
82
|
+
if (urlObj.hostname !== 'github.com' && urlObj.hostname !== 'www.github.com') {
|
|
83
|
+
return {
|
|
84
|
+
valid: false,
|
|
85
|
+
error: 'Not a GitHub URL',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Normalize hostname
|
|
89
|
+
if (urlObj.hostname === 'www.github.com') {
|
|
90
|
+
normalizedUrl = normalizedUrl.replace('www.github.com', 'github.com');
|
|
91
|
+
urlObj = new globalThis.URL(normalizedUrl);
|
|
92
|
+
}
|
|
93
|
+
// Parse the pathname
|
|
94
|
+
const pathParts = urlObj.pathname.split('/').filter(p => p);
|
|
95
|
+
// Handle different GitHub URL patterns
|
|
96
|
+
const result = {
|
|
97
|
+
valid: true,
|
|
98
|
+
normalized: normalizedUrl,
|
|
99
|
+
hostname: 'github.com',
|
|
100
|
+
protocol: 'https',
|
|
101
|
+
path: urlObj.pathname,
|
|
102
|
+
};
|
|
103
|
+
// No path - just github.com
|
|
104
|
+
if (pathParts.length === 0) {
|
|
105
|
+
result.type = 'home';
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
// User/Organization page: /owner
|
|
109
|
+
if (pathParts.length === 1) {
|
|
110
|
+
result.type = 'user';
|
|
111
|
+
result.owner = pathParts[0];
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
// Set owner for all other cases
|
|
115
|
+
result.owner = pathParts[0];
|
|
116
|
+
// Repository page: /owner/repo
|
|
117
|
+
if (pathParts.length === 2) {
|
|
118
|
+
result.type = 'repo';
|
|
119
|
+
result.repo = pathParts[1];
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
// Set repo for paths with 3+ parts
|
|
123
|
+
result.repo = pathParts[1];
|
|
124
|
+
// Handle specific GitHub paths
|
|
125
|
+
const thirdPart = pathParts[2];
|
|
126
|
+
switch (thirdPart) {
|
|
127
|
+
case 'issues':
|
|
128
|
+
if (pathParts.length === 3) {
|
|
129
|
+
// /owner/repo/issues - issues list
|
|
130
|
+
result.type = 'issues_list';
|
|
131
|
+
} else if (pathParts.length === 4 && /^\d+$/.test(pathParts[3])) {
|
|
132
|
+
// /owner/repo/issues/123 - specific issue
|
|
133
|
+
result.type = 'issue';
|
|
134
|
+
result.number = parseInt(pathParts[3]);
|
|
135
|
+
} else {
|
|
136
|
+
result.type = 'issues_page';
|
|
137
|
+
result.subpath = pathParts.slice(3).join('/');
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
case 'pull':
|
|
141
|
+
if (pathParts.length === 4 && /^\d+$/.test(pathParts[3])) {
|
|
142
|
+
// /owner/repo/pull/456 - specific PR
|
|
143
|
+
result.type = 'pull';
|
|
144
|
+
result.number = parseInt(pathParts[3]);
|
|
145
|
+
} else {
|
|
146
|
+
result.type = 'pull_page';
|
|
147
|
+
result.subpath = pathParts.slice(3).join('/');
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
case 'pulls':
|
|
151
|
+
// /owner/repo/pulls - PR list
|
|
152
|
+
result.type = 'pulls_list';
|
|
153
|
+
if (pathParts.length > 3) {
|
|
154
|
+
result.subpath = pathParts.slice(3).join('/');
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
case 'actions':
|
|
158
|
+
// /owner/repo/actions - GitHub Actions
|
|
159
|
+
result.type = 'actions';
|
|
160
|
+
if (pathParts.length > 3) {
|
|
161
|
+
result.subpath = pathParts.slice(3).join('/');
|
|
162
|
+
if (pathParts[3] === 'runs' && pathParts[4] && /^\d+$/.test(pathParts[4])) {
|
|
163
|
+
result.type = 'action_run';
|
|
164
|
+
result.runId = parseInt(pathParts[4]);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
case 'releases':
|
|
169
|
+
// /owner/repo/releases
|
|
170
|
+
result.type = 'releases';
|
|
171
|
+
if (pathParts.length > 3) {
|
|
172
|
+
result.subpath = pathParts.slice(3).join('/');
|
|
173
|
+
if (pathParts[3] === 'tag' && pathParts[4]) {
|
|
174
|
+
result.type = 'release';
|
|
175
|
+
result.tag = pathParts[4];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
case 'tree':
|
|
180
|
+
case 'blob':
|
|
181
|
+
// /owner/repo/tree/branch or /owner/repo/blob/branch/file
|
|
182
|
+
result.type = thirdPart === 'tree' ? 'tree' : 'file';
|
|
183
|
+
if (pathParts.length > 3) {
|
|
184
|
+
result.branch = pathParts[3];
|
|
185
|
+
if (pathParts.length > 4) {
|
|
186
|
+
result.filepath = pathParts.slice(4).join('/');
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
case 'commit':
|
|
191
|
+
case 'commits':
|
|
192
|
+
// /owner/repo/commit/sha or /owner/repo/commits/branch
|
|
193
|
+
result.type = thirdPart === 'commit' ? 'commit' : 'commits';
|
|
194
|
+
if (pathParts.length > 3) {
|
|
195
|
+
result.ref = pathParts[3]; // Could be SHA or branch
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
case 'compare':
|
|
199
|
+
// /owner/repo/compare/base...head
|
|
200
|
+
result.type = 'compare';
|
|
201
|
+
if (pathParts.length > 3) {
|
|
202
|
+
result.comparison = pathParts[3];
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
case 'wiki':
|
|
206
|
+
// /owner/repo/wiki
|
|
207
|
+
result.type = 'wiki';
|
|
208
|
+
if (pathParts.length > 3) {
|
|
209
|
+
result.subpath = pathParts.slice(3).join('/');
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
case 'settings':
|
|
213
|
+
// /owner/repo/settings
|
|
214
|
+
result.type = 'settings';
|
|
215
|
+
if (pathParts.length > 3) {
|
|
216
|
+
result.subpath = pathParts.slice(3).join('/');
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
case 'projects':
|
|
220
|
+
// /owner/repo/projects or /owner/repo/projects/1
|
|
221
|
+
result.type = 'projects';
|
|
222
|
+
if (pathParts.length > 3 && /^\d+$/.test(pathParts[3])) {
|
|
223
|
+
result.type = 'project';
|
|
224
|
+
result.projectNumber = parseInt(pathParts[3]);
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
default:
|
|
228
|
+
// Unknown path structure but still valid GitHub URL
|
|
229
|
+
result.type = 'other';
|
|
230
|
+
result.subpath = pathParts.slice(2).join('/');
|
|
231
|
+
}
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Normalize a GitHub URL to standard https://github.com format
|
|
236
|
+
* This is a convenience function that uses parseGitHubUrl
|
|
237
|
+
* @param {string} url - The URL to normalize
|
|
238
|
+
* @returns {string|null} The normalized URL or null if invalid
|
|
239
|
+
*/
|
|
240
|
+
export function normalizeGitHubUrl(url) {
|
|
241
|
+
const parsed = parseGitHubUrl(url);
|
|
242
|
+
return parsed.valid ? parsed.normalized : null;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Check if a URL is a valid GitHub URL of a specific type
|
|
246
|
+
* @param {string} url - The URL to check
|
|
247
|
+
* @param {string|Array} types - The type(s) to check for ('issue', 'pull', 'repo', etc.)
|
|
248
|
+
* @returns {boolean} True if the URL matches the specified type(s)
|
|
249
|
+
*/
|
|
250
|
+
export function isGitHubUrlType(url, types) {
|
|
251
|
+
const parsed = parseGitHubUrl(url);
|
|
252
|
+
if (!parsed.valid) return false;
|
|
253
|
+
const typeArray = Array.isArray(types) ? types : [types];
|
|
254
|
+
return typeArray.includes(parsed.type);
|
|
255
|
+
}
|