@link-assistant/hive-mind 0.46.1 → 0.47.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 +20 -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
|
@@ -3,24 +3,7 @@
|
|
|
3
3
|
* Handles automatic creation of draft pull requests with initial commits
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export async function handleAutoPrCreation({
|
|
7
|
-
argv,
|
|
8
|
-
tempDir,
|
|
9
|
-
branchName,
|
|
10
|
-
issueNumber,
|
|
11
|
-
owner,
|
|
12
|
-
repo,
|
|
13
|
-
defaultBranch,
|
|
14
|
-
forkedRepo,
|
|
15
|
-
isContinueMode,
|
|
16
|
-
prNumber,
|
|
17
|
-
log,
|
|
18
|
-
formatAligned,
|
|
19
|
-
$,
|
|
20
|
-
reportError,
|
|
21
|
-
path,
|
|
22
|
-
fs
|
|
23
|
-
}) {
|
|
6
|
+
export async function handleAutoPrCreation({ argv, tempDir, branchName, issueNumber, owner, repo, defaultBranch, forkedRepo, isContinueMode, prNumber, log, formatAligned, $, reportError, path, fs }) {
|
|
24
7
|
// Skip auto-PR creation if:
|
|
25
8
|
// 1. Auto-PR creation is disabled AND we're not in continue mode with no PR
|
|
26
9
|
// 2. Continue mode is active AND we already have a PR
|
|
@@ -80,9 +63,13 @@ export async function handleAutoPrCreation({
|
|
|
80
63
|
const timestamp = new Date().toISOString();
|
|
81
64
|
const taskInfo = `Issue to solve: ${issueUrl}
|
|
82
65
|
Your prepared branch: ${branchName}
|
|
83
|
-
Your prepared working directory: ${tempDir}${
|
|
66
|
+
Your prepared working directory: ${tempDir}${
|
|
67
|
+
argv.fork && forkedRepo
|
|
68
|
+
? `
|
|
84
69
|
Your forked repository: ${forkedRepo}
|
|
85
|
-
Original repository (upstream): ${owner}/${repo}`
|
|
70
|
+
Original repository (upstream): ${owner}/${repo}`
|
|
71
|
+
: ''
|
|
72
|
+
}
|
|
86
73
|
|
|
87
74
|
Proceed.`;
|
|
88
75
|
|
|
@@ -158,7 +145,9 @@ Proceed.`;
|
|
|
158
145
|
|
|
159
146
|
if (gitkeepAddResult.code !== 0) {
|
|
160
147
|
await log('❌ Failed to add .gitkeep', { level: 'error' });
|
|
161
|
-
await log(` Error: ${gitkeepAddResult.stderr ? gitkeepAddResult.stderr.toString() : 'Unknown error'}`, {
|
|
148
|
+
await log(` Error: ${gitkeepAddResult.stderr ? gitkeepAddResult.stderr.toString() : 'Unknown error'}`, {
|
|
149
|
+
level: 'error',
|
|
150
|
+
});
|
|
162
151
|
throw new Error('Failed to add .gitkeep');
|
|
163
152
|
}
|
|
164
153
|
|
|
@@ -168,7 +157,9 @@ Proceed.`;
|
|
|
168
157
|
|
|
169
158
|
if (!gitStatus || gitStatus.length === 0) {
|
|
170
159
|
await log('');
|
|
171
|
-
await log(formatAligned('❌', 'GIT ADD FAILED:', 'Neither CLAUDE.md nor .gitkeep could be staged'), {
|
|
160
|
+
await log(formatAligned('❌', 'GIT ADD FAILED:', 'Neither CLAUDE.md nor .gitkeep could be staged'), {
|
|
161
|
+
level: 'error',
|
|
162
|
+
});
|
|
172
163
|
await log('');
|
|
173
164
|
await log(' 🔍 What happened:');
|
|
174
165
|
await log(' Both CLAUDE.md and .gitkeep failed to stage.');
|
|
@@ -211,14 +202,15 @@ Proceed.`;
|
|
|
211
202
|
}
|
|
212
203
|
|
|
213
204
|
await log(formatAligned('📝', 'Creating commit:', `With ${commitFileName} file`));
|
|
214
|
-
const commitMessage =
|
|
215
|
-
|
|
205
|
+
const commitMessage =
|
|
206
|
+
commitFileName === 'CLAUDE.md'
|
|
207
|
+
? `Initial commit with task details
|
|
216
208
|
|
|
217
209
|
Adding CLAUDE.md with task information for AI processing.
|
|
218
210
|
This file will be removed when the task is complete.
|
|
219
211
|
|
|
220
212
|
Issue: ${issueUrl}`
|
|
221
|
-
|
|
213
|
+
: `Initial commit with task details
|
|
222
214
|
|
|
223
215
|
Adding .gitkeep for PR creation (CLAUDE.md is in .gitignore).
|
|
224
216
|
This file will be removed when the task is complete.
|
|
@@ -332,7 +324,9 @@ Issue: ${issueUrl}`;
|
|
|
332
324
|
|
|
333
325
|
// Check for archived repository error
|
|
334
326
|
if (errorOutput.includes('archived') && errorOutput.includes('read-only')) {
|
|
335
|
-
await log(`\n${formatAligned('❌', 'REPOSITORY ARCHIVED:', 'Cannot push to archived repository')}`, {
|
|
327
|
+
await log(`\n${formatAligned('❌', 'REPOSITORY ARCHIVED:', 'Cannot push to archived repository')}`, {
|
|
328
|
+
level: 'error',
|
|
329
|
+
});
|
|
336
330
|
await log('');
|
|
337
331
|
await log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
338
332
|
await log('');
|
|
@@ -396,7 +390,7 @@ Issue: ${issueUrl}`;
|
|
|
396
390
|
context: 'fork_check',
|
|
397
391
|
owner,
|
|
398
392
|
repo,
|
|
399
|
-
operation: 'check_user_fork'
|
|
393
|
+
operation: 'check_user_fork',
|
|
400
394
|
});
|
|
401
395
|
// Ignore error - fork check is optional
|
|
402
396
|
}
|
|
@@ -408,7 +402,7 @@ Issue: ${issueUrl}`;
|
|
|
408
402
|
await log(` 🔒 You don't have write access to ${owner}/${repo}`);
|
|
409
403
|
await log('');
|
|
410
404
|
await log(' This typically happens when:');
|
|
411
|
-
await log(
|
|
405
|
+
await log(" • You're not a collaborator on the repository");
|
|
412
406
|
await log(' • The repository belongs to another user/organization');
|
|
413
407
|
await log('');
|
|
414
408
|
await log(' 📋 HOW TO FIX THIS:');
|
|
@@ -532,7 +526,9 @@ Issue: ${issueUrl}`;
|
|
|
532
526
|
} else {
|
|
533
527
|
headRef = branchName;
|
|
534
528
|
}
|
|
535
|
-
compareResult = await $({
|
|
529
|
+
compareResult = await $({
|
|
530
|
+
silent: true,
|
|
531
|
+
})`gh api repos/${owner}/${repo}/compare/${targetBranchForCompare}...${headRef} --jq '.ahead_by' 2>&1`;
|
|
536
532
|
|
|
537
533
|
if (compareResult.code === 0) {
|
|
538
534
|
const aheadBy = parseInt(compareResult.stdout.toString().trim(), 10);
|
|
@@ -569,7 +565,9 @@ Issue: ${issueUrl}`;
|
|
|
569
565
|
await log('');
|
|
570
566
|
await log(formatAligned('🔍', 'Investigating:', 'Checking fork relationship...'));
|
|
571
567
|
|
|
572
|
-
const forkInfoResult = await $({
|
|
568
|
+
const forkInfoResult = await $({
|
|
569
|
+
silent: true,
|
|
570
|
+
})`gh api repos/${forkedRepo} --jq '{fork: .fork, parent: .parent.full_name, source: .source.full_name}' 2>&1`;
|
|
573
571
|
|
|
574
572
|
let isFork = false;
|
|
575
573
|
let parentRepo = null;
|
|
@@ -597,7 +595,7 @@ Issue: ${issueUrl}`;
|
|
|
597
595
|
await log('');
|
|
598
596
|
await log(' 💡 Why this happens:');
|
|
599
597
|
await log(' This repository was likely created by cloning and pushing (git clone + git push)');
|
|
600
|
-
await log(
|
|
598
|
+
await log(" instead of using GitHub's Fork button or API.");
|
|
601
599
|
await log('');
|
|
602
600
|
await log(' When a repository is created this way:');
|
|
603
601
|
await log(' • GitHub does not track it as a fork');
|
|
@@ -627,7 +625,9 @@ Issue: ${issueUrl}`;
|
|
|
627
625
|
} else if (parentRepo !== `${owner}/${repo}` && sourceRepo !== `${owner}/${repo}`) {
|
|
628
626
|
// Repository IS a fork, but of a different repository
|
|
629
627
|
await log('');
|
|
630
|
-
await log(formatAligned('❌', 'WRONG FORK PARENT:', 'Fork is from different repository'), {
|
|
628
|
+
await log(formatAligned('❌', 'WRONG FORK PARENT:', 'Fork is from different repository'), {
|
|
629
|
+
level: 'error',
|
|
630
|
+
});
|
|
631
631
|
await log('');
|
|
632
632
|
await log(' 🔍 What happened:');
|
|
633
633
|
await log(` The repository ${forkedRepo} IS a GitHub fork,`);
|
|
@@ -666,7 +666,7 @@ Issue: ${issueUrl}`;
|
|
|
666
666
|
await log('');
|
|
667
667
|
await log(' 🔍 What happened:');
|
|
668
668
|
await log(` The repository ${forkedRepo} is a valid fork of ${owner}/${repo},`);
|
|
669
|
-
await log(
|
|
669
|
+
await log(" but GitHub's compare API still returned an error.");
|
|
670
670
|
await log('');
|
|
671
671
|
await log(' 📦 Fork verification:');
|
|
672
672
|
await log(' • Your fork: ' + forkedRepo);
|
|
@@ -692,15 +692,17 @@ Issue: ${issueUrl}`;
|
|
|
692
692
|
} else {
|
|
693
693
|
// Original timeout error for other cases
|
|
694
694
|
await log('');
|
|
695
|
-
await log(formatAligned('❌', 'GITHUB SYNC TIMEOUT:', 'Compare API not ready after retries'), {
|
|
695
|
+
await log(formatAligned('❌', 'GITHUB SYNC TIMEOUT:', 'Compare API not ready after retries'), {
|
|
696
|
+
level: 'error',
|
|
697
|
+
});
|
|
696
698
|
await log('');
|
|
697
699
|
await log(' 🔍 What happened:');
|
|
698
700
|
await log(` After ${maxCompareAttempts} attempts, GitHub's compare API still shows no commits`);
|
|
699
701
|
await log(` between ${targetBranchForCompare} and ${branchName}.`);
|
|
700
702
|
await log('');
|
|
701
703
|
await log(' 💡 This usually means:');
|
|
702
|
-
await log(
|
|
703
|
-
await log(
|
|
704
|
+
await log(" • GitHub's backend systems haven't finished indexing the push");
|
|
705
|
+
await log(" • There's a temporary issue with GitHub's API");
|
|
704
706
|
await log(' • The commits may not have been pushed correctly');
|
|
705
707
|
await log('');
|
|
706
708
|
await log(' 🔧 How to fix:');
|
|
@@ -714,7 +716,7 @@ Issue: ${issueUrl}`;
|
|
|
714
716
|
}
|
|
715
717
|
await log(' 2. Check if the branch exists on GitHub:');
|
|
716
718
|
// Show the correct repository where the branch was pushed
|
|
717
|
-
const branchRepo =
|
|
719
|
+
const branchRepo = argv.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
|
|
718
720
|
await log(` https://github.com/${branchRepo}/tree/${branchName}`);
|
|
719
721
|
await log(' 3. Check the commit is on GitHub:');
|
|
720
722
|
// Use the correct head reference for the compare API check
|
|
@@ -732,13 +734,17 @@ Issue: ${issueUrl}`;
|
|
|
732
734
|
|
|
733
735
|
// Verify the push actually worked by checking GitHub API
|
|
734
736
|
// When using fork mode, check the fork repository; otherwise check the original repository
|
|
735
|
-
const repoToCheck =
|
|
736
|
-
const branchCheckResult = await $({
|
|
737
|
+
const repoToCheck = argv.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
|
|
738
|
+
const branchCheckResult = await $({
|
|
739
|
+
silent: true,
|
|
740
|
+
})`gh api repos/${repoToCheck}/branches/${branchName} --jq .name 2>&1`;
|
|
737
741
|
if (branchCheckResult.code === 0 && branchCheckResult.stdout.toString().trim() === branchName) {
|
|
738
742
|
await log(` Branch verified on GitHub: ${branchName}`);
|
|
739
743
|
|
|
740
744
|
// Get the commit SHA from GitHub
|
|
741
|
-
const shaCheckResult = await $({
|
|
745
|
+
const shaCheckResult = await $({
|
|
746
|
+
silent: true,
|
|
747
|
+
})`gh api repos/${repoToCheck}/branches/${branchName} --jq .commit.sha 2>&1`;
|
|
742
748
|
if (shaCheckResult.code === 0) {
|
|
743
749
|
const remoteSha = shaCheckResult.stdout.toString().trim();
|
|
744
750
|
await log(` Remote commit SHA: ${remoteSha.substring(0, 7)}...`);
|
|
@@ -751,7 +757,9 @@ Issue: ${issueUrl}`;
|
|
|
751
757
|
await log(` Branch check result: ${branchCheckResult.stdout || branchCheckResult.stderr || 'empty'}`);
|
|
752
758
|
|
|
753
759
|
// Show all branches on GitHub
|
|
754
|
-
const allBranchesResult = await $({
|
|
760
|
+
const allBranchesResult = await $({
|
|
761
|
+
silent: true,
|
|
762
|
+
})`gh api repos/${repoToCheck}/branches --jq '.[].name' 2>&1`;
|
|
755
763
|
if (allBranchesResult.code === 0) {
|
|
756
764
|
await log(` All GitHub branches: ${allBranchesResult.stdout.toString().split('\n').slice(0, 5).join(', ')}...`);
|
|
757
765
|
}
|
|
@@ -780,7 +788,9 @@ Issue: ${issueUrl}`;
|
|
|
780
788
|
|
|
781
789
|
// Get issue title for PR title
|
|
782
790
|
await log(formatAligned('📋', 'Getting issue:', 'Title from GitHub...'), { verbose: true });
|
|
783
|
-
const issueTitleResult = await $({
|
|
791
|
+
const issueTitleResult = await $({
|
|
792
|
+
silent: true,
|
|
793
|
+
})`gh api repos/${owner}/${repo}/issues/${issueNumber} --jq .title 2>&1`;
|
|
784
794
|
let issueTitle = `Fix issue #${issueNumber}`;
|
|
785
795
|
if (issueTitleResult.code === 0) {
|
|
786
796
|
issueTitle = issueTitleResult.stdout.toString().trim();
|
|
@@ -807,8 +817,10 @@ Issue: ${issueUrl}`;
|
|
|
807
817
|
const { promisify } = await import('util');
|
|
808
818
|
const execAsync = promisify(exec);
|
|
809
819
|
// This will throw if user doesn't have access, but won't print anything
|
|
810
|
-
await execAsync(`gh api repos/${owner}/${repo}/collaborators/${currentUser} 2>/dev/null`,
|
|
811
|
-
|
|
820
|
+
await execAsync(`gh api repos/${owner}/${repo}/collaborators/${currentUser} 2>/dev/null`, {
|
|
821
|
+
encoding: 'utf8',
|
|
822
|
+
env: process.env,
|
|
823
|
+
});
|
|
812
824
|
canAssign = true;
|
|
813
825
|
await log(' User has collaborator access', { verbose: true });
|
|
814
826
|
} catch (e) {
|
|
@@ -817,7 +829,7 @@ Issue: ${issueUrl}`;
|
|
|
817
829
|
owner,
|
|
818
830
|
repo,
|
|
819
831
|
currentUser,
|
|
820
|
-
operation: 'check_collaborator_access'
|
|
832
|
+
operation: 'check_collaborator_access',
|
|
821
833
|
});
|
|
822
834
|
// User doesn't have permission, but that's okay - we just won't assign
|
|
823
835
|
canAssign = false;
|
|
@@ -840,12 +852,17 @@ Issue: ${issueUrl}`;
|
|
|
840
852
|
// Fetch latest state of target branch to ensure accurate comparison
|
|
841
853
|
const targetBranch = argv.baseBranch || defaultBranch;
|
|
842
854
|
await log(formatAligned('🔄', 'Fetching:', `Latest ${targetBranch} branch...`));
|
|
843
|
-
const fetchBaseResult = await $({
|
|
855
|
+
const fetchBaseResult = await $({
|
|
856
|
+
cwd: tempDir,
|
|
857
|
+
silent: true,
|
|
858
|
+
})`git fetch origin ${targetBranch}:refs/remotes/origin/${targetBranch} 2>&1`;
|
|
844
859
|
|
|
845
860
|
if (fetchBaseResult.code !== 0) {
|
|
846
861
|
await log(`⚠️ Warning: Could not fetch latest ${targetBranch}`, { level: 'warning' });
|
|
847
862
|
if (argv.verbose) {
|
|
848
|
-
await log(` Fetch output: ${fetchBaseResult.stdout || fetchBaseResult.stderr || 'none'}`, {
|
|
863
|
+
await log(` Fetch output: ${fetchBaseResult.stdout || fetchBaseResult.stderr || 'none'}`, {
|
|
864
|
+
verbose: true,
|
|
865
|
+
});
|
|
849
866
|
}
|
|
850
867
|
} else {
|
|
851
868
|
await log(formatAligned('✅', 'Base updated:', `Fetched latest ${targetBranch}`));
|
|
@@ -853,7 +870,10 @@ Issue: ${issueUrl}`;
|
|
|
853
870
|
|
|
854
871
|
// Verify there are commits between base and head before attempting PR creation
|
|
855
872
|
await log(formatAligned('🔍', 'Checking:', 'Commits between branches...'));
|
|
856
|
-
const commitCheckResult = await $({
|
|
873
|
+
const commitCheckResult = await $({
|
|
874
|
+
cwd: tempDir,
|
|
875
|
+
silent: true,
|
|
876
|
+
})`git rev-list --count origin/${targetBranch}..HEAD 2>&1`;
|
|
857
877
|
|
|
858
878
|
if (commitCheckResult.code === 0) {
|
|
859
879
|
const commitCount = parseInt(commitCheckResult.stdout.toString().trim(), 10);
|
|
@@ -863,7 +883,10 @@ Issue: ${issueUrl}`;
|
|
|
863
883
|
|
|
864
884
|
if (commitCount === 0) {
|
|
865
885
|
// Check if the branch was already merged
|
|
866
|
-
const mergedCheckResult = await $({
|
|
886
|
+
const mergedCheckResult = await $({
|
|
887
|
+
cwd: tempDir,
|
|
888
|
+
silent: true,
|
|
889
|
+
})`git branch -r --merged origin/${targetBranch} | grep -q "origin/${branchName}" 2>&1`;
|
|
867
890
|
const wasAlreadyMerged = mergedCheckResult.code === 0;
|
|
868
891
|
|
|
869
892
|
// No commits to create PR - branch is up to date with base or behind it
|
|
@@ -930,7 +953,9 @@ Issue: ${issueUrl}`;
|
|
|
930
953
|
} else {
|
|
931
954
|
await log('⚠️ Warning: Could not verify commit count', { level: 'warning' });
|
|
932
955
|
if (argv.verbose) {
|
|
933
|
-
await log(` Check output: ${commitCheckResult.stdout || commitCheckResult.stderr || 'none'}`, {
|
|
956
|
+
await log(` Check output: ${commitCheckResult.stdout || commitCheckResult.stderr || 'none'}`, {
|
|
957
|
+
verbose: true,
|
|
958
|
+
});
|
|
934
959
|
}
|
|
935
960
|
}
|
|
936
961
|
|
|
@@ -968,8 +993,11 @@ _Details will be added as the solution draft is developed..._
|
|
|
968
993
|
if (currentUser) {
|
|
969
994
|
await log(` Assignee: ${currentUser}`, { verbose: true });
|
|
970
995
|
}
|
|
971
|
-
await log(
|
|
972
|
-
|
|
996
|
+
await log(
|
|
997
|
+
` PR Body:
|
|
998
|
+
${prBody}`,
|
|
999
|
+
{ verbose: true }
|
|
1000
|
+
);
|
|
973
1001
|
}
|
|
974
1002
|
|
|
975
1003
|
// Use async exec for gh pr create to avoid command-stream output issues
|
|
@@ -1024,7 +1052,9 @@ ${prBody}`, { verbose: true });
|
|
|
1024
1052
|
// Assignee validation failed - retry without assignee
|
|
1025
1053
|
assigneeFailed = true;
|
|
1026
1054
|
await log('');
|
|
1027
|
-
await log(formatAligned('⚠️', 'Warning:', `User assignment failed for '${currentUser}'`), {
|
|
1055
|
+
await log(formatAligned('⚠️', 'Warning:', `User assignment failed for '${currentUser}'`), {
|
|
1056
|
+
level: 'warning',
|
|
1057
|
+
});
|
|
1028
1058
|
await log(' Retrying PR creation without assignee...');
|
|
1029
1059
|
|
|
1030
1060
|
// Rebuild command without --assignee flag
|
|
@@ -1049,18 +1079,18 @@ ${prBody}`, { verbose: true });
|
|
|
1049
1079
|
}
|
|
1050
1080
|
|
|
1051
1081
|
// Clean up temp files
|
|
1052
|
-
await fs.unlink(prBodyFile).catch(
|
|
1082
|
+
await fs.unlink(prBodyFile).catch(unlinkError => {
|
|
1053
1083
|
reportError(unlinkError, {
|
|
1054
1084
|
context: 'pr_body_file_cleanup',
|
|
1055
1085
|
prBodyFile,
|
|
1056
|
-
operation: 'delete_temp_file'
|
|
1086
|
+
operation: 'delete_temp_file',
|
|
1057
1087
|
});
|
|
1058
1088
|
});
|
|
1059
|
-
await fs.unlink(prTitleFile).catch(
|
|
1089
|
+
await fs.unlink(prTitleFile).catch(unlinkError => {
|
|
1060
1090
|
reportError(unlinkError, {
|
|
1061
1091
|
context: 'pr_title_file_cleanup',
|
|
1062
1092
|
prTitleFile,
|
|
1063
|
-
operation: 'delete_temp_file'
|
|
1093
|
+
operation: 'delete_temp_file',
|
|
1064
1094
|
});
|
|
1065
1095
|
});
|
|
1066
1096
|
|
|
@@ -1089,7 +1119,9 @@ ${prBody}`, { verbose: true });
|
|
|
1089
1119
|
// CRITICAL: Verify the PR was actually created by querying GitHub API
|
|
1090
1120
|
// This is essential because gh pr create can return a URL but PR creation might have failed
|
|
1091
1121
|
await log(formatAligned('🔍', 'Verifying:', 'PR creation...'), { verbose: true });
|
|
1092
|
-
const verifyResult = await $({
|
|
1122
|
+
const verifyResult = await $({
|
|
1123
|
+
silent: true,
|
|
1124
|
+
})`gh pr view ${localPrNumber} --repo ${owner}/${repo} --json number,url,state 2>&1`;
|
|
1093
1125
|
|
|
1094
1126
|
if (verifyResult.code === 0) {
|
|
1095
1127
|
try {
|
|
@@ -1197,18 +1229,28 @@ ${prBody}`, { verbose: true });
|
|
|
1197
1229
|
const linkCheckResult = await $`gh api graphql -f query='query { repository(owner: "${owner}", name: "${repo}") { pullRequest(number: ${localPrNumber}) { closingIssuesReferences(first: 10) { nodes { number } } } } }' --jq '.data.repository.pullRequest.closingIssuesReferences.nodes[].number'`;
|
|
1198
1230
|
|
|
1199
1231
|
if (linkCheckResult.code === 0) {
|
|
1200
|
-
const linkedIssues = linkCheckResult.stdout
|
|
1232
|
+
const linkedIssues = linkCheckResult.stdout
|
|
1233
|
+
.toString()
|
|
1234
|
+
.trim()
|
|
1235
|
+
.split('\n')
|
|
1236
|
+
.filter(n => n);
|
|
1201
1237
|
if (linkedIssues.includes(issueNumber)) {
|
|
1202
1238
|
await log(formatAligned('✅', 'Link verified:', `Issue #${issueNumber} → PR #${localPrNumber}`));
|
|
1203
1239
|
} else {
|
|
1204
1240
|
// This is a problem - the link wasn't created
|
|
1205
1241
|
await log('');
|
|
1206
|
-
await log(formatAligned('⚠️', 'ISSUE LINK MISSING:', 'PR not linked to issue'), {
|
|
1242
|
+
await log(formatAligned('⚠️', 'ISSUE LINK MISSING:', 'PR not linked to issue'), {
|
|
1243
|
+
level: 'warning',
|
|
1244
|
+
});
|
|
1207
1245
|
await log('');
|
|
1208
1246
|
|
|
1209
1247
|
if (argv.fork) {
|
|
1210
|
-
await log(
|
|
1211
|
-
|
|
1248
|
+
await log(" The PR was created from a fork but wasn't linked to the issue.", {
|
|
1249
|
+
level: 'warning',
|
|
1250
|
+
});
|
|
1251
|
+
await log(` Expected: "Fixes ${owner}/${repo}#${issueNumber}" in PR body`, {
|
|
1252
|
+
level: 'warning',
|
|
1253
|
+
});
|
|
1212
1254
|
await log('');
|
|
1213
1255
|
await log(' To fix manually:', { level: 'warning' });
|
|
1214
1256
|
await log(` 1. Edit the PR description at: ${prUrl}`, { level: 'warning' });
|
|
@@ -1235,7 +1277,7 @@ ${prBody}`, { verbose: true });
|
|
|
1235
1277
|
context: 'pr_issue_link_verification',
|
|
1236
1278
|
prUrl,
|
|
1237
1279
|
issueNumber,
|
|
1238
|
-
operation: 'verify_issue_link'
|
|
1280
|
+
operation: 'verify_issue_link',
|
|
1239
1281
|
});
|
|
1240
1282
|
const expectedRef = argv.fork ? `${owner}/${repo}#${issueNumber}` : `#${issueNumber}`;
|
|
1241
1283
|
await log(`⚠️ Could not verify issue linking: ${linkError.message}`, { level: 'warning' });
|
|
@@ -1256,7 +1298,7 @@ ${prBody}`, { verbose: true });
|
|
|
1256
1298
|
context: 'pr_creation',
|
|
1257
1299
|
issueNumber,
|
|
1258
1300
|
branchName,
|
|
1259
|
-
operation: 'create_pull_request'
|
|
1301
|
+
operation: 'create_pull_request',
|
|
1260
1302
|
});
|
|
1261
1303
|
const errorMsg = prCreateError.message || '';
|
|
1262
1304
|
|
|
@@ -1273,7 +1315,7 @@ ${prBody}`, { verbose: true });
|
|
|
1273
1315
|
// Check for specific error types
|
|
1274
1316
|
// Note: Assignee errors are now handled by automatic retry in the try block above
|
|
1275
1317
|
// This catch block only handles other types of PR creation failures
|
|
1276
|
-
if (errorMsg.includes('No commits between') || errorMsg.includes(
|
|
1318
|
+
if (errorMsg.includes('No commits between') || errorMsg.includes("Head sha can't be blank")) {
|
|
1277
1319
|
// Empty PR error
|
|
1278
1320
|
await log('');
|
|
1279
1321
|
await log(formatAligned('❌', 'PR CREATION FAILED', ''), { level: 'error' });
|
|
@@ -1287,8 +1329,8 @@ ${prBody}`, { verbose: true });
|
|
|
1287
1329
|
}
|
|
1288
1330
|
await log('');
|
|
1289
1331
|
await log(' 💡 Possible causes:');
|
|
1290
|
-
await log(
|
|
1291
|
-
await log(
|
|
1332
|
+
await log(" • The branch wasn't pushed properly");
|
|
1333
|
+
await log(" • The commit wasn't created");
|
|
1292
1334
|
await log(' • GitHub sync issue');
|
|
1293
1335
|
await log('');
|
|
1294
1336
|
await log(' 🔧 How to fix:');
|
|
@@ -1333,7 +1375,7 @@ ${prBody}`, { verbose: true });
|
|
|
1333
1375
|
reportError(prError, {
|
|
1334
1376
|
context: 'auto_pr_creation',
|
|
1335
1377
|
issueNumber,
|
|
1336
|
-
operation: 'handle_auto_pr'
|
|
1378
|
+
operation: 'handle_auto_pr',
|
|
1337
1379
|
});
|
|
1338
1380
|
|
|
1339
1381
|
// CRITICAL: PR creation failure should stop the entire process
|
|
@@ -1371,4 +1413,4 @@ ${prBody}`, { verbose: true });
|
|
|
1371
1413
|
}
|
|
1372
1414
|
|
|
1373
1415
|
return { prUrl, prNumber: localPrNumber, claudeCommitHash };
|
|
1374
|
-
}
|
|
1416
|
+
}
|
|
@@ -9,19 +9,7 @@
|
|
|
9
9
|
// Import Sentry integration
|
|
10
10
|
import { reportError } from './sentry.lib.mjs';
|
|
11
11
|
|
|
12
|
-
export async function handleBranchCheckoutError({
|
|
13
|
-
branchName,
|
|
14
|
-
prNumber,
|
|
15
|
-
errorOutput,
|
|
16
|
-
issueUrl,
|
|
17
|
-
owner,
|
|
18
|
-
repo,
|
|
19
|
-
tempDir,
|
|
20
|
-
argv,
|
|
21
|
-
formatAligned,
|
|
22
|
-
log,
|
|
23
|
-
$
|
|
24
|
-
}) {
|
|
12
|
+
export async function handleBranchCheckoutError({ branchName, prNumber, errorOutput, issueUrl, owner, repo, tempDir, argv, formatAligned, log, $ }) {
|
|
25
13
|
// Check if this is a PR from a fork
|
|
26
14
|
let isForkPR = false;
|
|
27
15
|
let forkOwner = null;
|
|
@@ -60,7 +48,7 @@ export async function handleBranchCheckoutError({
|
|
|
60
48
|
forkOwner,
|
|
61
49
|
forkRepoFullName,
|
|
62
50
|
branchName,
|
|
63
|
-
operation: 'verify_fork_branch'
|
|
51
|
+
operation: 'verify_fork_branch',
|
|
64
52
|
});
|
|
65
53
|
// Branch doesn't exist in fork or can't access it
|
|
66
54
|
}
|
|
@@ -71,7 +59,7 @@ export async function handleBranchCheckoutError({
|
|
|
71
59
|
context: 'handle_branch_checkout_error',
|
|
72
60
|
prNumber,
|
|
73
61
|
branchName,
|
|
74
|
-
operation: 'analyze_branch_error'
|
|
62
|
+
operation: 'analyze_branch_error',
|
|
75
63
|
});
|
|
76
64
|
// Ignore error, proceed with default message
|
|
77
65
|
}
|
|
@@ -82,7 +70,7 @@ export async function handleBranchCheckoutError({
|
|
|
82
70
|
if (userResult.code === 0) {
|
|
83
71
|
const currentUser = userResult.stdout.toString().trim();
|
|
84
72
|
// Determine fork name based on --prefix-fork-name-with-owner-name option
|
|
85
|
-
const userForkRepoName =
|
|
73
|
+
const userForkRepoName = argv && argv.prefixForkNameWithOwnerName ? `${owner}-${repo}` : repo;
|
|
86
74
|
const userForkRepo = `${currentUser}/${userForkRepoName}`;
|
|
87
75
|
const forkCheckResult = await $`gh repo view ${userForkRepo} --json parent 2>/dev/null`;
|
|
88
76
|
if (forkCheckResult.code === 0) {
|
|
@@ -108,7 +96,7 @@ export async function handleBranchCheckoutError({
|
|
|
108
96
|
userForkOwner: currentUser,
|
|
109
97
|
userForkRepoName,
|
|
110
98
|
branchName,
|
|
111
|
-
operation: 'check_branch_in_user_fork'
|
|
99
|
+
operation: 'check_branch_in_user_fork',
|
|
112
100
|
});
|
|
113
101
|
// Branch doesn't exist in user's fork
|
|
114
102
|
}
|
|
@@ -121,7 +109,7 @@ export async function handleBranchCheckoutError({
|
|
|
121
109
|
context: 'handle_branch_checkout_error',
|
|
122
110
|
prNumber,
|
|
123
111
|
branchName,
|
|
124
|
-
operation: 'analyze_branch_error'
|
|
112
|
+
operation: 'analyze_branch_error',
|
|
125
113
|
});
|
|
126
114
|
// Ignore error, proceed with default message
|
|
127
115
|
}
|
|
@@ -159,7 +147,7 @@ export async function handleBranchCheckoutError({
|
|
|
159
147
|
if (branchExistsInFork) {
|
|
160
148
|
await log(` The PR branch '${branchName}' exists in the fork repository:`);
|
|
161
149
|
await log(` https://github.com/${forkOwner}/${displayForkRepo}`);
|
|
162
|
-
await log(
|
|
150
|
+
await log(" but you're trying to access it from the main repository:");
|
|
163
151
|
await log(` https://github.com/${owner}/${repo}`);
|
|
164
152
|
await log(' This branch does NOT exist in the main repository.');
|
|
165
153
|
} else {
|
|
@@ -223,22 +211,14 @@ export async function handleBranchCheckoutError({
|
|
|
223
211
|
await log(` 2. Check remote branches: cd ${tempDir} && git branch -r`);
|
|
224
212
|
await log(` 3. Try fetching manually: cd ${tempDir} && git fetch origin`);
|
|
225
213
|
await log('');
|
|
226
|
-
await log(
|
|
214
|
+
await log(" If you don't have write access to this repository,");
|
|
227
215
|
await log(' consider using the --fork option:');
|
|
228
216
|
const altFullUrl = prNumber ? `https://github.com/${owner}/${repo}/pull/${prNumber}` : issueUrl;
|
|
229
217
|
await log(` ./solve.mjs "${altFullUrl}" --fork`);
|
|
230
218
|
}
|
|
231
219
|
}
|
|
232
220
|
|
|
233
|
-
export async function handleBranchCreationError({
|
|
234
|
-
branchName,
|
|
235
|
-
errorOutput,
|
|
236
|
-
tempDir,
|
|
237
|
-
owner,
|
|
238
|
-
repo,
|
|
239
|
-
formatAligned,
|
|
240
|
-
log
|
|
241
|
-
}) {
|
|
221
|
+
export async function handleBranchCreationError({ branchName, errorOutput, tempDir, owner, repo, formatAligned, log }) {
|
|
242
222
|
await log(`${formatAligned('❌', 'BRANCH CREATION FAILED', '')}`, { level: 'error' });
|
|
243
223
|
await log('');
|
|
244
224
|
await log(' 🔍 What happened:');
|
|
@@ -263,26 +243,17 @@ export async function handleBranchCreationError({
|
|
|
263
243
|
await log(` 3. View existing branches: cd ${tempDir} && git branch -a`);
|
|
264
244
|
}
|
|
265
245
|
|
|
266
|
-
export async function handleBranchVerificationError({
|
|
267
|
-
isContinueMode,
|
|
268
|
-
branchName,
|
|
269
|
-
actualBranch,
|
|
270
|
-
prNumber,
|
|
271
|
-
owner,
|
|
272
|
-
repo,
|
|
273
|
-
tempDir,
|
|
274
|
-
formatAligned,
|
|
275
|
-
log,
|
|
276
|
-
$
|
|
277
|
-
}) {
|
|
246
|
+
export async function handleBranchVerificationError({ isContinueMode, branchName, actualBranch, prNumber, owner, repo, tempDir, formatAligned, log, $ }) {
|
|
278
247
|
await log('');
|
|
279
|
-
await log(`${formatAligned('❌', isContinueMode ? 'BRANCH CHECKOUT FAILED' : 'BRANCH CREATION FAILED', '')}`, {
|
|
248
|
+
await log(`${formatAligned('❌', isContinueMode ? 'BRANCH CHECKOUT FAILED' : 'BRANCH CREATION FAILED', '')}`, {
|
|
249
|
+
level: 'error',
|
|
250
|
+
});
|
|
280
251
|
await log('');
|
|
281
252
|
await log(' 🔍 What happened:');
|
|
282
253
|
if (isContinueMode) {
|
|
283
|
-
await log(
|
|
254
|
+
await log(" Git checkout command didn't switch to the PR branch.");
|
|
284
255
|
} else {
|
|
285
|
-
await log(
|
|
256
|
+
await log(" Git checkout -b command didn't create or switch to the branch.");
|
|
286
257
|
}
|
|
287
258
|
if (owner && repo) {
|
|
288
259
|
await log(` Repository: https://github.com/${owner}/${repo}`);
|
|
@@ -308,7 +279,7 @@ export async function handleBranchVerificationError({
|
|
|
308
279
|
|
|
309
280
|
if (isContinueMode) {
|
|
310
281
|
await log(' 💡 This might mean:');
|
|
311
|
-
await log(
|
|
282
|
+
await log(" • PR branch doesn't exist on remote");
|
|
312
283
|
await log(' • Branch name mismatch');
|
|
313
284
|
await log(' • Network/permission issues');
|
|
314
285
|
await log('');
|
|
@@ -338,4 +309,4 @@ export async function handleBranchVerificationError({
|
|
|
338
309
|
await log('');
|
|
339
310
|
await log(` 📂 Working directory: ${tempDir}`);
|
|
340
311
|
await log('');
|
|
341
|
-
}
|
|
312
|
+
}
|