@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.
Files changed (41) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/package.json +4 -1
  3. package/src/agent-command.lib.mjs +74 -0
  4. package/src/agent.lib.mjs +59 -34
  5. package/src/agentic-cli-updater.lib.mjs +241 -0
  6. package/src/claude.connection.lib.mjs +209 -0
  7. package/src/claude.lib.mjs +6 -202
  8. package/src/codex.lib.mjs +0 -128
  9. package/src/formal-ai-isolation.lib.mjs +62 -0
  10. package/src/formal-ai-maintenance.lib.mjs +106 -0
  11. package/src/formal-ai-model.lib.mjs +25 -0
  12. package/src/formal-ai-runtime.lib.mjs +10 -0
  13. package/src/formal-ai-sidecar.lib.mjs +565 -0
  14. package/src/formal-ai-updater.lib.mjs +294 -0
  15. package/src/formal-ai-version.lib.mjs +100 -0
  16. package/src/formal-ai.lib.mjs +11 -16
  17. package/src/github-rate-limit.lib.mjs +3 -0
  18. package/src/github-url-parser.lib.mjs +255 -0
  19. package/src/github.lib.mjs +22 -343
  20. package/src/hive.mjs +0 -152
  21. package/src/interactive-mode.lib.mjs +0 -43
  22. package/src/isolation-runner.lib.mjs +44 -173
  23. package/src/limits.lib.mjs +0 -89
  24. package/src/model-args.lib.mjs +32 -0
  25. package/src/models/index.mjs +5 -19
  26. package/src/session-monitor.lib.mjs +14 -172
  27. package/src/solve.auto-merge.lib.mjs +70 -164
  28. package/src/solve.mjs +31 -193
  29. package/src/solve.repository.lib.mjs +0 -83
  30. package/src/solve.results.lib.mjs +2 -92
  31. package/src/solve.session.lib.mjs +52 -19
  32. package/src/solve.tool-uncommitted.lib.mjs +22 -0
  33. package/src/state-lock.lib.mjs +82 -0
  34. package/src/telegram-bot.mjs +17 -65
  35. package/src/telegram-fix-command.lib.mjs +1 -8
  36. package/src/telegram-merge-queue.lib.mjs +3 -155
  37. package/src/telegram-solve-queue.lib.mjs +9 -168
  38. package/src/telegram-task-command.lib.mjs +1 -8
  39. package/src/use-m-bootstrap.lib.mjs +6 -5
  40. package/src/use-with-retry.lib.mjs +128 -2
  41. 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
+ }