fix(executor): worktree 软链主仓 node_modules,修 verify exit 127 误判

git worktree 是干净检出不带 gitignored 的 node_modules,导致 verify
(tsc/tsx 等)与执行 agent 跑 npm 脚本时 command not found(exit 127)被误判为失败。
建 worktree 后软链主仓 node_modules:零网络、同平台 native 兼容、被 gitignore 忽略不污染 diff;
非 Node 项目(无 node_modules)自动跳过。removeWorktree 只移除软链不动主仓依赖。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-13 09:01:47 +08:00
parent bb6186902a
commit 9bd02f2479
2 changed files with 62 additions and 2 deletions
+29 -1
View File
@@ -1,7 +1,7 @@
import { execFile } from 'node:child_process';
import { homedir } from 'node:os';
import { basename, dirname, join } from 'node:path';
import { mkdirSync, rmSync } from 'node:fs';
import { existsSync, lstatSync, mkdirSync, rmSync, symlinkSync } from 'node:fs';
const GIT_TIMEOUT_MS = 60_000;
const MAX_BUFFER = 16 * 1024 * 1024;
@@ -60,9 +60,37 @@ export async function createWorktree(repoPath: string, taskId: string, baseBranc
mkdirSync(dirname(dir), { recursive: true });
await git(repoPath, ['worktree', 'add', '-b', branch, dir, baseBranch]);
linkNodeModules(repoPath, dir);
return { dir, branch };
}
/**
* 把主仓的 node_modules 软链进 worktree。
* git worktree 是干净检出,不带 gitignored 的 node_modules,导致 verifytsc/tsx/vitest 等)
* 与执行 agent 跑 npm 脚本时 `command not found`exit 127)。软链复用主仓依赖:
* 零网络、同平台 native 模块兼容、被 .gitignore 忽略不污染 diff。主仓无 node_modules 则跳过(非 Node 项目)。
*/
function linkNodeModules(repoPath: string, dir: string): void {
const src = join(repoPath, 'node_modules');
if (!existsSync(src)) return;
const dest = join(dir, 'node_modules');
if (existsSync(dest) || isBrokenSymlink(dest)) return; // 项目自己提交了 node_modules / 残留软链,别覆盖
try {
symlinkSync(src, dest, 'dir');
} catch {
// 软链失败(权限/平台)不阻断执行——verify 仍可能因缺依赖失败,但那是显式可见的,不在这里吞
}
}
/** 目标是「指向已失效路径的软链」——existsSync 对断链返回 false,需 lstat 兜底判断 */
function isBrokenSymlink(p: string): boolean {
try {
return lstatSync(p).isSymbolicLink();
} catch {
return false;
}
}
/** 分支相对 baseBranch 的改动:diff --stat 摘要 + commit 列表(新→旧,"<短hash> <标题>" */
export async function worktreeDiff(
repoPath: string,