@link-assistant/hive-mind 1.32.3 → 1.33.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 +6 -0
- package/README.md +7 -6
- package/package.json +1 -1
- package/src/option-suggestions.lib.mjs +1 -0
- package/src/solve.branch-errors.lib.mjs +29 -9
- package/src/solve.config.lib.mjs +5 -0
- package/src/solve.mjs +5 -0
- package/src/solve.repo-setup.lib.mjs +197 -20
- package/src/solve.repository.lib.mjs +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.33.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f7a2fdd: Add --auto-init-repository option to automatically initialize empty repositories by creating a simple README.md file, enabling branch creation and pull request workflows on repositories with no commits
|
|
8
|
+
|
|
3
9
|
## 1.32.3
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -341,12 +341,13 @@ solve <issue-url> [options]
|
|
|
341
341
|
|
|
342
342
|
**Other useful options:**
|
|
343
343
|
|
|
344
|
-
| Option
|
|
345
|
-
|
|
|
346
|
-
| `--tool`
|
|
347
|
-
| `--verbose`
|
|
348
|
-
| `--attach-logs`
|
|
349
|
-
| `--
|
|
344
|
+
| Option | Alias | Description | Default |
|
|
345
|
+
| ------------------------ | ----- | ------------------------------------------------ | ------- |
|
|
346
|
+
| `--tool` | | AI tool (claude, opencode, codex, agent) | claude |
|
|
347
|
+
| `--verbose` | `-v` | Enable verbose logging | false |
|
|
348
|
+
| `--attach-logs` | | Attach logs to PR (⚠️ may expose sensitive data) | false |
|
|
349
|
+
| `--auto-init-repository` | | Auto-initialize empty repos (creates README.md) | false |
|
|
350
|
+
| `--help` | `-h` | Show all available options | - |
|
|
350
351
|
|
|
351
352
|
> **📖 Full options list**: See [docs/CONFIGURATION.md](./docs/CONFIGURATION.md#solve-options) for all available options including forking, auto-continue, watch mode, and experimental features.
|
|
352
353
|
|
package/package.json
CHANGED
|
@@ -232,15 +232,35 @@ export async function handleBranchCreationError({ branchName, errorOutput, tempD
|
|
|
232
232
|
await log(` ${line}`);
|
|
233
233
|
}
|
|
234
234
|
await log('');
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
235
|
+
|
|
236
|
+
// Check if this is an empty repository error (no commits to branch from)
|
|
237
|
+
const isEmptyRepoError = errorOutput.includes('is not a commit') || errorOutput.includes('not a valid object name') || errorOutput.includes('unknown revision');
|
|
238
|
+
if (isEmptyRepoError) {
|
|
239
|
+
await log(' 💡 Root cause:');
|
|
240
|
+
await log(' The repository appears to be empty (no commits).');
|
|
241
|
+
await log(' Cannot create a branch from a non-existent commit.');
|
|
242
|
+
await log('');
|
|
243
|
+
await log(' 🔧 How to fix:');
|
|
244
|
+
await log(' Use the --auto-init-repository flag to automatically initialize the repository:');
|
|
245
|
+
if (owner && repo) {
|
|
246
|
+
await log(` solve https://github.com/${owner}/${repo}/issues/<number> --auto-init-repository`);
|
|
247
|
+
} else {
|
|
248
|
+
await log(' solve <issue-url> --auto-init-repository');
|
|
249
|
+
}
|
|
250
|
+
await log('');
|
|
251
|
+
await log(' This will create a simple README.md file to make the repository non-empty,');
|
|
252
|
+
await log(' allowing branch creation and pull request workflows to proceed.');
|
|
253
|
+
} else {
|
|
254
|
+
await log(' 💡 Possible causes:');
|
|
255
|
+
await log(' • Branch name already exists');
|
|
256
|
+
await log(' • Uncommitted changes in repository');
|
|
257
|
+
await log(' • Git configuration issues');
|
|
258
|
+
await log('');
|
|
259
|
+
await log(' 🔧 How to fix:');
|
|
260
|
+
await log(' 1. Try running the command again (uses random names)');
|
|
261
|
+
await log(` 2. Check git status: cd ${tempDir} && git status`);
|
|
262
|
+
await log(` 3. View existing branches: cd ${tempDir} && git branch -a`);
|
|
263
|
+
}
|
|
244
264
|
}
|
|
245
265
|
|
|
246
266
|
export async function handleBranchVerificationError({ isContinueMode, branchName, actualBranch, prNumber, owner, repo, tempDir, formatAligned, log, $ }) {
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -362,6 +362,11 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
362
362
|
description: 'Guide Claude to use agent-commander CLI (start-agent) instead of native Task tool for subagent delegation. Allows using any supported agent type (claude, opencode, codex, agent) with unified API. Only works with --tool claude and requires agent-commander to be installed.',
|
|
363
363
|
default: false,
|
|
364
364
|
},
|
|
365
|
+
'auto-init-repository': {
|
|
366
|
+
type: 'boolean',
|
|
367
|
+
description: 'Automatically initialize empty repositories by creating a simple README.md file. Only works when you have write access to the repository. This allows branch creation and pull request workflows to proceed on repositories that have no commits.',
|
|
368
|
+
default: false,
|
|
369
|
+
},
|
|
365
370
|
'attach-solution-summary': {
|
|
366
371
|
type: 'boolean',
|
|
367
372
|
description: 'Attach the AI solution summary (from the result field) as a comment to the PR/issue after completion. The summary is extracted from the AI tool JSON output and posted under a "Solution summary" header.',
|
package/src/solve.mjs
CHANGED
|
@@ -534,11 +534,16 @@ try {
|
|
|
534
534
|
});
|
|
535
535
|
|
|
536
536
|
// Verify default branch and status using the new module
|
|
537
|
+
// Pass argv, owner, repo, issueUrl for empty repository auto-initialization (--auto-init-repository)
|
|
537
538
|
const defaultBranch = await verifyDefaultBranchAndStatus({
|
|
538
539
|
tempDir,
|
|
539
540
|
log,
|
|
540
541
|
formatAligned,
|
|
541
542
|
$,
|
|
543
|
+
argv,
|
|
544
|
+
owner,
|
|
545
|
+
repo,
|
|
546
|
+
issueUrl,
|
|
542
547
|
});
|
|
543
548
|
// Create or checkout branch using the new module
|
|
544
549
|
const branchName = await createOrCheckoutBranch({
|
|
@@ -56,7 +56,7 @@ async function setupPrForkRemote(tempDir, argv, prForkOwner, repo, isContinueMod
|
|
|
56
56
|
return await setupPrForkFn(tempDir, argv, prForkOwner, repo, isContinueMode, owner);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export async function verifyDefaultBranchAndStatus({ tempDir, log, formatAligned,
|
|
59
|
+
export async function verifyDefaultBranchAndStatus({ tempDir, log, formatAligned, $, argv, owner, repo, issueUrl }) {
|
|
60
60
|
// Verify we're on the default branch and get its name
|
|
61
61
|
const defaultBranchResult = await $({ cwd: tempDir })`git branch --show-current`;
|
|
62
62
|
|
|
@@ -66,27 +66,128 @@ export async function verifyDefaultBranchAndStatus({ tempDir, log, formatAligned
|
|
|
66
66
|
throw new Error('Failed to get current branch');
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
let defaultBranch = defaultBranchResult.stdout.toString().trim();
|
|
70
70
|
if (!defaultBranch) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
71
|
+
// Repository is likely empty (no commits) - detect and handle
|
|
72
|
+
const isEmptyRepo = await detectEmptyRepository(tempDir, $);
|
|
73
|
+
|
|
74
|
+
if (isEmptyRepo && argv && argv.autoInitRepository && owner && repo) {
|
|
75
|
+
// --auto-init-repository is enabled, try to initialize
|
|
76
|
+
await log('');
|
|
77
|
+
await log(`${formatAligned('⚠️', 'EMPTY REPOSITORY', 'detected')}`, { level: 'warn' });
|
|
78
|
+
await log(`${formatAligned('', '', `Repository ${owner}/${repo} contains no commits`)}`);
|
|
79
|
+
await log(`${formatAligned('', '', '--auto-init-repository is enabled, attempting initialization...')}`);
|
|
80
|
+
await log('');
|
|
81
|
+
|
|
82
|
+
const repository = await import('./solve.repository.lib.mjs');
|
|
83
|
+
const { tryInitializeEmptyRepository } = repository;
|
|
84
|
+
const initialized = await tryInitializeEmptyRepository(owner, repo);
|
|
85
|
+
|
|
86
|
+
if (initialized) {
|
|
87
|
+
await log('');
|
|
88
|
+
await log(`${formatAligned('🔄', 'Re-fetching:', 'Pulling initialized repository...')}`);
|
|
89
|
+
// Wait for GitHub to process the new file
|
|
90
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
91
|
+
|
|
92
|
+
// Re-fetch the origin to get the new commit
|
|
93
|
+
const fetchResult = await $({ cwd: tempDir })`git fetch origin`;
|
|
94
|
+
if (fetchResult.code !== 0) {
|
|
95
|
+
await log(`${formatAligned('❌', 'Fetch failed:', 'Could not fetch after initialization')}`, { level: 'error' });
|
|
96
|
+
throw new Error('Failed to fetch after empty repository initialization');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Determine default branch name from the remote
|
|
100
|
+
const remoteHeadResult = await $({ cwd: tempDir })`git remote show origin`;
|
|
101
|
+
let remoteBranch = 'main'; // default fallback
|
|
102
|
+
if (remoteHeadResult.code === 0) {
|
|
103
|
+
const remoteOutput = remoteHeadResult.stdout.toString();
|
|
104
|
+
const headMatch = remoteOutput.match(/HEAD branch:\s*(\S+)/);
|
|
105
|
+
if (headMatch) {
|
|
106
|
+
remoteBranch = headMatch[1];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Checkout the remote branch locally
|
|
111
|
+
const checkoutResult = await $({ cwd: tempDir })`git checkout -b ${remoteBranch} origin/${remoteBranch}`;
|
|
112
|
+
if (checkoutResult.code !== 0) {
|
|
113
|
+
// Try alternative: maybe the branch already exists locally somehow
|
|
114
|
+
const altResult = await $({ cwd: tempDir })`git checkout ${remoteBranch}`;
|
|
115
|
+
if (altResult.code !== 0) {
|
|
116
|
+
await log(`${formatAligned('❌', 'Checkout failed:', `Could not checkout ${remoteBranch} after initialization`)}`, { level: 'error' });
|
|
117
|
+
throw new Error('Failed to checkout branch after empty repository initialization');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
defaultBranch = remoteBranch;
|
|
122
|
+
await log(`${formatAligned('✅', 'Repository initialized:', `Now on branch ${defaultBranch}`)}`);
|
|
123
|
+
await log(`\n${formatAligned('📌', 'Default branch:', defaultBranch)}`);
|
|
124
|
+
} else {
|
|
125
|
+
// Auto-init failed - provide helpful message with --auto-init-repository context
|
|
126
|
+
await log('');
|
|
127
|
+
await log(`${formatAligned('❌', 'AUTO-INIT FAILED', '')}`, { level: 'error' });
|
|
128
|
+
await log('');
|
|
129
|
+
await log(' 🔍 What happened:');
|
|
130
|
+
await log(` Repository ${owner}/${repo} is empty (no commits).`);
|
|
131
|
+
await log(' --auto-init-repository was enabled but initialization failed.');
|
|
132
|
+
await log(' You may not have write access to create files in the repository.');
|
|
133
|
+
await log('');
|
|
134
|
+
await log(' 💡 How to fix:');
|
|
135
|
+
await log(' Option 1: Ask repository owner to add initial content');
|
|
136
|
+
await log(' Even a simple README.md file would allow branch creation');
|
|
137
|
+
await log('');
|
|
138
|
+
await log(` Option 2: Manually initialize: gh api repos/${owner}/${repo}/contents/README.md \\`);
|
|
139
|
+
await log(' --method PUT --field message="Initialize repository" \\');
|
|
140
|
+
await log(' --field content="$(echo "# repo" | base64)"');
|
|
141
|
+
await log('');
|
|
142
|
+
|
|
143
|
+
// Post a comment on the issue informing about the empty repository
|
|
144
|
+
await tryCommentOnIssueAboutEmptyRepo({ issueUrl, owner, repo, log, formatAligned, $ });
|
|
145
|
+
|
|
146
|
+
throw new Error('Empty repository auto-initialization failed');
|
|
147
|
+
}
|
|
148
|
+
} else if (isEmptyRepo) {
|
|
149
|
+
// Empty repo detected but --auto-init-repository is not enabled
|
|
150
|
+
await log('');
|
|
151
|
+
await log(`${formatAligned('❌', 'EMPTY REPOSITORY DETECTED', '')}`, { level: 'error' });
|
|
152
|
+
await log('');
|
|
153
|
+
await log(' 🔍 What happened:');
|
|
154
|
+
await log(` The repository${owner && repo ? ` ${owner}/${repo}` : ''} is empty (no commits).`);
|
|
155
|
+
await log(' Cannot create branches or pull requests on an empty repository.');
|
|
156
|
+
await log('');
|
|
157
|
+
await log(' 💡 How to fix:');
|
|
158
|
+
await log(' Option 1: Use --auto-init-repository flag to automatically create a README.md');
|
|
159
|
+
await log(` solve <issue-url> --auto-init-repository`);
|
|
160
|
+
await log('');
|
|
161
|
+
await log(' Option 2: Ask repository owner to add initial content');
|
|
162
|
+
await log(' Even a simple README.md file would allow branch creation');
|
|
163
|
+
await log('');
|
|
164
|
+
|
|
165
|
+
// Post a comment on the issue informing about the empty repository
|
|
166
|
+
await tryCommentOnIssueAboutEmptyRepo({ issueUrl, owner, repo, log, formatAligned, $ });
|
|
167
|
+
|
|
168
|
+
throw new Error('Empty repository detected - use --auto-init-repository to initialize');
|
|
169
|
+
} else {
|
|
170
|
+
// Not an empty repo, some other issue with branch detection
|
|
171
|
+
await log('');
|
|
172
|
+
await log(`${formatAligned('❌', 'DEFAULT BRANCH DETECTION FAILED', '')}`, { level: 'error' });
|
|
173
|
+
await log('');
|
|
174
|
+
await log(' 🔍 What happened:');
|
|
175
|
+
await log(" Unable to determine the repository's default branch.");
|
|
176
|
+
await log('');
|
|
177
|
+
await log(' 💡 This might mean:');
|
|
178
|
+
await log(' • Unusual repository configuration');
|
|
179
|
+
await log(' • Git command issues');
|
|
180
|
+
await log('');
|
|
181
|
+
await log(' 🔧 How to fix:');
|
|
182
|
+
await log(' 1. Check repository status');
|
|
183
|
+
await log(` 2. Verify locally: cd ${tempDir} && git branch`);
|
|
184
|
+
await log(` 3. Check remote: cd ${tempDir} && git branch -r`);
|
|
185
|
+
await log('');
|
|
186
|
+
throw new Error('Default branch detection failed');
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
await log(`\n${formatAligned('📌', 'Default branch:', defaultBranch)}`);
|
|
88
190
|
}
|
|
89
|
-
await log(`\n${formatAligned('📌', 'Default branch:', defaultBranch)}`);
|
|
90
191
|
|
|
91
192
|
// Ensure we're on a clean default branch
|
|
92
193
|
const statusResult = await $({ cwd: tempDir })`git status --porcelain`;
|
|
@@ -106,3 +207,79 @@ export async function verifyDefaultBranchAndStatus({ tempDir, log, formatAligned
|
|
|
106
207
|
|
|
107
208
|
return defaultBranch;
|
|
108
209
|
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Try to post a comment on the issue informing the user about the empty repository.
|
|
213
|
+
* This is a non-critical operation - errors are silently ignored.
|
|
214
|
+
* When --auto-init-repository succeeds, no comment is posted (no action needed from the user).
|
|
215
|
+
*/
|
|
216
|
+
async function tryCommentOnIssueAboutEmptyRepo({ issueUrl, owner, repo, log, formatAligned, $ }) {
|
|
217
|
+
if (!issueUrl) return;
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const issueMatch = issueUrl.match(/\/issues\/(\d+)/);
|
|
221
|
+
if (!issueMatch) return;
|
|
222
|
+
|
|
223
|
+
const issueNumber = issueMatch[1];
|
|
224
|
+
await log(`${formatAligned('💬', 'Creating comment:', 'Informing about empty repository...')}`);
|
|
225
|
+
|
|
226
|
+
const commentBody = `## ⚠️ Repository Initialization Required
|
|
227
|
+
|
|
228
|
+
Hello! I attempted to work on this issue, but encountered a problem:
|
|
229
|
+
|
|
230
|
+
**Issue**: The repository is empty (no commits) and branches cannot be created.
|
|
231
|
+
**Reason**: Git cannot create branches in a repository with no commits.
|
|
232
|
+
|
|
233
|
+
### 🔧 How to resolve:
|
|
234
|
+
|
|
235
|
+
**Option 1: Use \`--auto-init-repository\` flag**
|
|
236
|
+
Re-run the solver with the \`--auto-init-repository\` flag to automatically create a simple README.md:
|
|
237
|
+
\`\`\`
|
|
238
|
+
solve ${issueUrl} --auto-init-repository
|
|
239
|
+
\`\`\`
|
|
240
|
+
|
|
241
|
+
**Option 2: Initialize the repository yourself**
|
|
242
|
+
Please add initial content to the repository. Even a simple README.md (even if it is empty or contains just the title) file would make it possible to create branches and work on this issue.
|
|
243
|
+
|
|
244
|
+
Once the repository contains at least one commit with any file, I'll be able to proceed with solving this issue.
|
|
245
|
+
|
|
246
|
+
Thank you!`;
|
|
247
|
+
|
|
248
|
+
const commentResult = await $`gh issue comment ${issueNumber} --repo ${owner}/${repo} --body ${commentBody}`;
|
|
249
|
+
if (commentResult.code === 0) {
|
|
250
|
+
await log(`${formatAligned('✅', 'Comment created:', `Posted to issue #${issueNumber}`)}`);
|
|
251
|
+
} else {
|
|
252
|
+
await log(`${formatAligned('⚠️', 'Note:', 'Could not post comment to issue (this is not critical)')}`);
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
// Silently ignore comment creation errors - not critical to the process
|
|
256
|
+
await log(`${formatAligned('⚠️', 'Note:', 'Could not post comment to issue (this is not critical)')}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Detect if a cloned repository is empty (has no commits).
|
|
262
|
+
* An empty repository has no branches and no commits.
|
|
263
|
+
*/
|
|
264
|
+
async function detectEmptyRepository(tempDir, $) {
|
|
265
|
+
// Check if there are any commits in the repository
|
|
266
|
+
const logResult = await $({ cwd: tempDir })`git rev-parse HEAD 2>&1`;
|
|
267
|
+
if (logResult.code !== 0) {
|
|
268
|
+
// git rev-parse HEAD fails when there are no commits
|
|
269
|
+
const output = (logResult.stdout || logResult.stderr || '').toString();
|
|
270
|
+
if (output.includes('unknown revision') || output.includes('bad default revision') || output.includes('does not have any commits')) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Also check if there are any remote branches
|
|
276
|
+
const remoteBranchResult = await $({ cwd: tempDir })`git branch -r`;
|
|
277
|
+
if (remoteBranchResult.code === 0) {
|
|
278
|
+
const branches = remoteBranchResult.stdout.toString().trim();
|
|
279
|
+
if (!branches) {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
@@ -317,8 +317,9 @@ export const setupTempDirectory = async (argv, workspaceInfo = null) => {
|
|
|
317
317
|
};
|
|
318
318
|
|
|
319
319
|
// Try to initialize an empty repository by creating a simple README.md
|
|
320
|
-
// This makes the repository forkable
|
|
321
|
-
|
|
320
|
+
// This makes the repository forkable and allows branch creation
|
|
321
|
+
// Exported for use in solve.repo-setup.lib.mjs (direct access path for empty repos)
|
|
322
|
+
export const tryInitializeEmptyRepository = async (owner, repo) => {
|
|
322
323
|
try {
|
|
323
324
|
await log(`${formatAligned('🔧', 'Auto-fix:', 'Attempting to initialize empty repository...')}`);
|
|
324
325
|
|