@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.
- package/CHANGELOG.md +10 -15
- 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
package/src/github.lib.mjs
CHANGED
|
@@ -15,10 +15,7 @@ import { log, maskToken, cleanErrorMessage } from './lib.mjs';
|
|
|
15
15
|
import { reportError } from './sentry.lib.mjs';
|
|
16
16
|
import { githubLimits, timeouts } from './config.lib.mjs';
|
|
17
17
|
// Import batch operations from separate module
|
|
18
|
-
import {
|
|
19
|
-
batchCheckPullRequestsForIssues as batchCheckPRs,
|
|
20
|
-
batchCheckArchivedRepositories as batchCheckArchived
|
|
21
|
-
} from './github.batch.lib.mjs';
|
|
18
|
+
import { batchCheckPullRequestsForIssues as batchCheckPRs, batchCheckArchivedRepositories as batchCheckArchived } from './github.batch.lib.mjs';
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
21
|
* Build cost estimation string for log comments
|
|
@@ -34,9 +31,7 @@ const buildCostInfoString = (totalCostUSD, anthropicTotalCostUSD, pricingInfo) =
|
|
|
34
31
|
if (pricingInfo.provider) costInfo += `\n- Provider: ${pricingInfo.provider}`;
|
|
35
32
|
}
|
|
36
33
|
if (totalCostUSD !== null && totalCostUSD !== undefined) {
|
|
37
|
-
costInfo += pricingInfo?.isFreeModel
|
|
38
|
-
? '\n- Public pricing estimate: $0.00 (Free model)'
|
|
39
|
-
: `\n- Public pricing estimate: $${totalCostUSD.toFixed(6)} USD`;
|
|
34
|
+
costInfo += pricingInfo?.isFreeModel ? '\n- Public pricing estimate: $0.00 (Free model)' : `\n- Public pricing estimate: $${totalCostUSD.toFixed(6)} USD`;
|
|
40
35
|
} else {
|
|
41
36
|
costInfo += '\n- Public pricing estimate: unknown';
|
|
42
37
|
}
|
|
@@ -51,7 +46,7 @@ const buildCostInfoString = (totalCostUSD, anthropicTotalCostUSD, pricingInfo) =
|
|
|
51
46
|
costInfo += `\n- Calculated by Anthropic: $${anthropicTotalCostUSD.toFixed(6)} USD`;
|
|
52
47
|
if (totalCostUSD !== null) {
|
|
53
48
|
const diff = anthropicTotalCostUSD - totalCostUSD;
|
|
54
|
-
const pct = totalCostUSD > 0 ? (
|
|
49
|
+
const pct = totalCostUSD > 0 ? (diff / totalCostUSD) * 100 : 0;
|
|
55
50
|
costInfo += `\n- Difference: $${diff.toFixed(6)} (${pct > 0 ? '+' : ''}${pct.toFixed(2)}%)`;
|
|
56
51
|
} else {
|
|
57
52
|
costInfo += '\n- Difference: unknown';
|
|
@@ -66,13 +61,18 @@ export const maskGitHubToken = maskToken;
|
|
|
66
61
|
// Helper function to get GitHub tokens from local config files
|
|
67
62
|
export const getGitHubTokensFromFiles = async () => {
|
|
68
63
|
const tokens = [];
|
|
69
|
-
|
|
64
|
+
|
|
70
65
|
try {
|
|
71
66
|
// Check ~/.config/gh/hosts.yml
|
|
72
67
|
const hostsFile = path.join(os.homedir(), '.config/gh/hosts.yml');
|
|
73
|
-
if (
|
|
68
|
+
if (
|
|
69
|
+
await fs
|
|
70
|
+
.access(hostsFile)
|
|
71
|
+
.then(() => true)
|
|
72
|
+
.catch(() => false)
|
|
73
|
+
) {
|
|
74
74
|
const hostsContent = await fs.readFile(hostsFile, 'utf8');
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
// Look for oauth_token and api_token patterns
|
|
77
77
|
const oauthMatches = hostsContent.match(/oauth_token:\s*([^\s\n]+)/g);
|
|
78
78
|
if (oauthMatches) {
|
|
@@ -83,7 +83,7 @@ export const getGitHubTokensFromFiles = async () => {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
const apiMatches = hostsContent.match(/api_token:\s*([^\s\n]+)/g);
|
|
88
88
|
if (apiMatches) {
|
|
89
89
|
for (const match of apiMatches) {
|
|
@@ -99,29 +99,26 @@ export const getGitHubTokensFromFiles = async () => {
|
|
|
99
99
|
if (global.verboseMode) {
|
|
100
100
|
reportError(error, {
|
|
101
101
|
context: 'github_token_file_access',
|
|
102
|
-
level: 'debug'
|
|
102
|
+
level: 'debug',
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
-
|
|
106
|
+
|
|
107
107
|
return tokens;
|
|
108
108
|
};
|
|
109
109
|
// Helper function to get GitHub tokens from gh command output
|
|
110
110
|
export const getGitHubTokensFromCommand = async () => {
|
|
111
111
|
const { $ } = await use('command-stream');
|
|
112
112
|
const tokens = [];
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
try {
|
|
115
115
|
// Run gh auth status to get token info
|
|
116
116
|
const authResult = await $`gh auth status 2>&1`.catch(() => ({ stdout: '', stderr: '' }));
|
|
117
117
|
const authOutput = authResult.stdout?.toString() + authResult.stderr?.toString() || '';
|
|
118
|
-
|
|
118
|
+
|
|
119
119
|
// Look for token patterns in the output
|
|
120
|
-
const tokenPatterns = [
|
|
121
|
-
|
|
122
|
-
/gh[pou]_[a-zA-Z0-9_]{20,}/gi
|
|
123
|
-
];
|
|
124
|
-
|
|
120
|
+
const tokenPatterns = [/(?:token|oauth|api)[:\s]*([a-zA-Z0-9_]{20,})/gi, /gh[pou]_[a-zA-Z0-9_]{20,}/gi];
|
|
121
|
+
|
|
125
122
|
for (const pattern of tokenPatterns) {
|
|
126
123
|
const matches = authOutput.match(pattern);
|
|
127
124
|
if (matches) {
|
|
@@ -139,25 +136,25 @@ export const getGitHubTokensFromCommand = async () => {
|
|
|
139
136
|
if (global.verboseMode) {
|
|
140
137
|
reportError(error, {
|
|
141
138
|
context: 'github_token_gh_auth',
|
|
142
|
-
level: 'debug'
|
|
139
|
+
level: 'debug',
|
|
143
140
|
});
|
|
144
141
|
}
|
|
145
142
|
}
|
|
146
|
-
|
|
143
|
+
|
|
147
144
|
return tokens;
|
|
148
145
|
};
|
|
149
146
|
// Helper function to escape code blocks in log content for safe embedding in markdown
|
|
150
147
|
// When log content is placed inside a markdown code block, any triple backticks (```)
|
|
151
148
|
// in the content will prematurely close the outer code block, breaking the markdown.
|
|
152
149
|
// This function escapes those backticks by replacing them with \`\`\` (with backslashes).
|
|
153
|
-
export const escapeCodeBlocksInLog =
|
|
150
|
+
export const escapeCodeBlocksInLog = logContent => {
|
|
154
151
|
// Replace all occurrences of triple backticks with escaped version
|
|
155
152
|
// We add backslashes before backticks to prevent them from being
|
|
156
153
|
// interpreted as markdown code block delimiters
|
|
157
154
|
return logContent.replace(/```/g, '\\`\\`\\`');
|
|
158
155
|
};
|
|
159
156
|
// Helper function to sanitize log content by masking GitHub tokens
|
|
160
|
-
export const sanitizeLogContent = async
|
|
157
|
+
export const sanitizeLogContent = async logContent => {
|
|
161
158
|
let sanitized = logContent;
|
|
162
159
|
|
|
163
160
|
try {
|
|
@@ -179,7 +176,7 @@ export const sanitizeLogContent = async (logContent) => {
|
|
|
179
176
|
const tokenPatterns = [
|
|
180
177
|
/gh[pou]_[a-zA-Z0-9_]{20,}/g,
|
|
181
178
|
/(?:^|[\s:=])([a-f0-9]{40})(?=[\s\n]|$)/gm, // 40-char hex tokens (like personal access tokens)
|
|
182
|
-
/(?:^|[\s:=])([a-zA-Z0-9_]{20,})(?=[\s\n]|$)/gm // General long tokens
|
|
179
|
+
/(?:^|[\s:=])([a-zA-Z0-9_]{20,})(?=[\s\n]|$)/gm, // General long tokens
|
|
183
180
|
];
|
|
184
181
|
|
|
185
182
|
for (const pattern of tokenPatterns) {
|
|
@@ -192,11 +189,10 @@ export const sanitizeLogContent = async (logContent) => {
|
|
|
192
189
|
}
|
|
193
190
|
|
|
194
191
|
await log(` 🔒 Sanitized ${allTokens.length} detected GitHub tokens in log content`, { verbose: true });
|
|
195
|
-
|
|
196
192
|
} catch (error) {
|
|
197
193
|
reportError(error, {
|
|
198
194
|
context: 'sanitize_log_content',
|
|
199
|
-
level: 'warning'
|
|
195
|
+
level: 'warning',
|
|
200
196
|
});
|
|
201
197
|
await log(` ⚠️ Warning: Could not fully sanitize log content: ${error.message}`, { verbose: true });
|
|
202
198
|
}
|
|
@@ -206,7 +202,7 @@ export const sanitizeLogContent = async (logContent) => {
|
|
|
206
202
|
// Helper function to check if a file exists in a GitHub branch
|
|
207
203
|
export const checkFileInBranch = async (owner, repo, fileName, branchName) => {
|
|
208
204
|
const { $ } = await use('command-stream');
|
|
209
|
-
|
|
205
|
+
|
|
210
206
|
try {
|
|
211
207
|
// Use GitHub CLI to check if file exists in the branch
|
|
212
208
|
const result = await $`gh api repos/${owner}/${repo}/contents/${fileName}?ref=${branchName}`;
|
|
@@ -220,7 +216,7 @@ export const checkFileInBranch = async (owner, repo, fileName, branchName) => {
|
|
|
220
216
|
owner,
|
|
221
217
|
repo,
|
|
222
218
|
fileName,
|
|
223
|
-
branchName
|
|
219
|
+
branchName,
|
|
224
220
|
});
|
|
225
221
|
}
|
|
226
222
|
return false;
|
|
@@ -256,14 +252,14 @@ export const checkGitHubPermissions = async () => {
|
|
|
256
252
|
warnings.push({
|
|
257
253
|
scope: 'workflow',
|
|
258
254
|
issue: 'Cannot push changes to .github/workflows/ directory',
|
|
259
|
-
solution: 'Run: gh auth refresh -h github.com -s workflow'
|
|
255
|
+
solution: 'Run: gh auth refresh -h github.com -s workflow',
|
|
260
256
|
});
|
|
261
257
|
}
|
|
262
258
|
if (!scopes.includes('repo')) {
|
|
263
259
|
warnings.push({
|
|
264
260
|
scope: 'repo',
|
|
265
261
|
issue: 'Limited repository access (may not be able to create PRs or push to private repos)',
|
|
266
|
-
solution: 'Run: gh auth refresh -h github.com -s repo'
|
|
262
|
+
solution: 'Run: gh auth refresh -h github.com -s repo',
|
|
267
263
|
});
|
|
268
264
|
}
|
|
269
265
|
// Display warnings
|
|
@@ -274,15 +270,23 @@ export const checkGitHubPermissions = async () => {
|
|
|
274
270
|
await log(` Impact: ${warning.issue}`, { level: 'warning' });
|
|
275
271
|
await log(` Solution: ${warning.solution}`, { level: 'warning' });
|
|
276
272
|
}
|
|
277
|
-
await log('\n 💡 You can continue, but some operations may fail due to insufficient permissions.', {
|
|
278
|
-
|
|
273
|
+
await log('\n 💡 You can continue, but some operations may fail due to insufficient permissions.', {
|
|
274
|
+
level: 'warning',
|
|
275
|
+
});
|
|
276
|
+
await log(" 💡 To avoid issues, it's recommended to refresh your authentication with the missing scopes.", {
|
|
277
|
+
level: 'warning',
|
|
278
|
+
});
|
|
279
279
|
} else {
|
|
280
280
|
await log('✅ All required permissions: Available');
|
|
281
281
|
}
|
|
282
282
|
return true;
|
|
283
283
|
} catch (error) {
|
|
284
|
-
await log(`⚠️ Warning: Could not check GitHub permissions: ${maskToken(error.message || error.toString())}`, {
|
|
285
|
-
|
|
284
|
+
await log(`⚠️ Warning: Could not check GitHub permissions: ${maskToken(error.message || error.toString())}`, {
|
|
285
|
+
level: 'warning',
|
|
286
|
+
});
|
|
287
|
+
await log(' Continuing anyway, but some operations may fail if permissions are insufficient', {
|
|
288
|
+
level: 'warning',
|
|
289
|
+
});
|
|
286
290
|
return true; // Continue despite permission check failure
|
|
287
291
|
}
|
|
288
292
|
};
|
|
@@ -309,8 +313,7 @@ export const checkRepositoryWritePermission = async (owner, repo, options = {})
|
|
|
309
313
|
const permResult = await $`gh api repos/${owner}/${repo} --jq .permissions`;
|
|
310
314
|
if (permResult.code !== 0) {
|
|
311
315
|
// API call failed - might be a private repo or network issue
|
|
312
|
-
const errorOutput = (permResult.stderr ? permResult.stderr.toString() : '') +
|
|
313
|
-
(permResult.stdout ? permResult.stdout.toString() : '');
|
|
316
|
+
const errorOutput = (permResult.stderr ? permResult.stderr.toString() : '') + (permResult.stdout ? permResult.stdout.toString() : '');
|
|
314
317
|
// If it's a 404, the repo doesn't exist or we don't have read access
|
|
315
318
|
if (errorOutput.includes('404') || errorOutput.includes('Not Found')) {
|
|
316
319
|
await log('❌ Repository not found or no access', { level: 'error' });
|
|
@@ -318,7 +321,9 @@ export const checkRepositoryWritePermission = async (owner, repo, options = {})
|
|
|
318
321
|
return false;
|
|
319
322
|
}
|
|
320
323
|
// For other errors, warn but continue (repo might still be accessible)
|
|
321
|
-
await log(`⚠️ Warning: Could not check repository permissions: ${cleanErrorMessage(errorOutput)}`, {
|
|
324
|
+
await log(`⚠️ Warning: Could not check repository permissions: ${cleanErrorMessage(errorOutput)}`, {
|
|
325
|
+
level: 'warning',
|
|
326
|
+
});
|
|
322
327
|
return true;
|
|
323
328
|
}
|
|
324
329
|
// Parse permissions
|
|
@@ -370,7 +375,7 @@ export const checkRepositoryWritePermission = async (owner, repo, options = {})
|
|
|
370
375
|
context: 'check_repository_write_permission',
|
|
371
376
|
owner,
|
|
372
377
|
repo,
|
|
373
|
-
operation: 'verify_write_access'
|
|
378
|
+
operation: 'verify_write_access',
|
|
374
379
|
});
|
|
375
380
|
// On unexpected errors, warn but allow to continue (better than blocking)
|
|
376
381
|
await log(`⚠️ Warning: Error checking repository permissions: ${cleanErrorMessage(error)}`, { level: 'warning' });
|
|
@@ -393,9 +398,10 @@ export const checkMaintainerCanModifyPR = async (owner, repo, prNumber) => {
|
|
|
393
398
|
// Use GitHub API to check PR details including maintainer_can_modify
|
|
394
399
|
const prResult = await $`gh api repos/${owner}/${repo}/pulls/${prNumber} --jq '{maintainer_can_modify: .maintainer_can_modify, head: .head}'`;
|
|
395
400
|
if (prResult.code !== 0) {
|
|
396
|
-
const errorOutput = (prResult.stderr ? prResult.stderr.toString() : '') +
|
|
397
|
-
|
|
398
|
-
|
|
401
|
+
const errorOutput = (prResult.stderr ? prResult.stderr.toString() : '') + (prResult.stdout ? prResult.stdout.toString() : '');
|
|
402
|
+
await log(`⚠️ Warning: Could not check maintainer_can_modify: ${cleanErrorMessage(errorOutput)}`, {
|
|
403
|
+
level: 'warning',
|
|
404
|
+
});
|
|
399
405
|
return { canModify: false, forkOwner: null, forkRepo: null };
|
|
400
406
|
}
|
|
401
407
|
// Parse PR data
|
|
@@ -409,7 +415,9 @@ export const checkMaintainerCanModifyPR = async (owner, repo, prNumber) => {
|
|
|
409
415
|
await log(` Fork: ${forkOwner}/${forkRepo}`, { verbose: true });
|
|
410
416
|
}
|
|
411
417
|
} else {
|
|
412
|
-
await log('ℹ️ Maintainer can modify: NO (contributor has not enabled "Allow edits by maintainers")', {
|
|
418
|
+
await log('ℹ️ Maintainer can modify: NO (contributor has not enabled "Allow edits by maintainers")', {
|
|
419
|
+
verbose: true,
|
|
420
|
+
});
|
|
413
421
|
}
|
|
414
422
|
return { canModify, forkOwner, forkRepo };
|
|
415
423
|
} catch (error) {
|
|
@@ -418,7 +426,7 @@ export const checkMaintainerCanModifyPR = async (owner, repo, prNumber) => {
|
|
|
418
426
|
owner,
|
|
419
427
|
repo,
|
|
420
428
|
prNumber,
|
|
421
|
-
operation: 'check_maintainer_modify_permission'
|
|
429
|
+
operation: 'check_maintainer_modify_permission',
|
|
422
430
|
});
|
|
423
431
|
await log(`⚠️ Warning: Error checking maintainer_can_modify: ${cleanErrorMessage(error)}`, { level: 'warning' });
|
|
424
432
|
return { canModify: false, forkOwner: null, forkRepo: null };
|
|
@@ -448,8 +456,7 @@ Thank you! 🙏`;
|
|
|
448
456
|
await log('✅ Comment posted successfully', { verbose: true });
|
|
449
457
|
return true;
|
|
450
458
|
} else {
|
|
451
|
-
const errorOutput = (commentResult.stderr ? commentResult.stderr.toString() : '') +
|
|
452
|
-
(commentResult.stdout ? commentResult.stdout.toString() : '');
|
|
459
|
+
const errorOutput = (commentResult.stderr ? commentResult.stderr.toString() : '') + (commentResult.stdout ? commentResult.stdout.toString() : '');
|
|
453
460
|
await log(`⚠️ Warning: Failed to post comment: ${cleanErrorMessage(errorOutput)}`, { level: 'warning' });
|
|
454
461
|
return false;
|
|
455
462
|
}
|
|
@@ -459,7 +466,7 @@ Thank you! 🙏`;
|
|
|
459
466
|
owner,
|
|
460
467
|
repo,
|
|
461
468
|
prNumber,
|
|
462
|
-
operation: 'post_comment_request_access'
|
|
469
|
+
operation: 'post_comment_request_access',
|
|
463
470
|
});
|
|
464
471
|
await log(`⚠️ Warning: Error posting comment: ${cleanErrorMessage(error)}`, { level: 'warning' });
|
|
465
472
|
return false;
|
|
@@ -508,7 +515,7 @@ export async function attachLogToGitHub(options) {
|
|
|
508
515
|
resumeCommand = null,
|
|
509
516
|
// New parameters for agent tool pricing support
|
|
510
517
|
publicPricingEstimate = null,
|
|
511
|
-
pricingInfo = null
|
|
518
|
+
pricingInfo = null,
|
|
512
519
|
} = options;
|
|
513
520
|
const targetName = targetType === 'pr' ? 'Pull Request' : 'Issue';
|
|
514
521
|
const ghCommand = targetType === 'pr' ? 'pr' : 'issue';
|
|
@@ -660,7 +667,7 @@ ${logContent}
|
|
|
660
667
|
context: 'check_repo_visibility',
|
|
661
668
|
level: 'warning',
|
|
662
669
|
owner,
|
|
663
|
-
repo
|
|
670
|
+
repo,
|
|
664
671
|
});
|
|
665
672
|
// Default to public if we can't determine visibility
|
|
666
673
|
await log(' ⚠️ Could not determine repository visibility, defaulting to public gist', { verbose: true });
|
|
@@ -670,9 +677,7 @@ ${logContent}
|
|
|
670
677
|
const tempLogFile = `/tmp/solution-draft-log-${targetType}-${Date.now()}.txt`;
|
|
671
678
|
// Use the original sanitized content (before escaping) for gist since it's a text file
|
|
672
679
|
await fs.writeFile(tempLogFile, await sanitizeLogContent(rawLogContent));
|
|
673
|
-
const gistCommand = isPublicRepo
|
|
674
|
-
? `gh gist create "${tempLogFile}" --public --desc "Solution draft log for https://github.com/${owner}/${repo}/${targetType === 'pr' ? 'pull' : 'issues'}/${targetNumber}" --filename "solution-draft-log.txt"`
|
|
675
|
-
: `gh gist create "${tempLogFile}" --desc "Solution draft log for https://github.com/${owner}/${repo}/${targetType === 'pr' ? 'pull' : 'issues'}/${targetNumber}" --filename "solution-draft-log.txt"`;
|
|
680
|
+
const gistCommand = isPublicRepo ? `gh gist create "${tempLogFile}" --public --desc "Solution draft log for https://github.com/${owner}/${repo}/${targetType === 'pr' ? 'pull' : 'issues'}/${targetNumber}" --filename "solution-draft-log.txt"` : `gh gist create "${tempLogFile}" --desc "Solution draft log for https://github.com/${owner}/${repo}/${targetType === 'pr' ? 'pull' : 'issues'}/${targetNumber}" --filename "solution-draft-log.txt"`;
|
|
676
681
|
if (verbose) {
|
|
677
682
|
await log(` 🔐 Creating ${isPublicRepo ? 'public' : 'private'} gist...`, { verbose: true });
|
|
678
683
|
}
|
|
@@ -785,7 +790,7 @@ This log file contains the complete execution trace of the AI ${targetType === '
|
|
|
785
790
|
}
|
|
786
791
|
} else {
|
|
787
792
|
await log(` ❌ Failed to create gist: ${gistResult.stderr ? gistResult.stderr.toString().trim() : 'unknown error'}`);
|
|
788
|
-
|
|
793
|
+
|
|
789
794
|
// Fallback to truncated comment
|
|
790
795
|
await log(' 🔄 Falling back to truncated comment...');
|
|
791
796
|
return await attachTruncatedLog(options);
|
|
@@ -793,7 +798,7 @@ This log file contains the complete execution trace of the AI ${targetType === '
|
|
|
793
798
|
} catch (gistError) {
|
|
794
799
|
reportError(gistError, {
|
|
795
800
|
context: 'create_gist',
|
|
796
|
-
level: 'error'
|
|
801
|
+
level: 'error',
|
|
797
802
|
});
|
|
798
803
|
await log(` ❌ Error creating gist: ${gistError.message}`);
|
|
799
804
|
// Try regular comment as last resort
|
|
@@ -827,7 +832,7 @@ async function attachTruncatedLog(options) {
|
|
|
827
832
|
const GITHUB_COMMENT_LIMIT = 65536;
|
|
828
833
|
const maxContentLength = GITHUB_COMMENT_LIMIT - 500;
|
|
829
834
|
const truncatedContent = logContent.substring(0, maxContentLength) + '\n\n[... Log truncated due to length ...]';
|
|
830
|
-
|
|
835
|
+
|
|
831
836
|
const truncatedComment = `## 🤖 Solution Draft Log (Truncated)
|
|
832
837
|
This log file contains the complete execution trace of the AI ${targetType === 'pr' ? 'solution draft' : 'analysis'} process.
|
|
833
838
|
⚠️ **Log was truncated** due to GitHub comment size limits.
|
|
@@ -841,11 +846,11 @@ ${truncatedContent}
|
|
|
841
846
|
*Now working session is ended, feel free to review and add any feedback on the solution draft.*`;
|
|
842
847
|
const tempFile = `/tmp/log-truncated-comment-${targetType}-${Date.now()}.md`;
|
|
843
848
|
await fs.writeFile(tempFile, truncatedComment);
|
|
844
|
-
|
|
849
|
+
|
|
845
850
|
const result = await $`gh ${ghCommand} comment ${targetNumber} --repo ${owner}/${repo} --body-file "${tempFile}"`;
|
|
846
|
-
|
|
851
|
+
|
|
847
852
|
await fs.unlink(tempFile).catch(() => {});
|
|
848
|
-
|
|
853
|
+
|
|
849
854
|
if (result.code === 0) {
|
|
850
855
|
await log(` ✅ Truncated solution draft log uploaded to ${targetName}`);
|
|
851
856
|
await log(` 📊 Log size: ${Math.round(logStats.size / 1024)}KB (truncated)`);
|
|
@@ -861,18 +866,18 @@ ${truncatedContent}
|
|
|
861
866
|
async function attachRegularComment(options, logComment) {
|
|
862
867
|
const fs = (await use('fs')).promises;
|
|
863
868
|
const { targetType, targetNumber, owner, repo, $, log, logFile } = options;
|
|
864
|
-
|
|
869
|
+
|
|
865
870
|
const targetName = targetType === 'pr' ? 'Pull Request' : 'Issue';
|
|
866
871
|
const ghCommand = targetType === 'pr' ? 'pr' : 'issue';
|
|
867
872
|
const logStats = await fs.stat(logFile);
|
|
868
|
-
|
|
873
|
+
|
|
869
874
|
const tempFile = `/tmp/log-comment-${targetType}-${Date.now()}.md`;
|
|
870
875
|
await fs.writeFile(tempFile, logComment);
|
|
871
|
-
|
|
876
|
+
|
|
872
877
|
const result = await $`gh ${ghCommand} comment ${targetNumber} --repo ${owner}/${repo} --body-file "${tempFile}"`;
|
|
873
|
-
|
|
878
|
+
|
|
874
879
|
await fs.unlink(tempFile).catch(() => {});
|
|
875
|
-
|
|
880
|
+
|
|
876
881
|
if (result.code === 0) {
|
|
877
882
|
await log(` ✅ Solution draft log uploaded to ${targetName} as comment`);
|
|
878
883
|
await log(` 📊 Log size: ${Math.round(logStats.size / 1024)}KB`);
|
|
@@ -890,16 +895,7 @@ async function attachRegularComment(options, logComment) {
|
|
|
890
895
|
export function isRateLimitError(error) {
|
|
891
896
|
const errorMessage = (error.message || error.toString()).toLowerCase();
|
|
892
897
|
// Common rate limit error patterns
|
|
893
|
-
const rateLimitPatterns = [
|
|
894
|
-
'rate limit',
|
|
895
|
-
'secondary rate limit',
|
|
896
|
-
'exceeded.*limit',
|
|
897
|
-
'too many requests',
|
|
898
|
-
'abuse detection',
|
|
899
|
-
'wait a few minutes',
|
|
900
|
-
'http 403.*rate',
|
|
901
|
-
'api rate limit exceeded'
|
|
902
|
-
];
|
|
898
|
+
const rateLimitPatterns = ['rate limit', 'secondary rate limit', 'exceeded.*limit', 'too many requests', 'abuse detection', 'wait a few minutes', 'http 403.*rate', 'api rate limit exceeded'];
|
|
903
899
|
return rateLimitPatterns.some(pattern => {
|
|
904
900
|
return new RegExp(pattern).test(errorMessage);
|
|
905
901
|
});
|
|
@@ -991,7 +987,7 @@ export async function fetchProjectIssues(projectNumber, owner, statusFilter) {
|
|
|
991
987
|
} catch (error) {
|
|
992
988
|
reportError(error, {
|
|
993
989
|
context: 'github.lib.mjs - GitHub CLI auth status check',
|
|
994
|
-
level: 'error'
|
|
990
|
+
level: 'error',
|
|
995
991
|
});
|
|
996
992
|
throw new Error('GitHub CLI authentication failed. Please run: gh auth login');
|
|
997
993
|
}
|
|
@@ -1000,7 +996,9 @@ export async function fetchProjectIssues(projectNumber, owner, statusFilter) {
|
|
|
1000
996
|
await new Promise(resolve => setTimeout(resolve, timeouts.githubRepoDelay));
|
|
1001
997
|
const startTime = Date.now();
|
|
1002
998
|
// Fetch all project items
|
|
1003
|
-
await log(` 🔎 Executing: gh project item-list ${projectNumber} --owner ${owner} --format json --limit 100`, {
|
|
999
|
+
await log(` 🔎 Executing: gh project item-list ${projectNumber} --owner ${owner} --format json --limit 100`, {
|
|
1000
|
+
verbose: true,
|
|
1001
|
+
});
|
|
1004
1002
|
const result = await $`gh project item-list ${projectNumber} --owner ${owner} --format json --limit 100`;
|
|
1005
1003
|
const endTime = Date.now();
|
|
1006
1004
|
const projectData = JSON.parse(result.stdout || '{"items": []}');
|
|
@@ -1028,7 +1026,7 @@ export async function fetchProjectIssues(projectNumber, owner, statusFilter) {
|
|
|
1028
1026
|
number: item.content.number,
|
|
1029
1027
|
repository: item.content.repository,
|
|
1030
1028
|
labels: item.content.labels || [],
|
|
1031
|
-
state: item.content.state || 'open'
|
|
1029
|
+
state: item.content.state || 'open',
|
|
1032
1030
|
}));
|
|
1033
1031
|
await log(` ✅ Found ${issues.length} issues with status "${statusFilter}"`);
|
|
1034
1032
|
if (issues.length > 0) {
|
|
@@ -1074,7 +1072,7 @@ export function parseGitHubUrl(url) {
|
|
|
1074
1072
|
if (!url || typeof url !== 'string') {
|
|
1075
1073
|
return {
|
|
1076
1074
|
valid: false,
|
|
1077
|
-
error: 'Invalid input: URL must be a non-empty string'
|
|
1075
|
+
error: 'Invalid input: URL must be a non-empty string',
|
|
1078
1076
|
};
|
|
1079
1077
|
}
|
|
1080
1078
|
// Trim whitespace and remove trailing slashes
|
|
@@ -1084,7 +1082,7 @@ export function parseGitHubUrl(url) {
|
|
|
1084
1082
|
if (/\s/.test(normalizedUrl) || /^[!@#$%^&*()[\]{}|\\:;"'<>,?`~]/.test(normalizedUrl)) {
|
|
1085
1083
|
return {
|
|
1086
1084
|
valid: false,
|
|
1087
|
-
error: 'Invalid GitHub URL format'
|
|
1085
|
+
error: 'Invalid GitHub URL format',
|
|
1088
1086
|
};
|
|
1089
1087
|
}
|
|
1090
1088
|
// Handle protocol normalization
|
|
@@ -1099,7 +1097,7 @@ export function parseGitHubUrl(url) {
|
|
|
1099
1097
|
// Has github.com somewhere but not at the start - likely malformed
|
|
1100
1098
|
return {
|
|
1101
1099
|
valid: false,
|
|
1102
|
-
error: 'Invalid GitHub URL format'
|
|
1100
|
+
error: 'Invalid GitHub URL format',
|
|
1103
1101
|
};
|
|
1104
1102
|
}
|
|
1105
1103
|
}
|
|
@@ -1119,7 +1117,7 @@ export function parseGitHubUrl(url) {
|
|
|
1119
1117
|
return {
|
|
1120
1118
|
valid: false,
|
|
1121
1119
|
error: 'Invalid character in URL: backslash (\\) is not allowed in URL paths',
|
|
1122
|
-
suggestion: suggestedUrl + urlAfterPath
|
|
1120
|
+
suggestion: suggestedUrl + urlAfterPath,
|
|
1123
1121
|
};
|
|
1124
1122
|
}
|
|
1125
1123
|
|
|
@@ -1132,19 +1130,19 @@ export function parseGitHubUrl(url) {
|
|
|
1132
1130
|
reportError(e, {
|
|
1133
1131
|
context: 'github.lib.mjs - URL parsing',
|
|
1134
1132
|
level: 'debug',
|
|
1135
|
-
url: normalizedUrl
|
|
1133
|
+
url: normalizedUrl,
|
|
1136
1134
|
});
|
|
1137
1135
|
}
|
|
1138
1136
|
return {
|
|
1139
1137
|
valid: false,
|
|
1140
|
-
error: 'Invalid URL format'
|
|
1138
|
+
error: 'Invalid URL format',
|
|
1141
1139
|
};
|
|
1142
1140
|
}
|
|
1143
1141
|
// Ensure it's a GitHub URL
|
|
1144
1142
|
if (urlObj.hostname !== 'github.com' && urlObj.hostname !== 'www.github.com') {
|
|
1145
1143
|
return {
|
|
1146
1144
|
valid: false,
|
|
1147
|
-
error: 'Not a GitHub URL'
|
|
1145
|
+
error: 'Not a GitHub URL',
|
|
1148
1146
|
};
|
|
1149
1147
|
}
|
|
1150
1148
|
// Normalize hostname
|
|
@@ -1160,7 +1158,7 @@ export function parseGitHubUrl(url) {
|
|
|
1160
1158
|
normalized: normalizedUrl,
|
|
1161
1159
|
hostname: 'github.com',
|
|
1162
1160
|
protocol: 'https',
|
|
1163
|
-
path: urlObj.pathname
|
|
1161
|
+
path: urlObj.pathname,
|
|
1164
1162
|
};
|
|
1165
1163
|
// No path - just github.com
|
|
1166
1164
|
if (pathParts.length === 0) {
|
|
@@ -1343,7 +1341,7 @@ export async function ghPrView({ prNumber, owner, repo, jsonFields = 'headRefNam
|
|
|
1343
1341
|
stdout,
|
|
1344
1342
|
stderr,
|
|
1345
1343
|
data,
|
|
1346
|
-
output: stdout + stderr
|
|
1344
|
+
output: stdout + stderr,
|
|
1347
1345
|
};
|
|
1348
1346
|
} catch (error) {
|
|
1349
1347
|
return {
|
|
@@ -1351,7 +1349,7 @@ export async function ghPrView({ prNumber, owner, repo, jsonFields = 'headRefNam
|
|
|
1351
1349
|
stdout: error.stdout?.toString() || '',
|
|
1352
1350
|
stderr: error.stderr?.toString() || error.message || '',
|
|
1353
1351
|
data: null,
|
|
1354
|
-
output: (error.stdout?.toString() || '') + (error.stderr?.toString() || error.message || '')
|
|
1352
|
+
output: (error.stdout?.toString() || '') + (error.stderr?.toString() || error.message || ''),
|
|
1355
1353
|
};
|
|
1356
1354
|
}
|
|
1357
1355
|
}
|
|
@@ -1383,7 +1381,7 @@ export async function ghIssueView({ issueNumber, owner, repo, jsonFields = 'numb
|
|
|
1383
1381
|
stdout,
|
|
1384
1382
|
stderr,
|
|
1385
1383
|
data,
|
|
1386
|
-
output: stdout + stderr
|
|
1384
|
+
output: stdout + stderr,
|
|
1387
1385
|
};
|
|
1388
1386
|
} catch (error) {
|
|
1389
1387
|
return {
|
|
@@ -1391,7 +1389,7 @@ export async function ghIssueView({ issueNumber, owner, repo, jsonFields = 'numb
|
|
|
1391
1389
|
stdout: error.stdout?.toString() || '',
|
|
1392
1390
|
stderr: error.stderr?.toString() || error.message || '',
|
|
1393
1391
|
data: null,
|
|
1394
|
-
output: (error.stdout?.toString() || '') + (error.stderr?.toString() || error.message || '')
|
|
1392
|
+
output: (error.stdout?.toString() || '') + (error.stderr?.toString() || error.message || ''),
|
|
1395
1393
|
};
|
|
1396
1394
|
}
|
|
1397
1395
|
}
|
|
@@ -1458,7 +1456,7 @@ export async function detectRepositoryVisibility(owner, repo) {
|
|
|
1458
1456
|
context: 'detect_repository_visibility',
|
|
1459
1457
|
owner,
|
|
1460
1458
|
repo,
|
|
1461
|
-
operation: 'get_repo_visibility'
|
|
1459
|
+
operation: 'get_repo_visibility',
|
|
1462
1460
|
});
|
|
1463
1461
|
// Default to public (safer to keep temp directories on error)
|
|
1464
1462
|
if (global.verboseMode) {
|
|
@@ -1491,5 +1489,5 @@ export default {
|
|
|
1491
1489
|
ghIssueView,
|
|
1492
1490
|
handlePRNotFoundError,
|
|
1493
1491
|
detectRepositoryVisibility,
|
|
1494
|
-
batchCheckArchivedRepositories
|
|
1492
|
+
batchCheckArchivedRepositories,
|
|
1495
1493
|
};
|