@link-assistant/hive-mind 1.34.5 → 1.34.7

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,23 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.34.7
4
+
5
+ ### Patch Changes
6
+
7
+ - bb83be9: fix: fail with helpful error when --fork used on own repository (issue #1206)
8
+
9
+ ## 1.34.6
10
+
11
+ ### Patch Changes
12
+
13
+ - 3157192: Optimize CI/CD to skip checks for .gitkeep-only changes and harden .gitkeep cleanup logic (Issue #1436).
14
+
15
+ CI/CD jobs `version-check` and `helm-pr-check` now skip when only `.gitkeep` files changed, saving ~21 seconds of runner time per PR on the initial commit. The `detect-code-changes.mjs` script now excludes `.gitkeep` files from code change detection and outputs a `gitkeep-only` flag.
16
+
17
+ The `.gitkeep` cleanup logic in `solve.results.lib.mjs` is hardened with: (1) full commit message body detection (`%B` instead of `%s`) so `.gitkeep` references in commit body are found, (2) fallback file detection via `git diff-tree`, and (3) post-cleanup verification with direct removal fallback to prevent leftover `.gitkeep` files.
18
+
19
+ Also removes the leftover `.gitkeep` file from the repository that was left behind by PR #1420.
20
+
3
21
  ## 1.34.5
4
22
 
5
23
  ### Patch Changes
package/README.md CHANGED
@@ -509,6 +509,21 @@ Shows:
509
509
  - Commands run as the system user running the bot
510
510
  - Ensure proper authentication (`gh auth login`, `claude-profiles`)
511
511
 
512
+ ## 🏆 Best Practices
513
+
514
+ Hive Mind works even better when repositories have strong CI/CD pipelines and clear issue requirements. See:
515
+
516
+ - [BEST-PRACTICES.md](./docs/BEST-PRACTICES.md) — Universal prompts, issue writing guidelines, architecture improvement, and subagent patterns
517
+ - [CI-CD-BEST-PRACTICES.md](./docs/CI-CD-BEST-PRACTICES.md) — CI/CD pipeline setup, recommended templates, and enforcement strategies
518
+
519
+ Key benefits of proper CI/CD:
520
+
521
+ - AI solvers iterate until all checks pass
522
+ - Consistent quality regardless of human/AI team composition
523
+ - File size limits ensure code is readable by both AI and humans
524
+
525
+ Ready-to-use templates are available for JavaScript, Rust, Python, Go, C#, and Java.
526
+
512
527
  ## đŸ—ī¸ Architecture
513
528
 
514
529
  The Hive Mind operates on three layers:
@@ -850,21 +865,6 @@ That can be done, but not recommended as reboot have better effect.
850
865
 
851
866
  Unlicense License - see [LICENSE](./LICENSE)
852
867
 
853
- ## 🏆 Best Practices
854
-
855
- Hive Mind works even better when repositories have strong CI/CD pipelines and clear issue requirements. See:
856
-
857
- - [BEST-PRACTICES.md](./docs/BEST-PRACTICES.md) — Universal prompts, issue writing guidelines, architecture improvement, and subagent patterns
858
- - [CI-CD-BEST-PRACTICES.md](./docs/CI-CD-BEST-PRACTICES.md) — CI/CD pipeline setup, recommended templates, and enforcement strategies
859
-
860
- Key benefits of proper CI/CD:
861
-
862
- - AI solvers iterate until all checks pass
863
- - Consistent quality regardless of human/AI team composition
864
- - File size limits ensure code is readable by both AI and humans
865
-
866
- Ready-to-use templates are available for JavaScript, Rust, Python, Go, C#, and Java.
867
-
868
868
  ## 🤖 Contributing
869
869
 
870
870
  This project uses AI-driven development. See [CONTRIBUTING.md](./docs/CONTRIBUTING.md) for human-AI collaboration guidelines.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.34.5",
3
+ "version": "1.34.7",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -33,38 +33,16 @@ import { safeExit } from './exit-handler.lib.mjs';
33
33
  const githubLib = await import('./github.lib.mjs');
34
34
  const { checkRepositoryWritePermission } = githubLib;
35
35
 
36
- // Get the root repository of any repository
37
- // Returns the source (root) repository if the repo is a fork, otherwise returns the repo itself
38
- // Returns null if repository is not accessible (404 or other errors)
36
+ // Get root repository (fork source or self), or null if inaccessible
39
37
  export const getRootRepository = async (owner, repo) => {
40
38
  try {
41
39
  const result = await $`gh api repos/${owner}/${repo} --jq '{fork: .fork, source: .source.full_name}' 2>&1`;
42
-
43
- if (result.code !== 0) {
44
- // Check if it's a 404 error - repository doesn't exist or no permissions
45
- const errorOutput = (result.stderr || result.stdout || '').toString();
46
- if (errorOutput.includes('HTTP 404') || errorOutput.includes('Not Found')) {
47
- // Repository not accessible - this will be handled by fork creation logic
48
- // Return null to indicate we couldn't determine root repo
49
- return null;
50
- }
51
- return null;
52
- }
40
+ if (result.code !== 0) return null;
53
41
 
54
42
  const repoInfo = JSON.parse(result.stdout.toString().trim());
55
-
56
- if (repoInfo.fork && repoInfo.source) {
57
- return repoInfo.source;
58
- } else {
59
- return `${owner}/${repo}`;
60
- }
43
+ return repoInfo.fork && repoInfo.source ? repoInfo.source : `${owner}/${repo}`;
61
44
  } catch (error) {
62
- reportError(error, {
63
- context: 'get_root_repository',
64
- owner,
65
- repo,
66
- operation: 'determine_fork_root',
67
- });
45
+ reportError(error, { context: 'get_root_repository', owner, repo, operation: 'determine_fork_root' });
68
46
  return null;
69
47
  }
70
48
  };
@@ -73,34 +51,20 @@ export const getRootRepository = async (owner, repo) => {
73
51
  export const checkExistingForkOfRoot = async rootRepo => {
74
52
  try {
75
53
  const userResult = await $`gh api user --jq .login`;
76
- if (userResult.code !== 0) {
77
- return null;
78
- }
54
+ if (userResult.code !== 0) return null;
79
55
  const currentUser = userResult.stdout.toString().trim();
80
56
 
81
57
  const forksResult = await $`gh api repos/${rootRepo}/forks --paginate --jq '.[] | select(.owner.login == "${currentUser}") | .full_name'`;
82
-
83
- if (forksResult.code !== 0) {
84
- return null;
85
- }
58
+ if (forksResult.code !== 0) return null;
86
59
 
87
60
  const forks = forksResult.stdout
88
61
  .toString()
89
62
  .trim()
90
63
  .split('\n')
91
64
  .filter(f => f);
92
-
93
- if (forks.length > 0) {
94
- return forks[0];
95
- } else {
96
- return null;
97
- }
65
+ return forks.length > 0 ? forks[0] : null;
98
66
  } catch (error) {
99
- reportError(error, {
100
- context: 'check_existing_fork_of_root',
101
- rootRepo,
102
- operation: 'search_user_forks',
103
- });
67
+ reportError(error, { context: 'check_existing_fork_of_root', rootRepo, operation: 'search_user_forks' });
104
68
  return null;
105
69
  }
106
70
  };
@@ -407,6 +371,30 @@ export const setupRepository = async (argv, owner, repo, forkOwner = null, issue
407
371
  }
408
372
  const currentUser = userResult.stdout.toString().trim();
409
373
 
374
+ // Check if user owns the repository (Issue #1206)
375
+ // GitHub doesn't allow forking your own repositories and returns HTTP 403
376
+ // When --fork is explicitly used, fail with a clear error and suggest --auto-fork
377
+ if (currentUser === owner) {
378
+ await log('');
379
+ await log(`${formatAligned('❌', 'CANNOT FORK OWN REPOSITORY', '')}`, { level: 'error' });
380
+ await log('');
381
+ await log(' 🔍 What happened:');
382
+ await log(` You are the owner of ${owner}/${repo}`);
383
+ await log(' GitHub does not allow forking your own repositories (returns HTTP 403)');
384
+ await log('');
385
+ await log(' 💡 Solutions:');
386
+ await log('');
387
+ await log(' Option 1: Use --auto-fork instead of --fork');
388
+ await log(' --auto-fork automatically detects ownership and works directly');
389
+ await log(' on the repository when you have write access, without forking.');
390
+ await log(` Example: solve "${issueUrl || `https://github.com/${owner}/${repo}/issues/<number>`}" --auto-fork`);
391
+ await log('');
392
+ await log(' Option 2: Work directly on the repository without forking');
393
+ await log(` Example: solve "${issueUrl || `https://github.com/${owner}/${repo}/issues/<number>`}"`);
394
+ await log('');
395
+ await safeExit(1, 'Cannot fork own repository - use --auto-fork or remove --fork flag');
396
+ }
397
+
410
398
  // Check for fork conflicts (Issue #344)
411
399
  // Detect if we're trying to fork a repository that shares the same root
412
400
  // as an existing fork we already have
@@ -251,11 +251,19 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
251
251
  await log(` Detected initial commit: ${claudeCommitHash.substring(0, 7)}`, { verbose: true });
252
252
  }
253
253
 
254
- // Determine which file was used based on the commit message or flags
255
- // Check the commit message to determine which file was committed
256
- const commitMsgResult = await $({ cwd: tempDir })`git log -1 --format=%s ${claudeCommitHash} 2>&1`;
254
+ // Determine which file was used based on the commit message or actual files changed
255
+ // Use %B (full message including body) instead of %s (subject only) to catch ".gitkeep" in body
256
+ // Also check the actual files changed as a fallback (Issue #1436)
257
+ const commitMsgResult = await $({ cwd: tempDir })`git log -1 --format=%B ${claudeCommitHash} 2>&1`;
257
258
  const commitMsg = commitMsgResult.stdout?.trim() || '';
258
- const isGitkeepFile = commitMsg.includes('.gitkeep');
259
+ let isGitkeepFile = commitMsg.includes('.gitkeep');
260
+
261
+ // Fallback: check actual files changed in the commit if message doesn't mention .gitkeep
262
+ if (!isGitkeepFile) {
263
+ const filesResult = await $({ cwd: tempDir })`git diff-tree --no-commit-id --name-only -r ${claudeCommitHash} 2>&1`;
264
+ const files = filesResult.stdout?.trim().split('\n').filter(Boolean) || [];
265
+ isGitkeepFile = files.includes('.gitkeep');
266
+ }
259
267
  const fileName = isGitkeepFile ? '.gitkeep' : 'CLAUDE.md';
260
268
 
261
269
  await log(formatAligned('🔄', 'Cleanup:', `Reverting ${fileName} commit`));
@@ -381,6 +389,31 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
381
389
  }
382
390
  }
383
391
  }
392
+ // Post-cleanup verification: check if the file was actually removed (Issue #1436)
393
+ // This catches cases where revert/push succeeded in logs but file still exists
394
+ const verifyResult = await $({ cwd: tempDir })`git ls-files ${fileName} 2>&1`;
395
+ const fileStillExists = verifyResult.code === 0 && verifyResult.stdout && verifyResult.stdout.trim();
396
+ if (fileStillExists) {
397
+ await log(` âš ī¸ WARNING: ${fileName} still exists after cleanup — attempting direct removal...`);
398
+ // Check if the file existed before the initial commit (parent)
399
+ const parentCommit = `${claudeCommitHash}~1`;
400
+ const parentFileExists = await $({ cwd: tempDir })`git cat-file -e ${parentCommit}:${fileName} 2>&1`;
401
+ if (parentFileExists.code !== 0) {
402
+ // File didn't exist before the session — force remove it
403
+ await $({ cwd: tempDir })`git rm -f ${fileName} 2>&1`;
404
+ const fallbackCommit = await $({ cwd: tempDir })`git commit -m "Remove leftover ${fileName} (post-cleanup fallback, Issue #1436)" 2>&1`;
405
+ if (fallbackCommit.code === 0) {
406
+ const fallbackPush = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
407
+ if (fallbackPush.code === 0) {
408
+ await log(` ✅ ${fileName} removed via post-cleanup fallback`);
409
+ } else {
410
+ await log(` âš ī¸ ${fileName} removed locally but push failed`, { verbose: true });
411
+ }
412
+ }
413
+ } else {
414
+ await log(` â„šī¸ ${fileName} existed before this session — keeping pre-existing file`, { verbose: true });
415
+ }
416
+ }
384
417
  } catch (e) {
385
418
  reportError(e, {
386
419
  context: 'cleanup_claude_file',