@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 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 | 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
- | `--help` | `-h` | Show all available options | - |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.32.3",
3
+ "version": "1.33.0",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -215,6 +215,7 @@ const KNOWN_OPTION_NAMES = [
215
215
  'prompt-examples-folder',
216
216
  'session-type',
217
217
  'working-directory',
218
+ 'auto-init-repository',
218
219
  'prompt-ensure-all-requirements-are-met',
219
220
  'finalize',
220
221
  'finalize-model',
@@ -232,15 +232,35 @@ export async function handleBranchCreationError({ branchName, errorOutput, tempD
232
232
  await log(` ${line}`);
233
233
  }
234
234
  await log('');
235
- await log(' 💡 Possible causes:');
236
- await log(' • Branch name already exists');
237
- await log(' Uncommitted changes in repository');
238
- await log(' • Git configuration issues');
239
- await log('');
240
- await log(' 🔧 How to fix:');
241
- await log(' 1. Try running the command again (uses random names)');
242
- await log(` 2. Check git status: cd ${tempDir} && git status`);
243
- await log(` 3. View existing branches: cd ${tempDir} && git branch -a`);
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, $ }) {
@@ -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
- const defaultBranch = defaultBranchResult.stdout.toString().trim();
69
+ let defaultBranch = defaultBranchResult.stdout.toString().trim();
70
70
  if (!defaultBranch) {
71
- await log('');
72
- await log(`${formatAligned('❌', 'DEFAULT BRANCH DETECTION FAILED', '')}`, { level: 'error' });
73
- await log('');
74
- await log(' 🔍 What happened:');
75
- await log(" Unable to determine the repository's default branch.");
76
- await log('');
77
- await log(' 💡 This might mean:');
78
- await log(' Repository is empty (no commits)');
79
- await log(' Unusual repository configuration');
80
- await log(' • Git command issues');
81
- await log('');
82
- await log(' 🔧 How to fix:');
83
- await log(' 1. Check repository status');
84
- await log(` 2. Verify locally: cd ${tempDir} && git branch`);
85
- await log(` 3. Check remote: cd ${tempDir} && git branch -r`);
86
- await log('');
87
- throw new Error('Default branch detection failed');
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
- const tryInitializeEmptyRepository = async (owner, repo) => {
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