@link-assistant/hive-mind 1.2.8 → 1.2.10

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,24 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.2.10
4
+
5
+ ### Patch Changes
6
+
7
+ - 7ba1476: Auto-cleanup .playwright-mcp/ folder to prevent false auto-restart triggers
8
+ - Add auto-cleanup of .playwright-mcp/ folder before checking uncommitted changes
9
+ - Add --playwright-mcp-auto-cleanup option (enabled by default)
10
+ - Use --no-playwright-mcp-auto-cleanup to disable cleanup for debugging
11
+ - Add comprehensive case study documentation for issue #1124
12
+
13
+ ## 1.2.9
14
+
15
+ ### Patch Changes
16
+
17
+ - b5e047a: Fix branch checkout error showing null/null instead of actual repository URL
18
+ - Pass owner/repo/prNumber to branch error handlers for accurate error messages
19
+ - Add upstream remote fallback when PR branch not found in origin (handles bot PRs)
20
+ - Add case study documentation for issue #1120
21
+
3
22
  ## 1.2.8
4
23
 
5
24
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.2.8",
3
+ "version": "1.2.10",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -100,7 +100,7 @@ export function detectBranchFormat(branchName) {
100
100
  return null;
101
101
  }
102
102
 
103
- export async function createOrCheckoutBranch({ isContinueMode, prBranch, issueNumber, tempDir, defaultBranch, argv, log, formatAligned, $, crypto }) {
103
+ export async function createOrCheckoutBranch({ isContinueMode, prBranch, issueNumber, tempDir, defaultBranch, argv, log, formatAligned, $, crypto, owner, repo, prNumber }) {
104
104
  // Create a branch for the issue or checkout existing PR branch
105
105
  let branchName;
106
106
  let checkoutResult;
@@ -136,11 +136,11 @@ export async function createOrCheckoutBranch({ isContinueMode, prBranch, issueNu
136
136
  const { handleBranchCheckoutError } = branchErrors;
137
137
  await handleBranchCheckoutError({
138
138
  branchName,
139
- prNumber: null, // Will be set later
139
+ prNumber,
140
140
  errorOutput,
141
141
  issueUrl: argv['issue-url'] || argv._[0],
142
- owner: null, // Will be set later
143
- repo: null, // Will be set later
142
+ owner,
143
+ repo,
144
144
  tempDir,
145
145
  argv,
146
146
  formatAligned,
@@ -154,8 +154,8 @@ export async function createOrCheckoutBranch({ isContinueMode, prBranch, issueNu
154
154
  branchName,
155
155
  errorOutput,
156
156
  tempDir,
157
- owner: null, // Will be set later
158
- repo: null, // Will be set later
157
+ owner,
158
+ repo,
159
159
  formatAligned,
160
160
  log,
161
161
  });
@@ -193,9 +193,9 @@ export async function createOrCheckoutBranch({ isContinueMode, prBranch, issueNu
193
193
  isContinueMode,
194
194
  branchName,
195
195
  actualBranch,
196
- prNumber: null, // Will be set later
197
- owner: null, // Will be set later
198
- repo: null, // Will be set later
196
+ prNumber,
197
+ owner,
198
+ repo,
199
199
  tempDir,
200
200
  formatAligned,
201
201
  log,
@@ -311,6 +311,11 @@ export const createYargsConfig = yargsInstance => {
311
311
  description: 'Include prompt to check related/sibling pull requests when studying related work. Enabled by default, use --no-prompt-check-sibling-pull-requests to disable.',
312
312
  default: true,
313
313
  })
314
+ .option('playwright-mcp-auto-cleanup', {
315
+ type: 'boolean',
316
+ description: 'Automatically remove .playwright-mcp/ folder before checking for uncommitted changes. This prevents browser automation artifacts from triggering auto-restart. Use --no-playwright-mcp-auto-cleanup to keep the folder for debugging.',
317
+ default: true,
318
+ })
314
319
  .parserConfiguration({
315
320
  'boolean-negation': true,
316
321
  })
package/src/solve.mjs CHANGED
@@ -553,6 +553,9 @@ try {
553
553
  formatAligned,
554
554
  $,
555
555
  crypto,
556
+ owner,
557
+ repo,
558
+ prNumber,
556
559
  });
557
560
 
558
561
  // Auto-merge default branch to pull request branch if enabled
@@ -1113,6 +1116,27 @@ try {
1113
1116
  await safeExit(1, `${argv.tool.toUpperCase()} execution failed`);
1114
1117
  }
1115
1118
 
1119
+ // Clean up .playwright-mcp/ folder before checking for uncommitted changes
1120
+ // This prevents browser automation artifacts from triggering auto-restart (Issue #1124)
1121
+ if (argv.playwrightMcpAutoCleanup !== false) {
1122
+ const playwrightMcpDir = path.join(tempDir, '.playwright-mcp');
1123
+ try {
1124
+ const playwrightMcpExists = await fs
1125
+ .stat(playwrightMcpDir)
1126
+ .then(() => true)
1127
+ .catch(() => false);
1128
+ if (playwrightMcpExists) {
1129
+ await fs.rm(playwrightMcpDir, { recursive: true, force: true });
1130
+ await log('🧹 Cleaned up .playwright-mcp/ folder (browser automation artifacts)', { verbose: true });
1131
+ }
1132
+ } catch (cleanupError) {
1133
+ // Non-critical error, just log and continue
1134
+ await log(`âš ī¸ Could not clean up .playwright-mcp/ folder: ${cleanupError.message}`, { verbose: true });
1135
+ }
1136
+ } else {
1137
+ await log('â„šī¸ Playwright MCP auto-cleanup disabled via --no-playwright-mcp-auto-cleanup', { verbose: true });
1138
+ }
1139
+
1116
1140
  // Check for uncommitted changes
1117
1141
  // When limit is reached, force auto-commit of any uncommitted changes to preserve work
1118
1142
  const shouldAutoCommit = argv['auto-commit-uncommitted-changes'] || limitReached;
@@ -1208,6 +1208,41 @@ export const checkoutPrBranch = async (tempDir, branchName, prForkRemote, prFork
1208
1208
  } else {
1209
1209
  // Branch doesn't exist locally, try to checkout from remote
1210
1210
  checkoutResult = await $({ cwd: tempDir })`git checkout -b ${branchName} ${remoteName}/${branchName}`;
1211
+
1212
+ // If checkout from origin failed, try upstream remote as fallback
1213
+ // This handles the case where we're in fork mode but the PR branch exists in upstream
1214
+ // (e.g., a bot created PR in the upstream repo, not a fork PR)
1215
+ if (checkoutResult.code !== 0 && remoteName === 'origin') {
1216
+ await log(`${formatAligned('🔄', 'Branch not in origin:', 'Checking upstream remote...')}`);
1217
+
1218
+ // Check if upstream remote exists
1219
+ const upstreamCheckResult = await $({ cwd: tempDir })`git remote get-url upstream 2>/dev/null`;
1220
+ if (upstreamCheckResult.code === 0) {
1221
+ // Fetch from upstream to ensure we have the latest branches
1222
+ await log(`${formatAligned('đŸ“Ĩ', 'Fetching from upstream:', 'Looking for PR branch...')}`);
1223
+ const fetchUpstreamResult = await $({ cwd: tempDir })`git fetch upstream`;
1224
+
1225
+ if (fetchUpstreamResult.code === 0) {
1226
+ // Check if branch exists in upstream
1227
+ const upstreamBranchCheckResult = await $({ cwd: tempDir })`git show-ref --verify --quiet refs/remotes/upstream/${branchName}`;
1228
+
1229
+ if (upstreamBranchCheckResult.code === 0) {
1230
+ await log(`${formatAligned('✅', 'Found branch in upstream:', `upstream/${branchName}`)}`);
1231
+ // Try to checkout from upstream instead
1232
+ checkoutResult = await $({ cwd: tempDir })`git checkout -b ${branchName} upstream/${branchName}`;
1233
+
1234
+ if (checkoutResult.code === 0) {
1235
+ await log(`${formatAligned('â„šī¸', 'Note:', 'PR branch was in upstream repository, not your fork')}`);
1236
+ await log(`${formatAligned('', '', 'This can happen when a bot creates a PR directly in the main repository')}`);
1237
+ }
1238
+ } else {
1239
+ await log(`${formatAligned('âš ī¸', 'Branch not found:', `Not in origin or upstream remotes`)}`, { level: 'warning' });
1240
+ }
1241
+ } else {
1242
+ await log(`${formatAligned('âš ī¸', 'Warning:', 'Failed to fetch from upstream')}`, { level: 'warning' });
1243
+ }
1244
+ }
1245
+ }
1211
1246
  }
1212
1247
 
1213
1248
  return checkoutResult;
@@ -15,6 +15,10 @@ const use = globalThis.use;
15
15
  // Use command-stream for consistent $ behavior across runtimes
16
16
  const { $ } = await use('command-stream');
17
17
 
18
+ // Import path and fs for cleanup operations
19
+ const path = (await use('path')).default;
20
+ const fs = (await use('fs')).promises;
21
+
18
22
  // Import shared library functions
19
23
  const lib = await import('./lib.mjs');
20
24
  const { log, cleanErrorMessage, formatAligned } = lib;
@@ -50,10 +54,36 @@ const checkPRMerged = async (owner, repo, prNumber) => {
50
54
  return false;
51
55
  };
52
56
 
57
+ /**
58
+ * Clean up .playwright-mcp/ folder to prevent browser automation artifacts
59
+ * from triggering auto-restart (Issue #1124)
60
+ */
61
+ const cleanupPlaywrightMcpFolder = async (tempDir, argv) => {
62
+ if (argv.playwrightMcpAutoCleanup !== false) {
63
+ const playwrightMcpDir = path.join(tempDir, '.playwright-mcp');
64
+ try {
65
+ const playwrightMcpExists = await fs
66
+ .stat(playwrightMcpDir)
67
+ .then(() => true)
68
+ .catch(() => false);
69
+ if (playwrightMcpExists) {
70
+ await fs.rm(playwrightMcpDir, { recursive: true, force: true });
71
+ await log('🧹 Cleaned up .playwright-mcp/ folder (browser automation artifacts)', { verbose: true });
72
+ }
73
+ } catch (cleanupError) {
74
+ // Non-critical error, just log and continue
75
+ await log(`âš ī¸ Could not clean up .playwright-mcp/ folder: ${cleanupError.message}`, { verbose: true });
76
+ }
77
+ }
78
+ };
79
+
53
80
  /**
54
81
  * Check if there are uncommitted changes in the repository
55
82
  */
56
- const checkForUncommittedChanges = async (tempDir, $) => {
83
+ const checkForUncommittedChanges = async (tempDir, $, argv = {}) => {
84
+ // First, clean up .playwright-mcp/ folder to prevent false positives (Issue #1124)
85
+ await cleanupPlaywrightMcpFolder(tempDir, argv);
86
+
57
87
  try {
58
88
  const gitStatusResult = await $({ cwd: tempDir })`git status --porcelain 2>&1`;
59
89
  if (gitStatusResult.code === 0) {
@@ -130,7 +160,7 @@ export const watchForFeedback = async params => {
130
160
 
131
161
  // In temporary watch mode, check if all changes have been committed
132
162
  if (isTemporaryWatch && !firstIterationInTemporaryMode) {
133
- const hasUncommitted = await checkForUncommittedChanges(tempDir, $);
163
+ const hasUncommitted = await checkForUncommittedChanges(tempDir, $, argv);
134
164
  if (!hasUncommitted) {
135
165
  await log('');
136
166
  await log(formatAligned('✅', 'CHANGES COMMITTED!', 'Exiting auto-restart mode'));
@@ -185,7 +215,7 @@ export const watchForFeedback = async params => {
185
215
  // In temporary watch mode, also check for uncommitted changes as a restart trigger
186
216
  let hasUncommittedInTempMode = false;
187
217
  if (isTemporaryWatch && !firstIterationInTemporaryMode) {
188
- hasUncommittedInTempMode = await checkForUncommittedChanges(tempDir, $);
218
+ hasUncommittedInTempMode = await checkForUncommittedChanges(tempDir, $, argv);
189
219
  }
190
220
 
191
221
  const shouldRestart = hasFeedback || firstIterationInTemporaryMode || hasUncommittedInTempMode;