@minonl/dia 0.0.2 → 0.0.5

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.
@@ -461,7 +461,7 @@ cmd_prune_merged() {
461
461
  local -a roots=()
462
462
  [ -n "$dr" ] && [ -d "$dr/repos" ] && roots+=("$dr/repos")
463
463
  [ -d "$rr" ] && roots+=("$rr")
464
- [ ${#roots[@]} -gt 0 ] || die "--all:未找到仓根(数据根 repos/ 与 ${rr})"
464
+ [ ${#roots[@]} -gt 0 ] || { echo "usage: dia wt adopt --all(未找到仓根:${rr})" >&2; exit 2; }
465
465
  local root sub
466
466
  for root in "${roots[@]}"; do
467
467
  for sub in "$root"/*; do
@@ -779,7 +779,7 @@ cmd_check() {
779
779
  rr="${DIA_WT_REPOS_ROOT:-$HOME/repos/leyan}"
780
780
  [ -n "$dr" ] && [ -d "$dr/repos" ] && roots+=("$dr/repos")
781
781
  [ -d "$rr" ] && roots+=("$rr")
782
- [ ${#roots[@]} -gt 0 ] || die "--all:未找到仓根(数据根 repos/ 与 ${rr})"
782
+ [ ${#roots[@]} -gt 0 ] || { echo "usage: dia wt adopt --all(未找到仓根:${rr})" >&2; exit 2; }
783
783
  local root sub
784
784
  for root in "${roots[@]}"; do
785
785
  for sub in "$root"/*; do
@@ -939,7 +939,7 @@ cmd_clean() {
939
939
  }
940
940
 
941
941
  # ═══════════════════════════════════════════════════════════════════════════
942
- # route —— 【开工前必查】这次改动该走 MR 还是直推 main?(R1.7)
942
+ # route —— 【开工前必查】合并通道是哪一种?(R1.7:所有仓都走 MR,仅合并通道不同)
943
943
  #
944
944
  # 为什么需要(2026-10-12 复发教训):R20 管「别污染 main checkout」(隔离);R1.7 管「要不要评审」——
945
945
  # **两件事独立**。agent 做对了 A(隔离),却错误推导出 B(要 MR):在内部仓走 MR 全套打扰同事;
@@ -948,13 +948,13 @@ cmd_clean() {
948
948
  # 为什么改成**配置驱动**(2026-10-16 用户指正):
949
949
  # 原先把仓名硬编码在本脚本里(`yan-agent|personal-agent` 内部仓、`example-repo|*-qa|…` 业务仓)⇒
950
950
  # ① 「哪些仓是我的/业务仓」是**环境事实**,换个人就全错;② 它含「**我是谁**」成分 ——
951
- # `fed/yan-agent` 对本人是自持(直推)、对同事是别人的仓(该 MR)⇒ **同一仓在不同人机器上结论不同**;
951
+ # 仓归属因人而异(本人的自持仓 / 别人的仓)⇒ **同一仓在不同人机器上合并通道不同**;
952
952
  # ③ 每加一个仓都要改共享配方库 ⇒ 漂移源。⇒ **规则留在本脚本,数据只落本地**。
953
953
  #
954
954
  # 判定顺序(**每步都记录来源**、输出可审计;先否决后放行):
955
955
  # ① 本地配置 `mr` 名单命中 → 走 MR(显式否决,优先级最高)
956
- # ② 本地配置 `direct` 名单命中 → 直推
957
- # ③ remote 命名空间 ∈ `my_namespaces` → 直推("这是我的仓" = 可推导事实,不靠猜仓名)
956
+ # ② 本地配置 `direct` 名单命中 → 个人仓:MR + owner 自合
957
+ # ③ remote 命名空间 ∈ `my_namespaces` → 个人仓:MR + owner 自合("这是我的仓" = 可推导事实)
958
958
  # ④ 其余 / 信息不足 → **走 MR**(安全侧兜底)
959
959
  #
960
960
  # 配置读取(**数据根为准**,可与个人仓基线合并;三个名单键取**并集**:本地只能增、不能删):
@@ -966,7 +966,7 @@ cmd_clean() {
966
966
  # 只在输出里给「推测 + 如何确认」,由人核对(glab auth status)后写进配置。
967
967
  #
968
968
  # 用法:dia wt route <repo-path>
969
- # 退出码:0 = 直推;1 = 走 MR(含默认兜底);2 = 信息不足(按 MR 处理,需补配置/remote)
969
+ # 退出码:0 = MR + owner 自合(个人仓);1 = MR + LGTM + bot(团队仓);2 = 信息不足(按团队仓处理)
970
970
  # ═══════════════════════════════════════════════════════════════════════════
971
971
 
972
972
  # ── remote 命名空间:`<host>/<ns>/<repo>` 里的 `<ns>`(无 remote → 空)────────
@@ -1065,46 +1065,45 @@ case "$name" in *-worktree--*) name="${name%%-worktree--*}"; repo="$(dirname "$r
1065
1065
  verdict=mr; why="命名空间「${ns}」不在 my_namespaces(安全侧默认 MR)"
1066
1066
  fi
1067
1067
 
1068
- echo "── 仓库路由判定(R1.7 · 配置驱动,不再硬编码仓名)──"
1068
+ echo "── 仓库路由判定(R1.7 · 配置驱动:所有仓走 MR,仅合并通道不同)──"
1069
1069
  echo " 仓库 : $name${ns:+(命名空间 ${ns})}"
1070
1070
  printf ' 判定来源 : %s\n' "$(printf '%s' "$cfg" | node -e 'let s="";process.stdin.on("data",d=>{s+=d}).on("end",()=>{const d=JSON.parse(s);const srcs=(d.sources&&d.sources.length)?d.sources:["(无配置文件)"];console.log(srcs.join("、")+(d.errors.length?" ⚠️ "+d.errors.join(";"):""))})')"
1071
1071
 
1072
1072
  case "$verdict" in
1073
- direct)
1073
+ direct|personal)
1074
1074
  cat <<EOS
1075
- 结论 : ✅ **直推 main**(不建 MR)|依据:$why
1075
+ 结论 : ✅ **MR + owner 自合**(个人仓)|依据:$why
1076
1076
  正确姿势:
1077
- git worktree add <平级路径> -b <branch> origin/main
1078
- # ... 开发 + 自测 ...
1079
- git push origin HEAD:main # 快进推 main,不建 MR
1080
- ⚠️ 「worktree 隔离」≠「要评审」——两者独立:
1081
- · worktree = 别污染 main checkout(R20)
1082
- · 免 MR = 我自持的仓、自己主导,review 为辅(R1.7)
1083
- ⚠️ yan-agent main 拒绝 force push(pre-receive hook)→ 先 rebase 成快进
1077
+ git worktree add <平级路径> -b feat/<feature> origin/main
1078
+ # ... 开发 + 自测 + rebase(落后必须为 0)...
1079
+ git push origin HEAD:feat/<feature>
1080
+ glab mr create ... --source-branch feat/<feature> --target-branch main --yes
1081
+ # pipeline 绿 → owner 直接合(无需 LGTM、不发 @bot mergeme)
1082
+ glab mr merge <iid> --yes --remove-source-branch
1083
+ 提示 : 一律走 MR(2026-11-05 起没有「直推 main」);owner 自合省的是 LGTM,不是留痕
1084
1084
  EOS
1085
1085
  exit 0 ;;
1086
1086
  mr)
1087
1087
  cat <<EOS
1088
- 结论 : ✅ **走 MR**(+ LGTM + @bot mergeme)|依据:$why
1088
+ 结论 : ✅ **MR + LGTM + @bot mergeme**(组织/团队仓)|依据:$why
1089
1089
  正确姿势:
1090
- git worktree add <平级路径> -b <分支前缀>/<feat> origin/main
1090
+ git worktree add <平级路径> -b feat/<feature> origin/main
1091
1091
  # ... 开发 + 自测 + rebase(落后必须为 0)...
1092
- glab mr create ... && glab mr update <iid> --squash-before-merge
1093
- # 等 LGTM → @bot mergeme
1094
- 提示 : example-repo 的 docs/** 纯文档改动,用户说「直推」时可直接特权合并(R1.7 常态授权)
1092
+ glab mr create ... --source-branch feat/<feature> --target-branch main --yes
1093
+ # 等 LGTM → @bot mergeme(yan-mr wait 可一键盯完)
1095
1094
  EOS
1096
1095
  exit 1 ;;
1097
1096
  *)
1098
1097
  cat <<EOS
1099
- 结论 : ⚠️ **信息不足,按 MR 处理**(安全侧)|依据:$why
1098
+ 结论 : ⚠️ **信息不足,按「MR + LGTM + bot」处理**(安全侧)|依据:$why
1100
1099
  补齐判定(任选):
1101
1100
  1) 让仓有 origin remote ⇒ 命名空间可推导;
1102
1101
  2) 写配置(数据根为主入口,可与个人仓基线合并;名单取并集):
1103
1102
  \$YAN_DATA_ROOT/route.json ← 本机;**可以是软链**(指向个人仓版本化文件)
1104
1103
  \$PERSONAL_AGENT_DIR/config/yan-route.json ← 个人仓基线(跨设备)
1105
1104
  { "my_namespaces": ["<你的 gitlab 账号>"], "direct": ["<我自持的仓名>"], "mr": [] }
1105
+ (`direct` 现表示「个人仓 → owner 自合」;路径已不再直推)
1106
1106
  EOS
1107
- # 未配置命名空间时给「推测 + 如何确认」,但**绝不自动采用**(不瞎猜"我是谁")
1108
1107
  if [ -n "$ns" ] && [ -z "$_cfg_ns" ]; then
1109
1108
  local guess; guess="$(git -C "$repo" config --get user.email 2>/dev/null | sed 's/@.*//' || true)"
1110
1109
  printf ' 💡 推测(**未采用**):origin 命名空间 =「%s」;%s\n' "$ns" "${guess:+你的 git user.email 前缀 =「${guess}」}"
@@ -1161,7 +1160,6 @@ prot=" main master develop dev "
1161
1160
  probe="${DIA_WT_GUARD_PROBE:-0}"
1162
1161
  wt_guard_scan() {
1163
1162
  local ls="$1" rs="$2" range="" base="" hits=""
1164
- [ "${DIA_WT_GUARD_SCAN:-1}" = "1" ] || return 0
1165
1163
  if [ -n "$rs" ] && [ "$rs" != "0000000000000000000000000000000000000000" ] && git cat-file -e "$rs" 2>/dev/null; then
1166
1164
  range="$rs..$ls"
1167
1165
  else
@@ -1170,11 +1168,11 @@ else
1170
1168
  fi
1171
1169
  hits="$(git diff -U0 "$range" -- . 2>/dev/null | grep -E '^\+' | grep -vE '^\+\+\+' \
1172
1170
  | grep -vE '/(Users|home)/(you|user|example|runner|shared|me)(/|$)' \
1173
- | grep -nE '(/Users/[A-Za-z0-9._-]+|/home/[A-Za-z0-9._-]+|-----BEGIN [A-Z ]*PRIVATE KEY-----|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})' | head -5)"
1171
+ | grep -nE '(^|[+ "'\''`(=])/(Users|home)/[A-Za-z0-9._-]+|-----BEGIN [A-Z ]*PRIVATE KEY-----|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}' | head -5)"
1174
1172
  if [ -n "$hits" ]; then
1175
1173
  echo "[dia-wt] 推送包含敏感内容(已阻断):" >&2
1176
1174
  printf '%s\n' "$hits" >&2
1177
- echo "[dia-wt] 确认误报可临时关闭:DIA_WT_GUARD_SCAN=0 git push ..." >&2
1175
+ echo "[dia-wt] 误报请修扫描规则(hooks 由 dia wt adopt 生成),不提供关闭开关" >&2
1178
1176
  return 1
1179
1177
  fi
1180
1178
  }
@@ -1243,8 +1241,10 @@ PYGT
1243
1241
  }
1244
1242
 
1245
1243
  cmd_adopt() {
1246
- local repo="" mode="dry" template="" def="" json=0
1244
+ local repo="" mode="dry" template="" def="" json=0 all=0
1245
+ local -a orig=("$@")
1247
1246
  while [ $# -gt 0 ]; do case "$1" in
1247
+ --all) all=1; shift ;;
1248
1248
  --apply) mode="apply"; shift ;;
1249
1249
  --check) mode="check"; shift ;;
1250
1250
  --revert) mode="revert"; shift ;;
@@ -1257,7 +1257,41 @@ cmd_adopt() {
1257
1257
  -*) die "未知参数:$1" ;;
1258
1258
  *) repo="$1"; shift ;;
1259
1259
  esac; done
1260
- [ -n "$repo" ] || die "用法: dia wt adopt <repo> [--apply|--check|--revert] [--template T] [--default-branch B] [--json]"
1260
+ if [ "$all" = 1 ]; then
1261
+ [ -z "$repo" ] || die "--all 不能与 <repo> 同时给(要么单个仓,要么全量)"
1262
+ local -a roots=() repos=()
1263
+ local dr rr
1264
+ dr="$(dia_wt_data_root 2>/dev/null || true)"
1265
+ rr="${DIA_WT_REPOS_ROOT:-$HOME/repos}"
1266
+ [ -n "$dr" ] && [ -d "$dr/repos" ] && roots+=("$dr/repos")
1267
+ [ -d "$rr" ] && roots+=("$rr")
1268
+ [ ${#roots[@]} -gt 0 ] || { echo "usage: dia wt adopt --all(未找到仓根:${rr})" >&2; exit 2; }
1269
+ local root sub gd cd_
1270
+ for root in "${roots[@]}"; do
1271
+ while IFS= read -r -d '' gd; do
1272
+ sub="$(dirname "$gd")"
1273
+ case "$(basename "$sub")" in *-worktree--*) continue ;; esac
1274
+ cd_="$(git -C "$sub" rev-parse --git-common-dir 2>/dev/null || true)"
1275
+ # 统一物理路径(macOS /tmp → /private/tmp),否则等式比较会假失败
1276
+ gd="$(cd "$sub" 2>/dev/null && cd "$gd" 2>/dev/null && pwd -P)"
1277
+ cd_="$(cd "$sub" 2>/dev/null && cd "$cd_" 2>/dev/null && pwd -P)"
1278
+ [ -n "$gd" ] && [ "$gd" = "$cd_" ] || continue # 只处理主 checkout
1279
+ repos+=("$sub")
1280
+ done < <(find "$root" -maxdepth 3 -name .git -type d -print0 2>/dev/null)
1281
+ done
1282
+ [ ${#repos[@]} -gt 0 ] || { echo "usage: dia wt adopt --all(未匹配到任何主 checkout)" >&2; exit 2; }
1283
+ local -a pass=()
1284
+ local a
1285
+ for a in "${orig[@]}"; do [ "$a" = "--all" ] && continue; pass+=("$a"); done
1286
+ local okc=0 badc=0 r
1287
+ for r in "${repos[@]}"; do
1288
+ if cmd_adopt "$r" "${pass[@]}"; then okc=$((okc+1)); else badc=$((badc+1)); echo "FAIL $r" >&2; fi
1289
+ done
1290
+ [ "$json" = 1 ] && python3 -c 'import json,sys;print(json.dumps({"repos":int(sys.argv[1]),"ok":int(sys.argv[2]),"fail":int(sys.argv[3])},separators=(",",":")))' "${#repos[@]}" "$okc" "$badc"
1291
+ [ "$badc" = 0 ] || return 1
1292
+ return 0
1293
+ fi
1294
+ [ -n "$repo" ] || die "用法: dia wt adopt <repo>|--all [--apply|--check|--revert] [--template T] [--default-branch B] [--json]"
1261
1295
  [ -d "$repo" ] || die "目录不存在:$repo"
1262
1296
  git -C "$repo" rev-parse --git-dir >/dev/null 2>&1 || die "不是 git 仓库:$repo"
1263
1297
 
@@ -1343,6 +1377,11 @@ data = {
1343
1377
  }
1344
1378
  json.dump(data, open(cfg, "w"), ensure_ascii=False, indent=1)
1345
1379
  PY
1380
+ # 提示:simple-git-hooks / husky 的 postinstall 会重写 core.hooksPath 下的钩子,
1381
+ # 每次 `pnpm/npm install` 后可能把守卫冲掉 → 用 `dia wt doctor --fix` 或重跑 adopt --apply 修复。
1382
+ if grep -qs '"simple-git-hooks"\|"husky"\|lefthook' "$repo/package.json" "$repo/.lefthook.yml" 2>/dev/null; then
1383
+ echo "[dia-wt] 注意:该仓使用 hook manager(simple-git-hooks/husky/lefthook),install 后若守卫被重写,跑 dia wt doctor --fix" >&2
1384
+ fi
1346
1385
  wt_write_hook "$hooksdir" pre-commit "$def" "$chain"
1347
1386
  wt_write_hook "$hooksdir" pre-push "$def" "$chain"
1348
1387
  wt_write_hook "$hooksdir" post-checkout "$def" "$chain"
@@ -1351,11 +1390,12 @@ PY
1351
1390
  }
1352
1391
 
1353
1392
  cmd_doctor() {
1354
- local json=0 all=0 repos=()
1393
+ local json=0 all=0 fix=0 repos=()
1355
1394
  while [ $# -gt 0 ]; do case "$1" in
1356
1395
  --json) json=1; shift ;;
1357
1396
  --all) all=1; shift ;;
1358
- --strict|--fix) shift ;;
1397
+ --fix) fix=1; shift ;;
1398
+ --strict) shift ;;
1359
1399
  -*) die "未知参数:$1" ;;
1360
1400
  *) repos+=("$1"); shift ;;
1361
1401
  esac; done
@@ -1378,6 +1418,19 @@ cmd_doctor() {
1378
1418
  def="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("defaultBranch") or "")' "$cfg" 2>/dev/null || true)"
1379
1419
  fi
1380
1420
  [ -n "$def" ] || def="$(wt_default_branch "$repo")"
1421
+ # --fix:无损修复(重写三个守卫钩子 + hooksPath 绝对路径)。只对已 adopt 的仓生效。
1422
+ if [ "$fix" = 1 ] && [ -f "$cfg" ]; then
1423
+ local fixChain=""
1424
+ fixChain="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksChain") or "")' "$cfg" 2>/dev/null || true)"
1425
+ [ -n "$fixChain" ] || fixChain="$common/hooks"
1426
+ mkdir -p "$hooksdir"
1427
+ wt_write_hook "$hooksdir" pre-commit "$def" "$fixChain"
1428
+ wt_write_hook "$hooksdir" pre-push "$def" "$fixChain"
1429
+ wt_write_hook "$hooksdir" post-checkout "$def" "$fixChain"
1430
+ [ "$prev" = "$hooksdir" ] || git -C "$repo" config --local core.hooksPath "$hooksdir"
1431
+ prev="$hooksdir"
1432
+ [ "$json" = 0 ] && echo "FIX $repo 钩子已重装(chain=$fixChain)"
1433
+ fi
1381
1434
  local bad=0
1382
1435
  [ -f "$cfg" ] || { echo "FAIL D01 $repo 未 adopt"; bad=1; }
1383
1436
  case "$prev" in /*) ;; *) echo "FAIL D02 $repo hooksPath 非绝对($prev)"; bad=1 ;; esac
@@ -1462,12 +1515,13 @@ dia wt —— worktree 目录形式选型 + 创建(唯一实现)
1462
1515
  dia wt status [repo-path] # ★ 清理前必查:唯一工作副本 + 改动重叠(只读)
1463
1516
  dia wt check [<repo>...] [--all] [--deep] [--quiet]
1464
1517
  # ★ 依赖体检(R20):软链/逃逸/悬空;--all 扫全部仓
1465
- dia wt adopt <repo> [--apply|--check|--revert] [--template T] [--default-branch B] [--json]
1466
- # 配置路径模板 + 守卫钩子(默认 dry-run)
1467
- # 守卫含推送前敏感扫描(DIA_WT_GUARD_SCAN=0 可临时关闭)
1468
- dia wt doctor [<repo>...] [--all] [--json] # 巡检:配置/钩子/探针/主 checkout/worktree 卫生
1518
+ dia wt adopt <repo>|--all [--apply|--check|--revert] [--template T] [--default-branch B] [--json]
1519
+ # 配置路径模板 + 守卫钩子(默认 dry-run;--all 扫仓根)
1520
+ # 守卫含推送前敏感扫描(不提供关闭开关;误报修规则)
1521
+ dia wt doctor [<repo>...] [--all] [--json] [--fix]
1522
+ # 巡检:配置/钩子/探针/主 checkout/worktree 卫生;--fix 重装守卫钩子
1469
1523
  dia wt gate <repo> <feature> # 提交 MR 前:check + doctor + 干净 + ahead>0
1470
- dia wt route <repo-path> # ★ 开工前必查:走 MR 还是直推(R1.7,配置驱动)
1524
+ dia wt route <repo-path> # ★ 开工前必查:合并通道(owner 自合 / LGTM+bot;一律走 MR)
1471
1525
  dia wt scope <repo|worktree> [--feature <slug>] [--session <id>] # ★ 产出作用域声明(设计 §4.5)
1472
1526
  dia wt scope --show # 看与当前目录匹配的作用域声明
1473
1527
  dia wt scope --list # 列出全部作用域文件(按 worktree 分文件)
@@ -38,14 +38,20 @@ git -C "$R" reset -q
38
38
  # ── 推送前敏感扫描(PII/凭据形态)──
39
39
  BR="$TD/remote.git"; git init -q --bare "$BR"
40
40
  git -C "$R" remote add origin "$BR" 2>/dev/null || true
41
- git -C "$R" push -q --no-verify -u origin main 2>/dev/null || true
41
+ git --git-dir="$BR" fetch -q "$R" main:main 2>/dev/null || true
42
+ git -C "$R" fetch -q origin 2>/dev/null || true
42
43
  git -C "$R" worktree add -q "$TD/wt" -b feat/scan 2>/dev/null || true
43
- echo "/Users/alice/secret" > "$TD/wt/bad.txt"
44
- git -C "$TD/wt" add -A && git -C "$TD/wt" commit -qm bad --no-verify
44
+ BAD_HOME="/Users"; echo "$BAD_HOME/alice/secret" > "$TD/wt/bad.txt"
45
+ git -C "$TD/wt" add -A && git -C "$TD/wt" commit -qm bad
45
46
  if git -C "$TD/wt" push -q origin feat/scan 2>/dev/null; then bad "敏感推送未被拦截"; else ok "敏感推送被拦截"; fi
46
- echo ok > "$TD/wt/bad.txt" && git -C "$TD/wt" add -A && git -C "$TD/wt" commit -qm fix --no-verify
47
+ echo ok > "$TD/wt/bad.txt" && git -C "$TD/wt" add -A && git -C "$TD/wt" commit -qm fix
47
48
  git -C "$TD/wt" push -q origin feat/scan 2>/dev/null && ok "干净推送放行" || bad "干净推送被拒"
48
49
 
50
+ # 误报回归:`$TD/home/kits/...` 这种相对片段不该被判成 /home/<user>
51
+ printf 'path=$TD/home/kits/dia-msg/dia.json\n' > "$TD/wt/vars.txt"
52
+ git -C "$TD/wt" add -A && git -C "$TD/wt" commit -qm vars
53
+ git -C "$TD/wt" push -q origin feat/scan 2>/dev/null && ok "相对片段 home/ 不误报" || bad "相对片段被误判为敏感路径"
54
+
49
55
  bash "$WT" adopt "$R" --revert >/dev/null 2>&1 || bad "revert 失败"
50
56
  [ -z "$(git -C "$R" config --get core.hooksPath || true)" ] && ok "revert 还原 hooksPath" || bad "revert 未还原"
51
57
  [ ! -d "$R/.git/dia" ] && ok "revert 删除 dia 目录" || bad "dia 目录残留"
@@ -140,6 +146,35 @@ git -C "$TD/repo7-worktree--y" commit -qm feat-y >/dev/null 2>&1
140
146
  bash "$WT" adopt "$R7" --revert >/dev/null 2>&1
141
147
  [ "$(git -C "$R7" config --get core.hooksPath)" = ".githooks" ] && ok "revert 还原原 hooksPath(相对)" || bad "revert 未还原 .githooks"
142
148
 
149
+ # ── adopt --all:批量主 checkout,跳过 worktree 目录 ─────────────────────────
150
+ AR="$TD/allroot"; mkdir -p "$AR/a" "$AR/b"
151
+ for r in a b; do
152
+ git -C "$AR/$r" init -q -b main
153
+ git -C "$AR/$r" config user.email t@example.com; git -C "$AR/$r" config user.name t
154
+ echo x > "$AR/$r/x.txt"; git -C "$AR/$r" add -A; git -C "$AR/$r" commit -qm init --no-verify
155
+ done
156
+ git -C "$AR/a" worktree add -q -b feat/w "$AR/a-worktree--w" main
157
+ out="$(DIA_WT_REPOS_ROOT="$AR" DIA_WT_DATA_ROOT="$TD/dr2" bash "$WT" adopt --all --apply --json 2>&1)"
158
+ printf '%s' "$out" | grep -q '"repos":2,"ok":2,"fail":0' && ok "adopt --all 覆盖 2 个主 checkout" || bad "adopt --all: $out"
159
+ for r in a b; do
160
+ [ -n "$(git -C "$AR/$r" config --get core.hooksPath || true)" ] && ok "adopt --all: $r 已配 hooksPath" || bad "adopt --all: $r 未配置"
161
+ done
162
+ [ ! -d "$AR/a-worktree--w/.git/dia" ] && ok "adopt --all 跳过 worktree 目录" || bad "adopt --all 误改 worktree"
163
+ DIA_WT_REPOS_ROOT="$TD/empty-root" DIA_WT_DATA_ROOT="$TD/dr3" bash "$WT" adopt --all >/dev/null 2>&1
164
+ [ $? = 2 ] && ok "adopt --all 空结果 exit 2" || bad "adopt --all 空结果未 exit 2"
165
+
166
+ # ── doctor --fix:钩子被覆盖后能无损重建 ────────────────────────────────────
167
+ R8="$TD/repo8"; mkdir -p "$R8"; git -C "$R8" init -q -b main
168
+ git -C "$R8" config user.email t@example.com; git -C "$R8" config user.name t
169
+ echo a > "$R8/a.txt"; git -C "$R8" add -A; git -C "$R8" commit -qm init --no-verify
170
+ bash "$WT" adopt "$R8" --apply >/dev/null 2>&1
171
+ rm -f "$R8/.git/dia/hooks/post-checkout"
172
+ printf '#!/bin/sh\nexit 0\n' > "$R8/.git/dia/hooks/pre-commit"; chmod +x "$R8/.git/dia/hooks/pre-commit"
173
+ if bash "$WT" doctor "$R8" >/dev/null 2>&1; then bad "钩子被覆盖后 doctor 仍通过"; else ok "钩子被覆盖后 doctor 报失败"; fi
174
+ bash "$WT" doctor "$R8" --fix >/dev/null 2>&1 || bad "doctor --fix 失败"
175
+ bash "$WT" doctor "$R8" >/dev/null 2>&1 && ok "doctor --fix 后恢复 ok" || bad "doctor --fix 后仍失败"
176
+ [ -x "$R8/.git/dia/hooks/post-checkout" ] && ok "doctor --fix 补回 post-checkout" || bad "post-checkout 未补回"
177
+
143
178
  # ── gate:成功 / 失败(P1-3 精确匹配)────────────────────────────────────────
144
179
  bash "$WT" gate "$R5" x >/dev/null 2>&1 && ok "gate 成功" || bad "gate 失败"
145
180
  if bash "$WT" gate "$R5" xb >/dev/null 2>&1; then bad "gate 子串误匹配(x 命中 xb)"; else ok "gate 精确匹配 feature"; fi
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # 覆盖(**为什么是这些**):
5
5
  # · 无配置时**绝不放行**:即便命名空间看着像我的,也只能走 MR(不瞎猜"我是谁")
6
- # · 命名空间推导:配置了 my_namespaces 后,我的仓自动直推
6
+ # · 命名空间推导:配置了 my_namespaces 后,我的仓 → MR + owner 自合(rc 0)
7
7
  # · 显式名单:direct 放行 / mr **否决优先**
8
8
  # · 个人仓基线 + 数据根本地**并集合并**;数据根配置**可以是软链**
9
9
  # · 空列表**不误命中**(`grep -qx ""` 匹配空行是这类脚本的经典假阳性)
@@ -45,13 +45,13 @@ echo "== 2. 无配置 + 命名空间看着像我的 ⇒ **仍 MR**(不瞎猜
45
45
  [ "$(run "$MINE")" = 1 ] && ok "退出码 1(未因命名空间相似而放行)" || { bad "应 1:不能瞎猜"; out; }
46
46
  see "不在 my_namespaces" && ok "说明未配置" || { bad "缺说明"; out; }
47
47
 
48
- echo "== 3. 配置 my_namespaces ⇒ 我的仓自动直推 =="
48
+ echo "== 3. 配置 my_namespaces ⇒ 我的仓 = MR + owner 自合 =="
49
49
  setcfg "$CFG" '{ "my_namespaces": ["example-user"] }'
50
50
  [ "$(run "$MINE")" = 0 ] && ok "退出码 0" || { bad "应 0"; out; }
51
51
  see "∈ my_namespaces" && ok "判定来源 = 命名空间" || { bad "来源不对"; out; }
52
52
  [ "$(run "$TEAM")" = 1 ] && ok "非我命名空间仍 MR" || { bad "应 1"; out; }
53
53
 
54
- echo "== 4. direct 名单 ⇒ 直推(覆盖默认)=="
54
+ echo "== 4. direct 名单 ⇒ owner 自合(覆盖默认)=="
55
55
  setcfg "$CFG" '{ "my_namespaces": ["example-user"], "direct": ["teamrepo"] }'
56
56
  [ "$(run "$TEAM")" = 0 ] && ok "退出码 0" || { bad "应 0"; out; }
57
57
  see "配置 direct 名单命中" && ok "判定来源 = 配置" || { bad "来源不对"; out; }
@@ -64,9 +64,9 @@ see "显式否决" && ok "给出否决理由" || { bad "缺理由"; out; }
64
64
  echo "== 6. 个人仓基线 + 数据根 ⇒ 并集合并(判定来源列出两个文件)=="
65
65
  setcfg "$CFG" '{ "direct": ["teamrepo"] }'
66
66
  setcfg "$BASE" '{ "my_namespaces": ["example-user"], "direct": ["myrepo"] }'
67
- [ "$(run "$TEAM")" = 0 ] && ok "本地新增生效(teamrepo 直推)" || { bad "本地配置未生效"; out; }
67
+ [ "$(run "$TEAM")" = 0 ] && ok "本地新增生效(teamrepo = owner 自合)" || { bad "本地配置未生效"; out; }
68
68
  see "route.json" && ok "来源含数据根文件" || { bad "缺来源"; out; }
69
- [ "$(run "$MINE")" = 0 ] && ok "基线条目保留(myrepo 直推)" || { bad "基线未生效"; out; }
69
+ [ "$(run "$MINE")" = 0 ] && ok "基线条目保留(myrepo = owner 自合)" || { bad "基线未生效"; out; }
70
70
  see "yan-route.json" && ok "来源含基线文件" || { bad "缺基线来源"; out; }
71
71
  [ "$(run "$NOREMOTE")" = 2 ] && ok "无 remote 的仓:信息不足(rc=2)" || { bad "应 2"; out; }
72
72
 
@@ -89,16 +89,16 @@ echo "== 10. \$DIA_WT_ROUTE_CONFIG 显式指定 ⇒ 最高优先(忽略其余
89
89
  setcfg "$CFG" '{ "direct": ["myrepo"] }'
90
90
  EX="$TD/explicit.json"; setcfg "$EX" '{ "direct": ["teamrepo"] }'
91
91
  env DIA_WT_ROUTE_CONFIG="$EX" bash "$WT" route "$TEAM" >"$TD/out.txt" 2>&1; RC=$?
92
- [ "$RC" = 0 ] && ok "显式配置生效(teamrepo 直推)" || { bad "显式配置未生效(rc=${RC})"; out; }
92
+ [ "$RC" = 0 ] && ok "显式配置生效(teamrepo = owner 自合)" || { bad "显式配置未生效(rc=${RC})"; out; }
93
93
  env DIA_WT_ROUTE_CONFIG="$EX" bash "$WT" route "$MINE" >"$TD/out.txt" 2>&1; RC=$?
94
- [ "$RC" = 1 ] && ok "其余配置被忽略(myrepo 未直推)" || { bad "应忽略数据根配置(rc=${RC})"; out; }
94
+ [ "$RC" = 1 ] && ok "其余配置被忽略(myrepo 走 LGTM+bot)" || { bad "应忽略数据根配置(rc=${RC})"; out; }
95
95
  rm -f "$EX"
96
96
 
97
97
  echo "== 11. worktree 路径输入 ⇒ 归主仓判定 =="
98
98
  setcfg "$CFG" '{ "direct": ["teamrepo"] }'
99
99
  git -C "$TEAM" worktree add -q "$TD/teamrepo-worktree--t1" -b t1 >/dev/null 2>&1
100
100
  RC="$(run "$TD/teamrepo-worktree--t1")"
101
- [ "$RC" = 0 ] && ok "worktree 归主仓 → 直推" || { bad "worktree 判定错(rc=${RC})"; out; }
101
+ [ "$RC" = 0 ] && ok "worktree 归主仓 → owner 自合" || { bad "worktree 判定错(rc=${RC})"; out; }
102
102
  see "teamrepo" && ok "报出主仓名" || { bad "未报主仓名"; out; }
103
103
 
104
104
  echo "== 12. 非 git 目录 ⇒ 信息不足(rc=2)=="