@link-assistant/hive-mind 0.39.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 +20 -0
- package/LICENSE +24 -0
- package/README.md +769 -0
- package/package.json +58 -0
- package/src/agent.lib.mjs +705 -0
- package/src/agent.prompts.lib.mjs +196 -0
- package/src/buildUserMention.lib.mjs +71 -0
- package/src/claude-limits.lib.mjs +389 -0
- package/src/claude.lib.mjs +1445 -0
- package/src/claude.prompts.lib.mjs +203 -0
- package/src/codex.lib.mjs +552 -0
- package/src/codex.prompts.lib.mjs +194 -0
- package/src/config.lib.mjs +207 -0
- package/src/contributing-guidelines.lib.mjs +268 -0
- package/src/exit-handler.lib.mjs +205 -0
- package/src/git.lib.mjs +145 -0
- package/src/github-issue-creator.lib.mjs +246 -0
- package/src/github-linking.lib.mjs +152 -0
- package/src/github.batch.lib.mjs +272 -0
- package/src/github.graphql.lib.mjs +258 -0
- package/src/github.lib.mjs +1479 -0
- package/src/hive.config.lib.mjs +254 -0
- package/src/hive.mjs +1500 -0
- package/src/instrument.mjs +191 -0
- package/src/interactive-mode.lib.mjs +1000 -0
- package/src/lenv-reader.lib.mjs +206 -0
- package/src/lib.mjs +490 -0
- package/src/lino.lib.mjs +176 -0
- package/src/local-ci-checks.lib.mjs +324 -0
- package/src/memory-check.mjs +419 -0
- package/src/model-mapping.lib.mjs +145 -0
- package/src/model-validation.lib.mjs +278 -0
- package/src/opencode.lib.mjs +479 -0
- package/src/opencode.prompts.lib.mjs +194 -0
- package/src/protect-branch.mjs +159 -0
- package/src/review.mjs +433 -0
- package/src/reviewers-hive.mjs +643 -0
- package/src/sentry.lib.mjs +284 -0
- package/src/solve.auto-continue.lib.mjs +568 -0
- package/src/solve.auto-pr.lib.mjs +1374 -0
- package/src/solve.branch-errors.lib.mjs +341 -0
- package/src/solve.branch.lib.mjs +230 -0
- package/src/solve.config.lib.mjs +342 -0
- package/src/solve.error-handlers.lib.mjs +256 -0
- package/src/solve.execution.lib.mjs +291 -0
- package/src/solve.feedback.lib.mjs +436 -0
- package/src/solve.mjs +1128 -0
- package/src/solve.preparation.lib.mjs +210 -0
- package/src/solve.repo-setup.lib.mjs +114 -0
- package/src/solve.repository.lib.mjs +961 -0
- package/src/solve.results.lib.mjs +558 -0
- package/src/solve.session.lib.mjs +135 -0
- package/src/solve.validation.lib.mjs +325 -0
- package/src/solve.watch.lib.mjs +572 -0
- package/src/start-screen.mjs +324 -0
- package/src/task.mjs +308 -0
- package/src/telegram-bot.mjs +1481 -0
- package/src/telegram-markdown.lib.mjs +64 -0
- package/src/usage-limit.lib.mjs +218 -0
- package/src/version.lib.mjs +41 -0
- package/src/youtrack/solve.youtrack.lib.mjs +116 -0
- package/src/youtrack/youtrack-sync.mjs +219 -0
- package/src/youtrack/youtrack.lib.mjs +425 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Branch error handling module for solve.mjs
|
|
6
|
+
* Provides improved error messages for branch checkout/creation failures
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Import Sentry integration
|
|
10
|
+
import { reportError } from './sentry.lib.mjs';
|
|
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
|
+
}) {
|
|
25
|
+
// Check if this is a PR from a fork
|
|
26
|
+
let isForkPR = false;
|
|
27
|
+
let forkOwner = null;
|
|
28
|
+
let forkRepoName = null; // Track the actual fork repo name (may be prefixed)
|
|
29
|
+
let userHasFork = false;
|
|
30
|
+
let suggestForkOption = false;
|
|
31
|
+
let branchExistsInFork = false;
|
|
32
|
+
|
|
33
|
+
if (prNumber) {
|
|
34
|
+
try {
|
|
35
|
+
const prCheckResult = await $`gh pr view ${prNumber} --repo ${owner}/${repo} --json headRepositoryOwner,headRefName,headRepository 2>/dev/null`;
|
|
36
|
+
if (prCheckResult.code === 0) {
|
|
37
|
+
const prData = JSON.parse(prCheckResult.stdout.toString());
|
|
38
|
+
if (prData.headRepositoryOwner && prData.headRepositoryOwner.login !== owner) {
|
|
39
|
+
isForkPR = true;
|
|
40
|
+
forkOwner = prData.headRepositoryOwner.login;
|
|
41
|
+
suggestForkOption = true;
|
|
42
|
+
|
|
43
|
+
// Get the actual fork repository name (might be prefixed)
|
|
44
|
+
let forkRepoFullName = `${forkOwner}/${repo}`;
|
|
45
|
+
if (prData.headRepository && prData.headRepository.name) {
|
|
46
|
+
forkRepoFullName = `${forkOwner}/${prData.headRepository.name}`;
|
|
47
|
+
forkRepoName = prData.headRepository.name;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check if the branch exists in the fork
|
|
51
|
+
try {
|
|
52
|
+
const branchCheckResult = await $`gh api repos/${forkRepoFullName}/git/ref/heads/${branchName} 2>/dev/null`;
|
|
53
|
+
if (branchCheckResult.code === 0) {
|
|
54
|
+
branchExistsInFork = true;
|
|
55
|
+
}
|
|
56
|
+
} catch (e) {
|
|
57
|
+
reportError(e, {
|
|
58
|
+
context: 'check_fork_for_branch',
|
|
59
|
+
prNumber,
|
|
60
|
+
forkOwner,
|
|
61
|
+
forkRepoFullName,
|
|
62
|
+
branchName,
|
|
63
|
+
operation: 'verify_fork_branch'
|
|
64
|
+
});
|
|
65
|
+
// Branch doesn't exist in fork or can't access it
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} catch (e) {
|
|
70
|
+
reportError(e, {
|
|
71
|
+
context: 'handle_branch_checkout_error',
|
|
72
|
+
prNumber,
|
|
73
|
+
branchName,
|
|
74
|
+
operation: 'analyze_branch_error'
|
|
75
|
+
});
|
|
76
|
+
// Ignore error, proceed with default message
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check if the current user has a fork of this repository
|
|
80
|
+
try {
|
|
81
|
+
const userResult = await $`gh api user --jq .login`;
|
|
82
|
+
if (userResult.code === 0) {
|
|
83
|
+
const currentUser = userResult.stdout.toString().trim();
|
|
84
|
+
// Determine fork name based on --prefix-fork-name-with-owner-name option
|
|
85
|
+
const userForkRepoName = (argv && argv.prefixForkNameWithOwnerName) ? `${owner}-${repo}` : repo;
|
|
86
|
+
const userForkRepo = `${currentUser}/${userForkRepoName}`;
|
|
87
|
+
const forkCheckResult = await $`gh repo view ${userForkRepo} --json parent 2>/dev/null`;
|
|
88
|
+
if (forkCheckResult.code === 0) {
|
|
89
|
+
const forkData = JSON.parse(forkCheckResult.stdout.toString());
|
|
90
|
+
if (forkData.parent && forkData.parent.owner && forkData.parent.owner.login === owner) {
|
|
91
|
+
userHasFork = true;
|
|
92
|
+
if (!forkOwner) forkOwner = currentUser;
|
|
93
|
+
if (!forkRepoName) forkRepoName = userForkRepoName;
|
|
94
|
+
suggestForkOption = true;
|
|
95
|
+
|
|
96
|
+
// Check if the branch exists in user's fork
|
|
97
|
+
if (!branchExistsInFork) {
|
|
98
|
+
try {
|
|
99
|
+
const branchCheckResult = await $`gh api repos/${userForkRepo}/git/ref/heads/${branchName} 2>/dev/null`;
|
|
100
|
+
if (branchCheckResult.code === 0) {
|
|
101
|
+
branchExistsInFork = true;
|
|
102
|
+
forkOwner = currentUser;
|
|
103
|
+
forkRepoName = userForkRepoName;
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
reportError(e, {
|
|
107
|
+
context: 'check_user_fork_branch',
|
|
108
|
+
userForkOwner: currentUser,
|
|
109
|
+
userForkRepoName,
|
|
110
|
+
branchName,
|
|
111
|
+
operation: 'check_branch_in_user_fork'
|
|
112
|
+
});
|
|
113
|
+
// Branch doesn't exist in user's fork
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} catch (e) {
|
|
120
|
+
reportError(e, {
|
|
121
|
+
context: 'handle_branch_checkout_error',
|
|
122
|
+
prNumber,
|
|
123
|
+
branchName,
|
|
124
|
+
operation: 'analyze_branch_error'
|
|
125
|
+
});
|
|
126
|
+
// Ignore error, proceed with default message
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await log(`${formatAligned('❌', 'BRANCH CHECKOUT FAILED', '')}`, { level: 'error' });
|
|
131
|
+
await log('');
|
|
132
|
+
|
|
133
|
+
// Provide a clearer explanation of what happened
|
|
134
|
+
await log(' 🔍 What happened:');
|
|
135
|
+
await log(` Failed to checkout the branch '${branchName}' for PR #${prNumber}.`);
|
|
136
|
+
await log(` Repository: https://github.com/${owner}/${repo}`);
|
|
137
|
+
await log(` Pull Request: https://github.com/${owner}/${repo}/pull/${prNumber}`);
|
|
138
|
+
if (errorOutput.includes('is not a commit')) {
|
|
139
|
+
await log(` The branch doesn't exist in the main repository (https://github.com/${owner}/${repo}).`);
|
|
140
|
+
} else {
|
|
141
|
+
await log(' Git was unable to find or access the branch.');
|
|
142
|
+
}
|
|
143
|
+
await log('');
|
|
144
|
+
|
|
145
|
+
// Only show git output if it's not the typical "not a commit" error
|
|
146
|
+
if (!errorOutput.includes('is not a commit') || argv.verbose) {
|
|
147
|
+
await log(' 📦 Git error details:');
|
|
148
|
+
for (const line of errorOutput.split('\n')) {
|
|
149
|
+
await log(` ${line}`);
|
|
150
|
+
}
|
|
151
|
+
await log('');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Explain why this happened
|
|
155
|
+
await log(' 💡 Why this happened:');
|
|
156
|
+
if (isForkPR && forkOwner) {
|
|
157
|
+
// Use forkRepoName if available, otherwise default to repo
|
|
158
|
+
const displayForkRepo = forkRepoName || repo;
|
|
159
|
+
if (branchExistsInFork) {
|
|
160
|
+
await log(` The PR branch '${branchName}' exists in the fork repository:`);
|
|
161
|
+
await log(` https://github.com/${forkOwner}/${displayForkRepo}`);
|
|
162
|
+
await log(' but you\'re trying to access it from the main repository:');
|
|
163
|
+
await log(` https://github.com/${owner}/${repo}`);
|
|
164
|
+
await log(' This branch does NOT exist in the main repository.');
|
|
165
|
+
} else {
|
|
166
|
+
await log(` The PR is from a fork (https://github.com/${forkOwner}/${displayForkRepo})`);
|
|
167
|
+
await log(` but the branch '${branchName}' could not be found there either.`);
|
|
168
|
+
await log(' The branch may have been deleted or renamed.');
|
|
169
|
+
}
|
|
170
|
+
await log(' This is a common issue with pull requests from forks.');
|
|
171
|
+
} else if (userHasFork && branchExistsInFork) {
|
|
172
|
+
// Use forkRepoName if available, otherwise default to repo
|
|
173
|
+
const displayForkRepo = forkRepoName || repo;
|
|
174
|
+
await log(` The branch '${branchName}' exists in your fork:`);
|
|
175
|
+
await log(` https://github.com/${forkOwner}/${displayForkRepo}`);
|
|
176
|
+
await log(' but NOT in the main repository:');
|
|
177
|
+
await log(` https://github.com/${owner}/${repo}`);
|
|
178
|
+
await log(' You need to use --fork to work with your fork.');
|
|
179
|
+
} else if (userHasFork) {
|
|
180
|
+
await log(' You have a fork of this repository, but the PR branch');
|
|
181
|
+
await log(` '${branchName}' doesn't exist in either the main repository`);
|
|
182
|
+
await log(' or your fork. It may have been deleted or renamed.');
|
|
183
|
+
} else {
|
|
184
|
+
await log(` • The branch '${branchName}' doesn't exist in https://github.com/${owner}/${repo}`);
|
|
185
|
+
await log(' • This might be a PR from a fork (use --fork option)');
|
|
186
|
+
await log(' • Or the branch may have been deleted/renamed');
|
|
187
|
+
}
|
|
188
|
+
await log('');
|
|
189
|
+
|
|
190
|
+
// Provide clear solutions
|
|
191
|
+
await log(' 🔧 How to fix this:');
|
|
192
|
+
|
|
193
|
+
if (isForkPR || suggestForkOption) {
|
|
194
|
+
await log('');
|
|
195
|
+
await log(' ┌──────────────────────────────────────────────────────────┐');
|
|
196
|
+
await log(' │ RECOMMENDED: Use the --fork option │');
|
|
197
|
+
await log(' └──────────────────────────────────────────────────────────┘');
|
|
198
|
+
await log('');
|
|
199
|
+
await log(' Run this command:');
|
|
200
|
+
const fullUrl = prNumber ? `https://github.com/${owner}/${repo}/pull/${prNumber}` : issueUrl;
|
|
201
|
+
await log(` ./solve.mjs "${fullUrl}" --fork`);
|
|
202
|
+
await log('');
|
|
203
|
+
await log(' This will automatically:');
|
|
204
|
+
if (userHasFork) {
|
|
205
|
+
// Use forkRepoName if available, otherwise default to repo
|
|
206
|
+
const displayForkRepo = forkRepoName || repo;
|
|
207
|
+
await log(` ✓ Use your existing fork (${forkOwner}/${displayForkRepo})`);
|
|
208
|
+
} else if (isForkPR && forkOwner) {
|
|
209
|
+
await log(' ✓ Work with the fork that contains the PR branch');
|
|
210
|
+
} else {
|
|
211
|
+
await log(' ✓ Create or use a fork of the repository');
|
|
212
|
+
}
|
|
213
|
+
await log(' ✓ Set up the correct remotes and branches');
|
|
214
|
+
await log(' ✓ Allow you to work on the PR without permission issues');
|
|
215
|
+
await log('');
|
|
216
|
+
await log(' ─────────────────────────────────────────────────────────');
|
|
217
|
+
await log('');
|
|
218
|
+
await log(' Alternative options:');
|
|
219
|
+
await log(` • Verify PR details: gh pr view ${prNumber} --repo ${owner}/${repo}`);
|
|
220
|
+
await log(` • Check your local setup: cd ${tempDir} && git remote -v`);
|
|
221
|
+
} else {
|
|
222
|
+
await log(` 1. Verify PR branch exists: gh pr view ${prNumber} --repo ${owner}/${repo}`);
|
|
223
|
+
await log(` 2. Check remote branches: cd ${tempDir} && git branch -r`);
|
|
224
|
+
await log(` 3. Try fetching manually: cd ${tempDir} && git fetch origin`);
|
|
225
|
+
await log('');
|
|
226
|
+
await log(' If you don\'t have write access to this repository,');
|
|
227
|
+
await log(' consider using the --fork option:');
|
|
228
|
+
const altFullUrl = prNumber ? `https://github.com/${owner}/${repo}/pull/${prNumber}` : issueUrl;
|
|
229
|
+
await log(` ./solve.mjs "${altFullUrl}" --fork`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function handleBranchCreationError({
|
|
234
|
+
branchName,
|
|
235
|
+
errorOutput,
|
|
236
|
+
tempDir,
|
|
237
|
+
owner,
|
|
238
|
+
repo,
|
|
239
|
+
formatAligned,
|
|
240
|
+
log
|
|
241
|
+
}) {
|
|
242
|
+
await log(`${formatAligned('❌', 'BRANCH CREATION FAILED', '')}`, { level: 'error' });
|
|
243
|
+
await log('');
|
|
244
|
+
await log(' 🔍 What happened:');
|
|
245
|
+
await log(` Unable to create branch '${branchName}'.`);
|
|
246
|
+
if (owner && repo) {
|
|
247
|
+
await log(` Repository: https://github.com/${owner}/${repo}`);
|
|
248
|
+
}
|
|
249
|
+
await log('');
|
|
250
|
+
await log(' 📦 Git output:');
|
|
251
|
+
for (const line of errorOutput.split('\n')) {
|
|
252
|
+
await log(` ${line}`);
|
|
253
|
+
}
|
|
254
|
+
await log('');
|
|
255
|
+
await log(' 💡 Possible causes:');
|
|
256
|
+
await log(' • Branch name already exists');
|
|
257
|
+
await log(' • Uncommitted changes in repository');
|
|
258
|
+
await log(' • Git configuration issues');
|
|
259
|
+
await log('');
|
|
260
|
+
await log(' 🔧 How to fix:');
|
|
261
|
+
await log(' 1. Try running the command again (uses random names)');
|
|
262
|
+
await log(` 2. Check git status: cd ${tempDir} && git status`);
|
|
263
|
+
await log(` 3. View existing branches: cd ${tempDir} && git branch -a`);
|
|
264
|
+
}
|
|
265
|
+
|
|
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
|
+
}) {
|
|
278
|
+
await log('');
|
|
279
|
+
await log(`${formatAligned('❌', isContinueMode ? 'BRANCH CHECKOUT FAILED' : 'BRANCH CREATION FAILED', '')}`, { level: 'error' });
|
|
280
|
+
await log('');
|
|
281
|
+
await log(' 🔍 What happened:');
|
|
282
|
+
if (isContinueMode) {
|
|
283
|
+
await log(' Git checkout command didn\'t switch to the PR branch.');
|
|
284
|
+
} else {
|
|
285
|
+
await log(' Git checkout -b command didn\'t create or switch to the branch.');
|
|
286
|
+
}
|
|
287
|
+
if (owner && repo) {
|
|
288
|
+
await log(` Repository: https://github.com/${owner}/${repo}`);
|
|
289
|
+
if (prNumber) {
|
|
290
|
+
await log(` Pull Request: https://github.com/${owner}/${repo}/pull/${prNumber}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
await log('');
|
|
294
|
+
await log(' 📊 Branch status:');
|
|
295
|
+
await log(` Expected branch: ${branchName}`);
|
|
296
|
+
await log(` Currently on: ${actualBranch || '(unknown)'}`);
|
|
297
|
+
await log('');
|
|
298
|
+
|
|
299
|
+
// Show all branches to help debug
|
|
300
|
+
const allBranchesResult = await $({ cwd: tempDir })`git branch -a 2>&1`;
|
|
301
|
+
if (allBranchesResult.code === 0) {
|
|
302
|
+
await log(' 🌿 Available branches:');
|
|
303
|
+
for (const line of allBranchesResult.stdout.toString().split('\n')) {
|
|
304
|
+
if (line.trim()) await log(` ${line}`);
|
|
305
|
+
}
|
|
306
|
+
await log('');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (isContinueMode) {
|
|
310
|
+
await log(' 💡 This might mean:');
|
|
311
|
+
await log(' • PR branch doesn\'t exist on remote');
|
|
312
|
+
await log(' • Branch name mismatch');
|
|
313
|
+
await log(' • Network/permission issues');
|
|
314
|
+
await log('');
|
|
315
|
+
await log(' 🔧 How to fix:');
|
|
316
|
+
await log(` 1. Check PR details: gh pr view ${prNumber} --repo ${owner}/${repo}`);
|
|
317
|
+
await log(` 2. List remote branches: cd ${tempDir} && git branch -r`);
|
|
318
|
+
await log(` 3. Try manual checkout: cd ${tempDir} && git checkout ${branchName}`);
|
|
319
|
+
} else {
|
|
320
|
+
await log(' 💡 This is unusual. Possible causes:');
|
|
321
|
+
await log(' • Git version incompatibility');
|
|
322
|
+
await log(' • File system permissions issue');
|
|
323
|
+
await log(' • Repository corruption');
|
|
324
|
+
await log('');
|
|
325
|
+
await log(' 🔧 How to fix:');
|
|
326
|
+
await log(' 1. Try creating the branch manually:');
|
|
327
|
+
await log(` cd ${tempDir}`);
|
|
328
|
+
await log(` git checkout -b ${branchName}`);
|
|
329
|
+
await log(' ');
|
|
330
|
+
await log(' 2. If that fails, try two-step approach:');
|
|
331
|
+
await log(` cd ${tempDir}`);
|
|
332
|
+
await log(` git branch ${branchName}`);
|
|
333
|
+
await log(` git checkout ${branchName}`);
|
|
334
|
+
await log(' ');
|
|
335
|
+
await log(' 3. Check your git version:');
|
|
336
|
+
await log(' git --version');
|
|
337
|
+
}
|
|
338
|
+
await log('');
|
|
339
|
+
await log(` 📂 Working directory: ${tempDir}`);
|
|
340
|
+
await log('');
|
|
341
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branch creation and checkout functionality for solve.mjs
|
|
3
|
+
* Handles creating new branches or checking out existing PR branches
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Regular expressions for branch name validation
|
|
8
|
+
* Supports both legacy (8-char) and new (12-char) formats
|
|
9
|
+
*/
|
|
10
|
+
const branchNameRegex = {
|
|
11
|
+
// Legacy format: issue-{number}-{8-hex-chars}
|
|
12
|
+
legacy: /^issue-(\d+)-([a-f0-9]{8})$/,
|
|
13
|
+
// New format: issue-{number}-{12-hex-chars}
|
|
14
|
+
new: /^issue-(\d+)-([a-f0-9]{12})$/,
|
|
15
|
+
// Combined pattern for both formats
|
|
16
|
+
any: /^issue-(\d+)-([a-f0-9]{8}|[a-f0-9]{12})$/,
|
|
17
|
+
// Pattern for prefix matching: issue-{number}-
|
|
18
|
+
prefix: (issueNumber) => new RegExp(`^issue-${issueNumber}-([a-f0-9]{8}|[a-f0-9]{12})$`)
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validates if a branch name matches the expected pattern for issue branches
|
|
23
|
+
* @param {string} branchName - The branch name to validate
|
|
24
|
+
* @param {number|string} [issueNumber] - Optional issue number to validate against
|
|
25
|
+
* @returns {boolean} True if branch name is valid
|
|
26
|
+
*/
|
|
27
|
+
export function isValidIssueBranchName(branchName, issueNumber = null) {
|
|
28
|
+
if (!branchName || typeof branchName !== 'string') {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (issueNumber !== null) {
|
|
33
|
+
// Validate against specific issue number
|
|
34
|
+
const regex = branchNameRegex.prefix(issueNumber);
|
|
35
|
+
return regex.test(branchName);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Validate against any issue branch pattern
|
|
39
|
+
return branchNameRegex.any.test(branchName);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Extracts issue number and random ID from a branch name
|
|
44
|
+
* @param {string} branchName - The branch name to parse
|
|
45
|
+
* @returns {{issueNumber: string, randomId: string} | null} Parsed components or null if invalid
|
|
46
|
+
*/
|
|
47
|
+
export function parseIssueBranchName(branchName) {
|
|
48
|
+
if (!branchName || typeof branchName !== 'string') {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const match = branchName.match(branchNameRegex.any);
|
|
53
|
+
if (!match) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
issueNumber: match[1],
|
|
59
|
+
randomId: match[2]
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates the branch name prefix for a given issue number
|
|
65
|
+
* @param {number|string} issueNumber - The issue number
|
|
66
|
+
* @returns {string} The branch name prefix (e.g., "issue-123-")
|
|
67
|
+
*/
|
|
68
|
+
export function getIssueBranchPrefix(issueNumber) {
|
|
69
|
+
return `issue-${issueNumber}-`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Checks if a branch name matches the expected pattern for a specific issue
|
|
74
|
+
* @param {string} branchName - The branch name to check
|
|
75
|
+
* @param {number|string} issueNumber - The issue number
|
|
76
|
+
* @returns {boolean} True if branch matches the issue pattern
|
|
77
|
+
*/
|
|
78
|
+
export function matchesIssuePattern(branchName, issueNumber) {
|
|
79
|
+
return isValidIssueBranchName(branchName, issueNumber);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Detects if a branch name uses the legacy (8-char) or new (12-char) format
|
|
84
|
+
* @param {string} branchName - The branch name to check
|
|
85
|
+
* @returns {'legacy' | 'new' | null} The format type or null if invalid
|
|
86
|
+
*/
|
|
87
|
+
export function detectBranchFormat(branchName) {
|
|
88
|
+
if (!branchName || typeof branchName !== 'string') {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (branchNameRegex.new.test(branchName)) {
|
|
93
|
+
return 'new';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (branchNameRegex.legacy.test(branchName)) {
|
|
97
|
+
return 'legacy';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function createOrCheckoutBranch({
|
|
104
|
+
isContinueMode,
|
|
105
|
+
prBranch,
|
|
106
|
+
issueNumber,
|
|
107
|
+
tempDir,
|
|
108
|
+
defaultBranch,
|
|
109
|
+
argv,
|
|
110
|
+
log,
|
|
111
|
+
formatAligned,
|
|
112
|
+
$,
|
|
113
|
+
crypto
|
|
114
|
+
}) {
|
|
115
|
+
// Create a branch for the issue or checkout existing PR branch
|
|
116
|
+
let branchName;
|
|
117
|
+
let checkoutResult;
|
|
118
|
+
|
|
119
|
+
if (isContinueMode && prBranch) {
|
|
120
|
+
// Continue mode: checkout existing PR branch
|
|
121
|
+
branchName = prBranch;
|
|
122
|
+
const repository = await import('./solve.repository.lib.mjs');
|
|
123
|
+
const { checkoutPrBranch } = repository;
|
|
124
|
+
checkoutResult = await checkoutPrBranch(tempDir, branchName, null, null); // prForkRemote and prForkOwner not needed here
|
|
125
|
+
} else {
|
|
126
|
+
// Traditional mode: create new branch for issue
|
|
127
|
+
const randomHex = crypto.randomBytes(6).toString('hex');
|
|
128
|
+
branchName = `issue-${issueNumber}-${randomHex}`;
|
|
129
|
+
await log(`\n${formatAligned('🌿', 'Creating branch:', `${branchName} from ${defaultBranch}`)}`);
|
|
130
|
+
|
|
131
|
+
// IMPORTANT: Don't use 2>&1 here as it can interfere with exit codes
|
|
132
|
+
// Git checkout -b outputs to stderr but that's normal
|
|
133
|
+
checkoutResult = await $({ cwd: tempDir })`git checkout -b ${branchName}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (checkoutResult.code !== 0) {
|
|
137
|
+
const errorOutput = (checkoutResult.stderr || checkoutResult.stdout || 'Unknown error').toString().trim();
|
|
138
|
+
await log('');
|
|
139
|
+
|
|
140
|
+
if (isContinueMode) {
|
|
141
|
+
const branchErrors = await import('./solve.branch-errors.lib.mjs');
|
|
142
|
+
const { handleBranchCheckoutError } = branchErrors;
|
|
143
|
+
await handleBranchCheckoutError({
|
|
144
|
+
branchName,
|
|
145
|
+
prNumber: null, // Will be set later
|
|
146
|
+
errorOutput,
|
|
147
|
+
issueUrl: argv['issue-url'] || argv._[0],
|
|
148
|
+
owner: null, // Will be set later
|
|
149
|
+
repo: null, // Will be set later
|
|
150
|
+
tempDir,
|
|
151
|
+
argv,
|
|
152
|
+
formatAligned,
|
|
153
|
+
log,
|
|
154
|
+
$
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
const branchErrors = await import('./solve.branch-errors.lib.mjs');
|
|
158
|
+
const { handleBranchCreationError } = branchErrors;
|
|
159
|
+
await handleBranchCreationError({
|
|
160
|
+
branchName,
|
|
161
|
+
errorOutput,
|
|
162
|
+
tempDir,
|
|
163
|
+
owner: null, // Will be set later
|
|
164
|
+
repo: null, // Will be set later
|
|
165
|
+
formatAligned,
|
|
166
|
+
log
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await log('');
|
|
171
|
+
await log(` 📂 Working directory: ${tempDir}`);
|
|
172
|
+
throw new Error('Branch operation failed');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// CRITICAL: Verify the branch was checked out and we switched to it
|
|
176
|
+
await log(`${formatAligned('🔍', 'Verifying:', isContinueMode ? 'Branch checkout...' : 'Branch creation...')}`);
|
|
177
|
+
const verifyResult = await $({ cwd: tempDir })`git branch --show-current`;
|
|
178
|
+
|
|
179
|
+
if (verifyResult.code !== 0 || !verifyResult.stdout) {
|
|
180
|
+
await log('');
|
|
181
|
+
await log(`${formatAligned('❌', 'BRANCH VERIFICATION FAILED', '')}`, { level: 'error' });
|
|
182
|
+
await log('');
|
|
183
|
+
await log(' 🔍 What happened:');
|
|
184
|
+
await log(` Unable to verify branch after ${isContinueMode ? 'checkout' : 'creation'} attempt.`);
|
|
185
|
+
await log('');
|
|
186
|
+
await log(' 🔧 Debug commands to try:');
|
|
187
|
+
await log(` cd ${tempDir} && git branch -a`);
|
|
188
|
+
await log(` cd ${tempDir} && git status`);
|
|
189
|
+
await log('');
|
|
190
|
+
throw new Error('Branch verification failed');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const actualBranch = verifyResult.stdout.toString().trim();
|
|
194
|
+
if (actualBranch !== branchName) {
|
|
195
|
+
// Branch wasn't actually created/checked out or we didn't switch to it
|
|
196
|
+
const branchErrors = await import('./solve.branch-errors.lib.mjs');
|
|
197
|
+
const { handleBranchVerificationError } = branchErrors;
|
|
198
|
+
await handleBranchVerificationError({
|
|
199
|
+
isContinueMode,
|
|
200
|
+
branchName,
|
|
201
|
+
actualBranch,
|
|
202
|
+
prNumber: null, // Will be set later
|
|
203
|
+
owner: null, // Will be set later
|
|
204
|
+
repo: null, // Will be set later
|
|
205
|
+
tempDir,
|
|
206
|
+
formatAligned,
|
|
207
|
+
log,
|
|
208
|
+
$
|
|
209
|
+
});
|
|
210
|
+
throw new Error('Branch verification mismatch');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (isContinueMode) {
|
|
214
|
+
await log(`${formatAligned('✅', 'Branch checked out:', branchName)}`);
|
|
215
|
+
await log(`${formatAligned('✅', 'Current branch:', actualBranch)}`);
|
|
216
|
+
if (argv.verbose) {
|
|
217
|
+
await log(' Branch operation: Checkout existing PR branch', { verbose: true });
|
|
218
|
+
await log(` Branch verification: ${actualBranch === branchName ? 'Matches expected' : 'MISMATCH!'}`, { verbose: true });
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
await log(`${formatAligned('✅', 'Branch created:', branchName)}`);
|
|
222
|
+
await log(`${formatAligned('✅', 'Current branch:', actualBranch)}`);
|
|
223
|
+
if (argv.verbose) {
|
|
224
|
+
await log(' Branch operation: Create new branch', { verbose: true });
|
|
225
|
+
await log(` Branch verification: ${actualBranch === branchName ? 'Matches expected' : 'MISMATCH!'}`, { verbose: true });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return branchName;
|
|
230
|
+
}
|