@minonl/dia 0.0.0-stage → 0.0.2-beta.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/LICENSE +21 -0
- package/README.md +253 -2
- package/bin/dia.js +5 -0
- package/dist/cli.js +7408 -0
- package/docs/acp-integration.md +136 -0
- package/docs/architecture.md +166 -0
- package/docs/cli.md +117 -0
- package/docs/dependency-registry.md +105 -0
- package/docs/deps.jsonl +7 -0
- package/docs/ecosystem-integration.md +155 -0
- package/docs/ecosystem-positioning.md +95 -0
- package/docs/entrypoints.md +83 -0
- package/docs/evolution-process.md +298 -0
- package/docs/history/framework-bootstrap.md +109 -0
- package/docs/host-control-surface.md +213 -0
- package/docs/index.md +80 -0
- package/docs/kit-install.md +339 -0
- package/docs/migrations/kb-golden.md +32 -0
- package/docs/migrations/llm-migration.md +49 -0
- package/docs/migrations/no-python.md +52 -0
- package/docs/migrations/yan-agent-current.md +52 -0
- package/docs/migrations/yan-agent-next.md +168 -0
- package/docs/migrations/yan-kb-parity.md +159 -0
- package/docs/migrations/yan-kb.md +120 -0
- package/docs/model-baseline-sidecar.md +220 -0
- package/docs/model-behavior.md +229 -0
- package/docs/monorepo.md +83 -0
- package/docs/overview.md +126 -0
- package/docs/patching.md +91 -0
- package/docs/persona-install.md +264 -0
- package/docs/platforms/index.md +66 -0
- package/docs/platforms/registry.json +54 -0
- package/docs/pricing.md +43 -0
- package/docs/proposals.md +76 -0
- package/docs/research/benchmarks-survey.md +96 -0
- package/docs/research/collaboration-theory.md +85 -0
- package/docs/research/dsh-superagent.md +97 -0
- package/docs/research/frameworks-survey-2.md +75 -0
- package/docs/research/h0/report.md +135 -0
- package/docs/research/h0/sample.json +34 -0
- package/docs/research/model-boundary-patterns.md +99 -0
- package/docs/research/opencode-session-scoping.md +84 -0
- package/docs/review/reuse-review.md +105 -0
- package/docs/review/verification-2026-10-28.md +49 -0
- package/docs/security/audits/2026-11-04.json +44 -0
- package/docs/security/credentials.md +95 -0
- package/docs/security/public-release.md +108 -0
- package/docs/security-sandbox.md +157 -0
- package/docs/status.md +158 -0
- package/docs/team-automation.md +169 -0
- package/docs/team.md +112 -0
- package/docs/versioning.md +237 -0
- package/docs/vision.md +161 -0
- package/package.json +45 -4
- package/packages/job/README.md +98 -0
- package/packages/job/bin/dia-job +1283 -0
- package/packages/job/config.example +5 -0
- package/packages/job/install.sh +37 -0
- package/packages/job/lib/config.sh +18 -0
- package/packages/job/lib/json.mjs +130 -0
- package/packages/job/lib/policy.sh +103 -0
- package/packages/job/package.json +10 -0
- package/packages/job/shim/yan-task +24 -0
- package/packages/job/tests/bootstrap.test.sh +115 -0
- package/packages/job/tests/install.test.sh +30 -0
- package/packages/job/tests/linux.test.sh +130 -0
- package/packages/job/tests/p2.test.sh +46 -0
- package/packages/job/tests/policy.test.sh +38 -0
- package/packages/wt/bin/dia-wt +1148 -0
- package/packages/wt/tests/check.test.sh +126 -0
- package/packages/wt/tests/clean.test.sh +126 -0
- package/packages/wt/tests/prune-merged.test.sh +169 -0
- package/packages/wt/tests/route.test.sh +112 -0
- package/packages/wt/tests/scope.test.sh +69 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# dia wt-check.test.sh —— `dia wt check`(R20 机械闸口)回归测试(2026-10-15)
|
|
3
|
+
#
|
|
4
|
+
# 覆盖:
|
|
5
|
+
# · A 顶层 node_modules 是软链(跨 checkout 共享的典型形态)
|
|
6
|
+
# · B 顶层链逃出本 checkout · C 顶层链悬空 · D 深层链逃逸(仅 --deep)
|
|
7
|
+
# · ✅ **GVS 白名单**:指向 `<storeDir>/links/@/…` 的链**不算违规** —— 这条断言是"启用 pnpm
|
|
8
|
+
# global virtual store 后不误报"的唯一保险(判据不区分共享 store 与别人的 checkout ⇒ 一开 GVS 全仓红)
|
|
9
|
+
# · 真装形态不误报 · 主仓 + 各 worktree 全覆盖 · 仓根杂散 node_modules(--all)
|
|
10
|
+
# · clean 软链守卫(拒绝清理且不动目录)· 退出码契约(0 干净 / 1 违规 / 2 未知子命令)
|
|
11
|
+
# 用法:bash scripts/dia wt-check.test.sh (退出码 0 = 全绿)
|
|
12
|
+
set -uo pipefail
|
|
13
|
+
|
|
14
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
WT="$HERE/../bin/dia-wt"
|
|
16
|
+
TD="$(mktemp -d)"
|
|
17
|
+
export DIA_WT_DATA_ROOT="$TD/dr" DIA_WT_REPOS_ROOT="$TD/repos"
|
|
18
|
+
mkdir -p "$DIA_WT_DATA_ROOT" "$DIA_WT_REPOS_ROOT"
|
|
19
|
+
STORE="$TD/store"; mkdir -p "$STORE/links/@/lodash/1.0.0/abc123/node_modules/lodash"
|
|
20
|
+
printf 'x\n' > "$STORE/links/@/lodash/1.0.0/abc123/node_modules/lodash/index.js"
|
|
21
|
+
|
|
22
|
+
PASS=0; FAIL=0
|
|
23
|
+
ok(){ echo " ✓ $1"; PASS=$((PASS+1)); }
|
|
24
|
+
bad(){ echo " ✗ $1"; FAIL=$((FAIL+1)); }
|
|
25
|
+
out(){ cat "$TD/out.txt" | sed 's/^/ | /'; }
|
|
26
|
+
run(){ bash "$WT" "$@" >"$TD/out.txt" 2>&1; echo $?; }
|
|
27
|
+
see(){ grep -q "$1" "$TD/out.txt"; }
|
|
28
|
+
|
|
29
|
+
mkrepo(){ git -C "$1" init -q; printf 'x\n' > "$1/f"; git -C "$1" add -A
|
|
30
|
+
git -C "$1" -c user.email=t@t -c user.name=t commit -qm init; }
|
|
31
|
+
# 真装形态:node_modules 真目录 + .pnpm 实体 + 顶层链指向 **checkout 内**
|
|
32
|
+
mk_real(){ local d="$1"
|
|
33
|
+
mkdir -p "$d/node_modules/.pnpm/lodash@1.0.0/node_modules/lodash"
|
|
34
|
+
printf 'x\n' > "$d/node_modules/.pnpm/lodash@1.0.0/node_modules/lodash/index.js"
|
|
35
|
+
ln -sfn .pnpm/lodash@1.0.0/node_modules/lodash "$d/node_modules/lodash"
|
|
36
|
+
printf '{"storeDir": "%s"}\n' "$STORE" > "$d/node_modules/.modules.yaml"; }
|
|
37
|
+
|
|
38
|
+
echo "== 1. 真装形态(顶层链指向 checkout 内)⇒ 0 违规 =="
|
|
39
|
+
A="$TD/repoA"; mkdir -p "$A"; mkrepo "$A"; mk_real "$A"
|
|
40
|
+
[ "$(run check "$A")" = 0 ] && ok "退出码 0" || { bad "退出码应为 0"; out; }
|
|
41
|
+
see "全部干净" && ok "输出「全部干净」" || { bad "缺「全部干净」"; out; }
|
|
42
|
+
|
|
43
|
+
echo "== 2. A:顶层 node_modules 本身是软链 ⇒ 违规 =="
|
|
44
|
+
B="$TD/repoB"; mkdir -p "$B"; mkrepo "$B"; mk_real "$B" # B 是"另一个 checkout"
|
|
45
|
+
rm -rf "$A/node_modules"; ln -sfn "$B/node_modules" "$A/node_modules"
|
|
46
|
+
RC="$(run check "$A")"
|
|
47
|
+
[ "$RC" = 1 ] && ok "退出码 1" || { bad "退出码应为 1(实得 ${RC})"; out; }
|
|
48
|
+
see "A 顶层 node_modules 是软链" && ok "报 A 类" || { bad "未报 A 类"; out; }
|
|
49
|
+
see "$B/node_modules" && ok "报出目标 checkout" || { bad "未报出目标"; out; }
|
|
50
|
+
|
|
51
|
+
echo "== 3. B:顶层链逃出 checkout(指向别的 checkout)⇒ 违规 =="
|
|
52
|
+
rm -f "$A/node_modules" # 先摘掉第 2 步的软链(否则后续夹具会顺着链写进 repoB)
|
|
53
|
+
mk_real "$A" # 恢复真目录,只留一条逃逸链
|
|
54
|
+
ln -sfn "$B/node_modules/.pnpm/lodash@1.0.0/node_modules/lodash" "$A/node_modules/lodash-foreign"
|
|
55
|
+
RC="$(run check "$A")"
|
|
56
|
+
[ "$RC" = 1 ] && ok "退出码 1" || { bad "退出码应为 1(实得 ${RC})"; out; }
|
|
57
|
+
see "B 顶层链指向其他 checkout" && ok "报 B 类" || { bad "未报 B 类"; out; }
|
|
58
|
+
rm -f "$A/node_modules/lodash-foreign"
|
|
59
|
+
|
|
60
|
+
echo "== 4. C:顶层链悬空 ⇒ 违规 =="
|
|
61
|
+
ln -sfn "$TD/gone/node_modules/lodash" "$A/node_modules/lodash-dangling"
|
|
62
|
+
RC="$(run check "$A")"
|
|
63
|
+
[ "$RC" = 1 ] && ok "退出码 1" || { bad "退出码应为 1(实得 ${RC})"; out; }
|
|
64
|
+
see "C 顶层链悬空" && ok "报 C 类" || { bad "未报 C 类"; out; }
|
|
65
|
+
rm -f "$A/node_modules/lodash-dangling"
|
|
66
|
+
|
|
67
|
+
echo "== 5. ✅ GVS 白名单:链指向 <storeDir>/links ⇒ **不算违规**(启用官方共享方案后不误报)=="
|
|
68
|
+
ln -sfn "$STORE/links/@/lodash/1.0.0/abc123/node_modules/lodash" "$A/node_modules/lodash-gvs"
|
|
69
|
+
RC="$(run check "$A")"
|
|
70
|
+
[ "$RC" = 0 ] && ok "退出码 0(白名单生效)" || { bad "退出码应为 0(实得 ${RC}):GVS 链被误判"; out; }
|
|
71
|
+
rm -f "$A/node_modules/lodash-gvs"
|
|
72
|
+
|
|
73
|
+
echo "== 6. D:深层链逃逸 ⇒ 默认不报、--deep 才报 =="
|
|
74
|
+
mkdir -p "$A/node_modules/.pnpm/foo@1.0.0/node_modules"
|
|
75
|
+
ln -sfn "$B/node_modules/.pnpm/lodash@1.0.0/node_modules/lodash" "$A/node_modules/.pnpm/foo@1.0.0/node_modules/foo"
|
|
76
|
+
RC="$(run check "$A")"
|
|
77
|
+
[ "$RC" = 0 ] && ok "默认(浅扫)0 违规" || { bad "浅扫不该报深层链(实得 ${RC})"; out; }
|
|
78
|
+
RC="$(run check "$A" --deep)"
|
|
79
|
+
[ "$RC" = 1 ] && ok "--deep 退出码 1" || { bad "--deep 应报违规(实得 ${RC})"; out; }
|
|
80
|
+
see "D 深层链逃逸" && ok "--deep 报 D 类" || { bad "--deep 未报 D 类"; out; }
|
|
81
|
+
rm -rf "$A/node_modules/.pnpm/foo@1.0.0"
|
|
82
|
+
|
|
83
|
+
echo "== 7. 全覆盖:主仓 ✓ + worktree ✗ 同时报出 =="
|
|
84
|
+
W="$TD/repoW"; mkdir -p "$W"; mkrepo "$W"; mk_real "$W"
|
|
85
|
+
git -C "$W" worktree add -q "$TD/repoW-worktree--t1" -b t1
|
|
86
|
+
mkdir -p "$TD/repoW-worktree--t1"; rm -rf "$TD/repoW-worktree--t1/node_modules"
|
|
87
|
+
ln -sfn "$W/node_modules" "$TD/repoW-worktree--t1/node_modules"
|
|
88
|
+
RC="$(run check "$W")"
|
|
89
|
+
[ "$RC" = 1 ] && ok "退出码 1" || { bad "退出码应为 1(实得 ${RC})"; out; }
|
|
90
|
+
see "repoW · 主仓 0 违规" && ok "主仓判干净" || { bad "主仓未被判为干净(误报)"; out; }
|
|
91
|
+
see "repoW-worktree--t1 违规" && ok "worktree 被判违规" || { bad "worktree 未被判违规"; out; }
|
|
92
|
+
|
|
93
|
+
echo "== 8. clean 守卫:软链 node_modules 的 worktree ⇒ 拒绝清理且目录保留 =="
|
|
94
|
+
RC="$(run clean "$W" t1)"
|
|
95
|
+
[ "$RC" = 1 ] && ok "退出码 1(拒绝)" || { bad "应拒绝清理(实得 ${RC})"; out; }
|
|
96
|
+
see "拒绝清理" && ok "提示「拒绝清理」" || { bad "缺拒绝提示"; out; }
|
|
97
|
+
see "check" && ok "指引先跑 check" || { bad "缺 check 指引"; out; }
|
|
98
|
+
[ -d "$TD/repoW-worktree--t1" ] && ok "worktree 目录仍在(未被动)" || bad "worktree 被删了(守卫失效)"
|
|
99
|
+
rm -f "$TD/repoW-worktree--t1/node_modules" # 收尾:删链后应可正常清理
|
|
100
|
+
|
|
101
|
+
echo "== 9. --all:仓根杂散 node_modules 也算违规 =="
|
|
102
|
+
mkdir -p "$DIA_WT_REPOS_ROOT/node_modules"
|
|
103
|
+
RC="$(run check --all)"
|
|
104
|
+
[ "$RC" = 1 ] && ok "退出码 1" || { bad "退出码应为 1(实得 ${RC})"; out; }
|
|
105
|
+
see "杂散" && ok "报杂散" || { bad "未报杂散"; out; }
|
|
106
|
+
rm -rf "$DIA_WT_REPOS_ROOT/node_modules"
|
|
107
|
+
|
|
108
|
+
echo "== 10. 退出码契约:未知子命令=2 / 参数错=1 =="
|
|
109
|
+
bash "$WT" bogus-subcmd >/dev/null 2>&1; RC_UNK=$?
|
|
110
|
+
bash "$WT" check --bogus-flag "$A" >/dev/null 2>&1; RC_ARG=$?
|
|
111
|
+
[ "$RC_UNK" = 2 ] && ok "未知子命令=2" || bad "未知子命令应为 2(实得 ${RC_UNK})"
|
|
112
|
+
[ "$RC_ARG" = 1 ] && ok "未知参数=1" || bad "未知参数应为 1(实得 ${RC_ARG})"
|
|
113
|
+
|
|
114
|
+
echo "== 11. 无 node_modules 的目录 ⇒ 跳过、不报错 =="
|
|
115
|
+
N="$TD/plain"; mkdir -p "$N"
|
|
116
|
+
[ "$(run check "$N")" = 0 ] && ok "退出码 0" || { bad "应 0(实得见现场)"; out; }
|
|
117
|
+
|
|
118
|
+
echo "== 12. --quiet:干净仓不打印 ✓ 行(定时任务降噪)=="
|
|
119
|
+
run check --quiet "$A" >/dev/null
|
|
120
|
+
see "✓" && bad "--quiet 仍打印 ✓ 行" || ok "--quiet 无 ✓ 行"
|
|
121
|
+
see "全部干净" && ok "仍打印结论行" || { bad "缺结论行"; out; }
|
|
122
|
+
|
|
123
|
+
echo
|
|
124
|
+
echo "── PASS $PASS · FAIL $FAIL ──"
|
|
125
|
+
rm -rf "$TD"
|
|
126
|
+
[ "$FAIL" = 0 ]
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# dia wt-clean.test.sh —— `dia wt clean`(R25 收尾闸口)回归测试(2026-10-19)
|
|
3
|
+
#
|
|
4
|
+
# 为什么单独测:`clean` 是**唯一**执行 R25「worktree + 分支」的机械闸口,此前**无测试**。
|
|
5
|
+
# 2026-10-19 实测发现 P0 级静默缺陷:
|
|
6
|
+
# 原实现写死 `br="feat/${feature}"`,而 `create` 接受任意分支名(`review/*`、`fix/*` …)
|
|
7
|
+
# ⇒ 名字对不上时**跳过删分支且不告警**,仍打印「✅ 清理完成」+ 退出码 0 ⇒ **谎报**。
|
|
8
|
+
#
|
|
9
|
+
# 覆盖:
|
|
10
|
+
# ① 任意前缀(review/ fix/ feat/ 无前缀)都能删分支 ← 本次修复的核心
|
|
11
|
+
# ② 已合并 → 删分支 + 退出码 0
|
|
12
|
+
# ③ 未合并 → **保留分支 + 退出码 1**(不能谎报完成)
|
|
13
|
+
# ④ detached HEAD → worktree 删掉、不报错、说清楚
|
|
14
|
+
# ⑤ 有未提交改动 → 拒绝(worktree 与分支都不动)
|
|
15
|
+
# ⑥ 软链 node_modules → 拒绝(R20 守卫仍在)
|
|
16
|
+
# ⑦ 清理后 worktree 目录确实消失
|
|
17
|
+
#
|
|
18
|
+
# 用法:bash scripts/dia wt-clean.test.sh (退出码 0 = 全绿)
|
|
19
|
+
set -uo pipefail
|
|
20
|
+
|
|
21
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
22
|
+
WT="$HERE/../bin/dia-wt"
|
|
23
|
+
TD="$(mktemp -d)"
|
|
24
|
+
trap 'rm -rf "$TD"' EXIT
|
|
25
|
+
export DIA_WT_DATA_ROOT="$TD/dr" DIA_WT_REPOS_ROOT="$TD/repos"
|
|
26
|
+
mkdir -p "$DIA_WT_DATA_ROOT" "$DIA_WT_REPOS_ROOT"
|
|
27
|
+
|
|
28
|
+
PASS=0; FAIL=0
|
|
29
|
+
ok(){ echo " ✓ $1"; PASS=$((PASS+1)); }
|
|
30
|
+
bad(){ echo " ✗ $1"; FAIL=$((FAIL+1)); }
|
|
31
|
+
out(){ sed 's/^/ | /' "$TD/out.txt"; }
|
|
32
|
+
run(){ bash "$WT" "$@" >"$TD/out.txt" 2>&1; echo $?; }
|
|
33
|
+
see(){ grep -q "$1" "$TD/out.txt"; }
|
|
34
|
+
|
|
35
|
+
mkrepo(){ git -C "$1" init -q -b main; printf 'x\n' > "$1/f"
|
|
36
|
+
git -C "$1" add -A
|
|
37
|
+
git -C "$1" -c user.email=t@t -c user.name=t commit -qm init; }
|
|
38
|
+
|
|
39
|
+
# 建一个 worktree(模拟 dia wt create 的产物:平级目录 + 指定分支名)
|
|
40
|
+
mkwt(){ local repo="$1" feature="$2" branch="$3"
|
|
41
|
+
local parent; parent="$(cd "$(dirname "$repo")" && pwd)"
|
|
42
|
+
git -C "$repo" worktree add "$parent/$(basename "$repo")-worktree--$feature" -b "$branch" >/dev/null 2>&1
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
echo '== ① 任意分支前缀都能删(本次修复核心)=='
|
|
46
|
+
for br in 'review/head-gate' 'fix/wt-clean' 'feat/plain' 'no-prefix'; do
|
|
47
|
+
R="$TD/r-$br"; mkdir -p "$R"; mkrepo "$R"
|
|
48
|
+
f="t$(echo "$br" | tr '/-' '__')"
|
|
49
|
+
mkwt "$R" "$f" "$br"
|
|
50
|
+
rc="$(run clean "$R" "$f")"
|
|
51
|
+
if [ "$rc" = 0 ] && ! git -C "$R" rev-parse --verify --quiet "refs/heads/$br" >/dev/null; then
|
|
52
|
+
ok "${br}:已删分支 + 退出码 0"
|
|
53
|
+
else
|
|
54
|
+
bad "${br}:应删分支且退出码 0(实际 rc=${rc})"; out
|
|
55
|
+
fi
|
|
56
|
+
[ -d "$TD/$(basename "$R")-worktree--$f" ] && bad "${br}:worktree 目录仍存在" || ok "${br}:worktree 目录已删"
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
echo '== ② 已合并分支 → 删分支 + rc 0 =='
|
|
60
|
+
R="$TD/r-merged"; mkdir -p "$R"; mkrepo "$R"; mkwt "$R" m1 'review/merged'
|
|
61
|
+
# 把分支内容合进 main,使其可被 -d 删除
|
|
62
|
+
git -C "$R" merge --no-ff -q 'review/merged' -m merge 2>/dev/null
|
|
63
|
+
rc="$(run clean "$R" m1)"
|
|
64
|
+
[ "$rc" = 0 ] && ! git -C "$R" rev-parse --verify --quiet refs/heads/review/merged >/dev/null \
|
|
65
|
+
&& ok '已合并:分支已删、rc 0' || { bad "已合并:应删分支 rc=0(实际 rc=${rc})"; out; }
|
|
66
|
+
|
|
67
|
+
echo '== ③ 未合并分支 → 保留分支 + rc 1(不谎报完成)=='
|
|
68
|
+
R="$TD/r-unmerged"; mkdir -p "$R"; mkrepo "$R"; mkwt "$R" u1 'review/unmerged'
|
|
69
|
+
# 在分支上加提交,使其未合并
|
|
70
|
+
( cd "$TD/r-unmerged-worktree--u1" && printf 'y\n' > y.txt && git add -A \
|
|
71
|
+
&& git -c user.email=t@t -c user.name=t commit -qm 'branch only' ) >/dev/null 2>&1
|
|
72
|
+
rc="$(run clean "$R" u1)"
|
|
73
|
+
[ "$rc" = 1 ] && git -C "$R" rev-parse --verify --quiet refs/heads/review/unmerged >/dev/null \
|
|
74
|
+
&& ok '未合并:分支保留 + rc 1' || { bad "未合并:应保留分支且 rc=1(实际 rc=${rc})"; out; }
|
|
75
|
+
see '未完全合并' && ok '未合并:有明确告警文案' || { bad '未合并:缺告警文案'; out; }
|
|
76
|
+
[ -d "$TD/r-unmerged-worktree--u1" ] && bad '未合并:worktree 应已删' || ok '未合并:worktree 已删'
|
|
77
|
+
|
|
78
|
+
echo '== ④ detached HEAD → 删 worktree、不报错 =='
|
|
79
|
+
R="$TD/r-detached"; mkdir -p "$R"; mkrepo "$R"
|
|
80
|
+
parent="$(cd "$(dirname "$R")" && pwd)"
|
|
81
|
+
git -C "$R" worktree add --detach "$parent/$(basename "$R")-worktree--d1" >/dev/null 2>&1
|
|
82
|
+
rc="$(run clean "$R" d1)"
|
|
83
|
+
[ "$rc" = 0 ] && [ ! -d "$parent/$(basename "$R")-worktree--d1" ] \
|
|
84
|
+
&& ok 'detached:worktree 已删、rc 0' || { bad "detached:应 rc=0 且目录消失(实际 rc=${rc})"; out; }
|
|
85
|
+
see 'detached HEAD' && ok 'detached:说明了跳过删分支' || { bad 'detached:未说明'; out; }
|
|
86
|
+
|
|
87
|
+
echo '== ⑤ 有未提交改动 → 拒绝(不动 worktree / 分支)=='
|
|
88
|
+
R="$TD/r-dirty"; mkdir -p "$R"; mkrepo "$R"; mkwt "$R" d1 'review/dirty'
|
|
89
|
+
printf 'dirty\n' > "$TD/r-dirty-worktree--d1/dirty.txt"
|
|
90
|
+
rc="$(run clean "$R" d1)"
|
|
91
|
+
[ "$rc" = 1 ] && [ -d "$TD/r-dirty-worktree--d1" ] \
|
|
92
|
+
&& git -C "$R" rev-parse --verify --quiet refs/heads/review/dirty >/dev/null \
|
|
93
|
+
&& ok '脏工作区:拒绝且未动' || { bad "脏工作区:应拒绝且不动(rc=${rc})"; out; }
|
|
94
|
+
see '唯一工作副本' && ok '脏工作区:给了抢救提示' || { bad '脏工作区:缺抢救提示'; out; }
|
|
95
|
+
|
|
96
|
+
echo '== ⑥ 软链 node_modules → 拒绝(R20 守卫)=='
|
|
97
|
+
R="$TD/r-link"; mkdir -p "$R"; mkrepo "$R"; mkwt "$R" l1 'review/link'
|
|
98
|
+
ln -sfn "$TD/store-node_modules" "$TD/r-link-worktree--l1/node_modules"
|
|
99
|
+
rc="$(run clean "$R" l1)"
|
|
100
|
+
[ "$rc" = 1 ] && [ -d "$TD/r-link-worktree--l1" ] && ok '软链:拒绝且未动' \
|
|
101
|
+
|| { bad "软链:应拒绝(rc=${rc})"; out; }
|
|
102
|
+
|
|
103
|
+
echo '== ⑦ worktree 不存在 → 明确报错 =='
|
|
104
|
+
R="$TD/r-missing"; mkdir -p "$R"; mkrepo "$R"
|
|
105
|
+
rc="$(run clean "$R" nope)"
|
|
106
|
+
[ "$rc" = 1 ] && ok '不存在:rc 1' || bad "不存在:应 rc=1(实际 ${rc})"
|
|
107
|
+
|
|
108
|
+
echo '== ⑧ 远端分支仍在 → 提示清理命令(不自动删)=='
|
|
109
|
+
R="$TD/r-remote"; mkdir -p "$R"; mkrepo "$R"
|
|
110
|
+
# 造一个假的 origin(裸库),推一个分支上去
|
|
111
|
+
git init -q --bare "$TD/remote.git"
|
|
112
|
+
git -C "$R" remote add origin "$TD/remote.git"
|
|
113
|
+
git -C "$R" push -q -u origin main 2>/dev/null
|
|
114
|
+
mkwt "$R" r1 'review/remote'
|
|
115
|
+
( cd "$TD/r-remote-worktree--r1" && printf 'z\n' > z.txt && git add -A \
|
|
116
|
+
&& git -c user.email=t@t -c user.name=t commit -qm 'r' && git push -q -u origin review/remote ) >/dev/null 2>&1
|
|
117
|
+
# 先合进 main 让本地分支可删
|
|
118
|
+
git -C "$R" merge --no-ff -q 'review/remote' -m merge 2>/dev/null
|
|
119
|
+
git -C "$R" push -q origin main 2>/dev/null
|
|
120
|
+
rc="$(run clean "$R" r1)"
|
|
121
|
+
see '远端分支仍在' && ok '远端存在:明确提示' || { bad '远端存在:未提示'; out; }
|
|
122
|
+
see 'push origin --delete' && ok '远端存在:给了删除命令' || { bad '远端存在:缺命令'; out; }
|
|
123
|
+
|
|
124
|
+
echo
|
|
125
|
+
printf '结果:%d 通过,%d 失败\n' "$PASS" "$FAIL"
|
|
126
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# dia wt-prune-merged.test.sh —— `prune-merged`(R25 定期闸口)回归测试(2026-10-19)
|
|
3
|
+
#
|
|
4
|
+
# 为什么测:这是**破坏性**操作(删分支),判据错会误删他人工作。必须机械验证:
|
|
5
|
+
# · 只删「已并入 base」的分支(`-d` 语义)
|
|
6
|
+
# · **不删**当前分支 / 被 worktree 占用的分支 / 保护名单 / 未合并分支
|
|
7
|
+
# · 默认 dry-run(不删),`--apply` 才真删
|
|
8
|
+
# · 退出码:0=干净或已清完 / 1=仍有残留
|
|
9
|
+
# · `--quiet` 只输出分支名(供巡检任务消费)
|
|
10
|
+
#
|
|
11
|
+
# 用法:bash scripts/dia wt-prune-merged.test.sh (退出码 0 = 全绿)
|
|
12
|
+
set -uo pipefail
|
|
13
|
+
|
|
14
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
WT="$HERE/../bin/dia-wt"
|
|
16
|
+
TD="$(mktemp -d)"
|
|
17
|
+
trap 'rm -rf "$TD"' EXIT
|
|
18
|
+
export DIA_WT_DATA_ROOT="$TD/dr" DIA_WT_REPOS_ROOT="$TD/repos"
|
|
19
|
+
mkdir -p "$DIA_WT_DATA_ROOT" "$DIA_WT_REPOS_ROOT"
|
|
20
|
+
|
|
21
|
+
PASS=0; FAIL=0
|
|
22
|
+
ok(){ echo " ✓ $1"; PASS=$((PASS+1)); }
|
|
23
|
+
bad(){ echo " ✗ $1"; FAIL=$((FAIL+1)); }
|
|
24
|
+
out(){ sed 's/^/ | /' "$TD/out.txt"; }
|
|
25
|
+
run(){ bash "$WT" prune-merged "$@" >"$TD/out.txt" 2>&1; echo $?; }
|
|
26
|
+
see(){ grep -q "$1" "$TD/out.txt"; }
|
|
27
|
+
notsee(){ grep -q "$1" "$TD/out.txt" && return 1 || return 0; }
|
|
28
|
+
|
|
29
|
+
# 造仓库:main 有 init 提交;另有 origin/main 指向同一提交(模拟真实基线)
|
|
30
|
+
mkrepo(){
|
|
31
|
+
local r="$1"; mkdir -p "$r"; git -C "$r" init -q -b main
|
|
32
|
+
printf 'x\n' > "$r/f"; git -C "$r" add -A
|
|
33
|
+
git -C "$r" -c user.email=t@t -c user.name=t commit -qm init
|
|
34
|
+
git -C "$r" update-ref refs/remotes/origin/main refs/heads/main
|
|
35
|
+
}
|
|
36
|
+
# 建一个已并入 main 的分支(内容与 main 相同 → git 认为是 ancestor)
|
|
37
|
+
mkmerged(){ git -C "$1" branch "$2" main >/dev/null 2>&1; }
|
|
38
|
+
# 建一个未并入的分支
|
|
39
|
+
mkunmerged(){
|
|
40
|
+
local r="$1" b="$2"
|
|
41
|
+
git -C "$r" checkout -q -b "$b" main
|
|
42
|
+
# ⚠️ 文件名不能用分支名(含 `/` 会变成子目录 → 写入失败 → 提交没发生 → 分支等于 main)
|
|
43
|
+
printf 'y\n' > "$r/unmerged-marker.txt"; git -C "$r" add -A
|
|
44
|
+
git -C "$r" -c user.email=t@t -c user.name=t commit -qm "$b only"
|
|
45
|
+
git -C "$r" checkout -q main
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
echo '== ① dry-run:只列出、不删除、退出码 1 =='
|
|
49
|
+
R="$TD/r1"; mkrepo "$R"
|
|
50
|
+
mkmerged "$R" feat/done1; mkmerged "$R" review/done2
|
|
51
|
+
rc="$(run "$R")"
|
|
52
|
+
[ "$rc" = 1 ] && ok '有残留 → 退出码 1' || { bad "应 rc=1(实际 ${rc})"; out; }
|
|
53
|
+
see 'feat/done1' && see 'review/done2' && ok '列出了两个残留分支' || { bad '未列出残留'; out; }
|
|
54
|
+
git -C "$R" rev-parse --verify -q refs/heads/feat/done1 >/dev/null \
|
|
55
|
+
&& ok 'dry-run 未删除' || bad 'dry-run 却删了分支'
|
|
56
|
+
|
|
57
|
+
echo '== ② --apply:真删已合并分支 =='
|
|
58
|
+
rc="$(run "$R" --apply)"
|
|
59
|
+
[ "$rc" = 0 ] && ok '全部删净 → 退出码 0' || { bad "应 rc=0(实际 ${rc})"; out; }
|
|
60
|
+
git -C "$R" rev-parse --verify -q refs/heads/feat/done1 >/dev/null \
|
|
61
|
+
&& bad 'feat/done1 仍在' || ok 'feat/done1 已删'
|
|
62
|
+
git -C "$R" rev-parse --verify -q refs/heads/review/done2 >/dev/null \
|
|
63
|
+
&& bad 'review/done2 仍在' || ok 'review/done2 已删'
|
|
64
|
+
|
|
65
|
+
echo '== ③ 保护名单:main / master / develop / 自定义 =='
|
|
66
|
+
R="$TD/r2"; mkrepo "$R"
|
|
67
|
+
git -C "$R" branch develop main; git -C "$R" branch keepme main
|
|
68
|
+
mkmerged "$R" feat/gone
|
|
69
|
+
rc="$(run "$R" --apply --protect main,master,develop,dev,keepme)"
|
|
70
|
+
git -C "$R" rev-parse --verify -q refs/heads/main >/dev/null && ok 'main 保留' || bad 'main 被删'
|
|
71
|
+
git -C "$R" rev-parse --verify -q refs/heads/develop >/dev/null && ok 'develop 保留' || bad 'develop 被删'
|
|
72
|
+
git -C "$R" rev-parse --verify -q refs/heads/keepme >/dev/null && ok '自定义保护生效' || bad 'keepme 被删'
|
|
73
|
+
git -C "$R" rev-parse --verify -q refs/heads/feat/gone >/dev/null && bad 'feat/gone 应被删' || ok '非保护分支已删'
|
|
74
|
+
|
|
75
|
+
echo '== ④ 未合并分支:绝不删 =='
|
|
76
|
+
R="$TD/r3"; mkrepo "$R"
|
|
77
|
+
mkunmerged "$R" feat/wip
|
|
78
|
+
rc="$(run "$R" --apply)"
|
|
79
|
+
[ "$rc" = 0 ] && ok '无已合并残留 → rc 0' || { bad "应 rc=0(实际 ${rc})"; out; }
|
|
80
|
+
git -C "$R" rev-parse --verify -q refs/heads/feat/wip >/dev/null \
|
|
81
|
+
&& ok '未合并分支保留' || bad '未合并分支被误删!'
|
|
82
|
+
notsee 'feat/wip' && ok '未合并分支未出现在候选里' || { bad '未合并分支被列为候选'; out; }
|
|
83
|
+
|
|
84
|
+
echo '== ⑤ 被 worktree 占用的分支:跳过 =='
|
|
85
|
+
R="$TD/r4"; mkrepo "$R"
|
|
86
|
+
mkmerged "$R" feat/wt-branch
|
|
87
|
+
parent="$(cd "$(dirname "$R")" && pwd)"
|
|
88
|
+
git -C "$R" worktree add "$parent/$(basename "$R")-worktree--wt" feat/wt-branch >/dev/null 2>&1
|
|
89
|
+
rc="$(run "$R" --apply)"
|
|
90
|
+
git -C "$R" rev-parse --verify -q refs/heads/feat/wt-branch >/dev/null \
|
|
91
|
+
&& ok '被 worktree 占用的分支保留' || bad '误删了 worktree 正在用的分支!'
|
|
92
|
+
git -C "$R" worktree remove "$parent/$(basename "$R")-worktree--wt" --force >/dev/null 2>&1
|
|
93
|
+
|
|
94
|
+
echo '== ⑥ 当前分支:跳过 =='
|
|
95
|
+
R="$TD/r5"; mkrepo "$R"
|
|
96
|
+
git -C "$R" checkout -q -b feat/current main
|
|
97
|
+
rc="$(run "$R" --apply)"
|
|
98
|
+
git -C "$R" rev-parse --verify -q refs/heads/feat/current >/dev/null \
|
|
99
|
+
&& ok '当前分支保留' || bad '误删当前分支!'
|
|
100
|
+
|
|
101
|
+
echo '== ⑦ --quiet:只输出分支名(供巡检消费)=='
|
|
102
|
+
R="$TD/r6"; mkrepo "$R"
|
|
103
|
+
mkmerged "$R" feat/q1; mkmerged "$R" feat/q2
|
|
104
|
+
bash "$WT" prune-merged "$R" --quiet >"$TD/out.txt" 2>&1
|
|
105
|
+
[ "$(grep -c 'feat/q' "$TD/out.txt")" = 2 ] && ok '输出 2 行分支名' || { bad '输出行数不对'; out; }
|
|
106
|
+
notsee '仓库:' && ok 'quiet 无装饰性输出' || { bad 'quiet 仍有装饰输出'; out; }
|
|
107
|
+
|
|
108
|
+
echo '== ⑧ 干净仓库:rc 0 + 明确提示 =='
|
|
109
|
+
R="$TD/r7"; mkrepo "$R"
|
|
110
|
+
rc="$(run "$R")"
|
|
111
|
+
[ "$rc" = 0 ] && ok '干净 → rc 0' || bad "应 rc=0(实际 ${rc})"
|
|
112
|
+
see '无已合并的残留分支' && ok '有明确提示' || { bad '缺提示'; out; }
|
|
113
|
+
|
|
114
|
+
echo '== ⑨ 基线不存在:明确报错 =='
|
|
115
|
+
R="$TD/r8"; mkrepo "$R"
|
|
116
|
+
rc="$(run "$R" --base origin/nonexistent)"
|
|
117
|
+
[ "$rc" = 1 ] && ok '基线缺失 → rc 1' || bad "应 rc=1(实际 ${rc})"
|
|
118
|
+
see '基线不存在' && ok '有明确报错' || { bad '缺报错文案'; out; }
|
|
119
|
+
|
|
120
|
+
echo '== ⑩ --all:批量扫仓(无基线的仓跳过、不中断)=='
|
|
121
|
+
RR="$TD/reposleyan"; mkdir -p "$RR"
|
|
122
|
+
A="$RR/repoA"; mkrepo "$A"; mkmerged "$A" feat/a-gone
|
|
123
|
+
B="$RR/repoB"; mkdir -p "$B"; git -C "$B" init -q -b main
|
|
124
|
+
printf 'x\n' > "$B/f"; git -C "$B" add -A
|
|
125
|
+
git -C "$B" -c user.email=t@t -c user.name=t commit -qm init # 无 origin/main
|
|
126
|
+
C="$RR/repoC"; mkrepo "$C"; mkmerged "$C" feat/c-gone
|
|
127
|
+
export DIA_WT_REPOS_ROOT="$RR"
|
|
128
|
+
rc="$(run --all)"
|
|
129
|
+
[ "$rc" = 1 ] && ok '--all 有残留 → rc 1' || { bad "应 rc=1(实际 ${rc})"; out; }
|
|
130
|
+
see 'feat/a-gone' && see 'feat/c-gone' && ok '扫到两个仓的残留' || { bad '漏扫'; out; }
|
|
131
|
+
notsee 'feat/b' && ok '无基线仓不产生候选(不误删)' || { bad 'repoB 不应有候选'; out; }
|
|
132
|
+
# ⚠️ 非 quiet 模式下「跳过 repoB」的告警是**预期行为**(让人知道少扫了一个仓);
|
|
133
|
+
# 断言重点 = **不中断**(后面 repoC 仍被扫到)——上面 see 'feat/c-gone' 已覆盖。
|
|
134
|
+
see '跳过' && ok '无基线仓给出跳过说明(不静默)' || { bad '无基线仓应说明'; out; }
|
|
135
|
+
# --all + --apply 一次清完
|
|
136
|
+
rc="$(run --all --apply)"
|
|
137
|
+
[ "$rc" = 0 ] && ok '--all --apply → rc 0' || { bad "应 rc=0(实际 ${rc})"; out; }
|
|
138
|
+
git -C "$A" rev-parse --verify -q refs/heads/feat/a-gone >/dev/null && bad 'a-gone 仍在' || ok 'repoA 已清'
|
|
139
|
+
git -C "$C" rev-parse --verify -q refs/heads/feat/c-gone >/dev/null && bad 'c-gone 仍在' || ok 'repoC 已清'
|
|
140
|
+
unset DIA_WT_REPOS_ROOT
|
|
141
|
+
|
|
142
|
+
echo '== ⑪ upstream 落后于本地(`-d` 误拒的真实根因)=='
|
|
143
|
+
# 复现:分支 push -u 后又提交(远端落后)→ 合进 main → `-d` 会因「未并入 upstream」拒绝,
|
|
144
|
+
# 即使该分支**已完全并入 main**(example-repo fix/format-superbook-cli 的真实情形)。
|
|
145
|
+
R="$TD/r9"; mkrepo "$R"
|
|
146
|
+
git -C "$R" init -q --bare "$TD/remote9.git"
|
|
147
|
+
git -C "$R" remote add origin "$TD/remote9.git"
|
|
148
|
+
git -C "$R" push -q -u origin main 2>/dev/null
|
|
149
|
+
git -C "$R" checkout -q -b feat/upstream-behind main
|
|
150
|
+
printf 'u\n' > "$R/u.txt"; git -C "$R" add -A
|
|
151
|
+
git -C "$R" -c user.email=t@t -c user.name=t commit -qm 'u1'
|
|
152
|
+
git -C "$R" push -q -u origin feat/upstream-behind 2>/dev/null # 远端 = u1
|
|
153
|
+
printf 'u2\n' > "$R/u2.txt"; git -C "$R" add -A
|
|
154
|
+
git -C "$R" -c user.email=t@t -c user.name=t commit -qm 'u2' # 本地再提交 → 远端落后
|
|
155
|
+
git -C "$R" checkout -q main
|
|
156
|
+
git -C "$R" -c user.email=t@t -c user.name=t merge -q --no-ff feat/upstream-behind -m merge
|
|
157
|
+
git -C "$R" update-ref refs/remotes/origin/main refs/heads/main
|
|
158
|
+
# 前置确认:确实「已并入 origin/main」但 upstream 落后
|
|
159
|
+
git -C "$R" merge-base --is-ancestor feat/upstream-behind refs/remotes/origin/main \
|
|
160
|
+
&& ok '前置:分支已并入 origin/main' || bad '前置条件不成立'
|
|
161
|
+
[ "$(git -C "$R" rev-parse feat/upstream-behind)" != "$(git -C "$R" rev-parse origin/feat/upstream-behind)" ] \
|
|
162
|
+
&& ok '前置:远端确实落后' || bad '前置:远端未落后'
|
|
163
|
+
rc="$(run "$R" --apply)"
|
|
164
|
+
git -C "$R" rev-parse --verify -q refs/heads/feat/upstream-behind >/dev/null \
|
|
165
|
+
&& bad 'upstream 落后导致误拒(未修复)' || ok '已正确删除(不再被 upstream 挡住)'
|
|
166
|
+
|
|
167
|
+
echo
|
|
168
|
+
printf '结果:%d 通过,%d 失败\n' "$PASS" "$FAIL"
|
|
169
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# dia wt-route.test.sh —— `dia wt route` 回归测试(R1.7 配置驱动判定,2026-10-16)
|
|
3
|
+
#
|
|
4
|
+
# 覆盖(**为什么是这些**):
|
|
5
|
+
# · 无配置时**绝不放行**:即便命名空间看着像我的,也只能走 MR(不瞎猜"我是谁")
|
|
6
|
+
# · 命名空间推导:配置了 my_namespaces 后,我的仓自动直推
|
|
7
|
+
# · 显式名单:direct 放行 / mr **否决优先**
|
|
8
|
+
# · 个人仓基线 + 数据根本地**并集合并**;数据根配置**可以是软链**
|
|
9
|
+
# · 空列表**不误命中**(`grep -qx ""` 匹配空行是这类脚本的经典假阳性)
|
|
10
|
+
# · 坏 JSON 不崩、无 remote / 非 git 仓 → 信息不足(rc=2,安全侧)
|
|
11
|
+
# · worktree 路径输入 → 归主仓判定(沿用既有行为)
|
|
12
|
+
# 用法:bash scripts/dia wt-route.test.sh (退出码 0 = 全绿)
|
|
13
|
+
set -uo pipefail
|
|
14
|
+
|
|
15
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
WT="$HERE/../bin/dia-wt"
|
|
17
|
+
TD="$(mktemp -d)"
|
|
18
|
+
export DIA_WT_DATA_ROOT="$TD/dr" PERSONAL_AGENT_DIR="$TD/pa"
|
|
19
|
+
mkdir -p "$DIA_WT_DATA_ROOT" "$PERSONAL_AGENT_DIR/config"
|
|
20
|
+
PASS=0; FAIL=0
|
|
21
|
+
ok(){ echo " ✓ $1"; PASS=$((PASS+1)); }
|
|
22
|
+
bad(){ echo " ✗ $1"; FAIL=$((FAIL+1)); }
|
|
23
|
+
out(){ sed 's/^/ | /' "$TD/out.txt"; }
|
|
24
|
+
run(){ env -u DIA_WT_ROUTE_CONFIG bash "$WT" route "$@" >"$TD/out.txt" 2>&1; echo $?; }
|
|
25
|
+
see(){ grep -q "$1" "$TD/out.txt"; }
|
|
26
|
+
setcfg(){ printf '%s\n' "$2" > "$1"; }
|
|
27
|
+
CFG="$DIA_WT_DATA_ROOT/route.json"
|
|
28
|
+
BASE="$PERSONAL_AGENT_DIR/config/yan-route.json"
|
|
29
|
+
|
|
30
|
+
mkrepo(){ # <name> <remote-url>(空 URL ⇒ 不配 remote)
|
|
31
|
+
local d="$TD/$1"; mkdir -p "$d"; git -C "$d" init -q
|
|
32
|
+
[ -n "${2:-}" ] && git -C "$d" remote add origin "$2"
|
|
33
|
+
printf 'x\n' > "$d/f"; git -C "$d" add -A
|
|
34
|
+
git -C "$d" -c user.email=t@t -c user.name=t commit -qm init; printf '%s' "$d"
|
|
35
|
+
}
|
|
36
|
+
MINE="$(mkrepo myrepo git@git.example.com:example-user/myrepo.git)"
|
|
37
|
+
TEAM="$(mkrepo teamrepo https://git.example.com/fed/teamrepo.git)"
|
|
38
|
+
NOREMOTE="$(mkrepo noremote)"
|
|
39
|
+
|
|
40
|
+
echo "== 1. 无配置 + 非我命名空间 ⇒ MR(安全侧)=="
|
|
41
|
+
[ "$(run "$TEAM")" = 1 ] && ok "退出码 1" || { bad "应 1"; out; }
|
|
42
|
+
see "安全侧默认 MR" && ok "给出默认理由" || { bad "缺理由"; out; }
|
|
43
|
+
|
|
44
|
+
echo "== 2. 无配置 + 命名空间看着像我的 ⇒ **仍 MR**(不瞎猜)=="
|
|
45
|
+
[ "$(run "$MINE")" = 1 ] && ok "退出码 1(未因命名空间相似而放行)" || { bad "应 1:不能瞎猜"; out; }
|
|
46
|
+
see "不在 my_namespaces" && ok "说明未配置" || { bad "缺说明"; out; }
|
|
47
|
+
|
|
48
|
+
echo "== 3. 配置 my_namespaces ⇒ 我的仓自动直推 =="
|
|
49
|
+
setcfg "$CFG" '{ "my_namespaces": ["example-user"] }'
|
|
50
|
+
[ "$(run "$MINE")" = 0 ] && ok "退出码 0" || { bad "应 0"; out; }
|
|
51
|
+
see "∈ my_namespaces" && ok "判定来源 = 命名空间" || { bad "来源不对"; out; }
|
|
52
|
+
[ "$(run "$TEAM")" = 1 ] && ok "非我命名空间仍 MR" || { bad "应 1"; out; }
|
|
53
|
+
|
|
54
|
+
echo "== 4. direct 名单 ⇒ 直推(覆盖默认)=="
|
|
55
|
+
setcfg "$CFG" '{ "my_namespaces": ["example-user"], "direct": ["teamrepo"] }'
|
|
56
|
+
[ "$(run "$TEAM")" = 0 ] && ok "退出码 0" || { bad "应 0"; out; }
|
|
57
|
+
see "配置 direct 名单命中" && ok "判定来源 = 配置" || { bad "来源不对"; out; }
|
|
58
|
+
|
|
59
|
+
echo "== 5. mr 名单 ⇒ **否决优先**(即便同时命中 direct)=="
|
|
60
|
+
setcfg "$CFG" '{ "my_namespaces": ["example-user"], "direct": ["teamrepo"], "mr": ["teamrepo"] }'
|
|
61
|
+
[ "$(run "$TEAM")" = 1 ] && ok "退出码 1(否决压过放行)" || { bad "应 1"; out; }
|
|
62
|
+
see "显式否决" && ok "给出否决理由" || { bad "缺理由"; out; }
|
|
63
|
+
|
|
64
|
+
echo "== 6. 个人仓基线 + 数据根 ⇒ 并集合并(判定来源列出两个文件)=="
|
|
65
|
+
setcfg "$CFG" '{ "direct": ["teamrepo"] }'
|
|
66
|
+
setcfg "$BASE" '{ "my_namespaces": ["example-user"], "direct": ["myrepo"] }'
|
|
67
|
+
[ "$(run "$TEAM")" = 0 ] && ok "本地新增生效(teamrepo 直推)" || { bad "本地配置未生效"; out; }
|
|
68
|
+
see "route.json" && ok "来源含数据根文件" || { bad "缺来源"; out; }
|
|
69
|
+
[ "$(run "$MINE")" = 0 ] && ok "基线条目保留(myrepo 直推)" || { bad "基线未生效"; out; }
|
|
70
|
+
see "yan-route.json" && ok "来源含基线文件" || { bad "缺基线来源"; out; }
|
|
71
|
+
[ "$(run "$NOREMOTE")" = 2 ] && ok "无 remote 的仓:信息不足(rc=2)" || { bad "应 2"; out; }
|
|
72
|
+
|
|
73
|
+
echo "== 7. 数据根配置**是软链** ⇒ 生效(指向个人仓版本化文件)=="
|
|
74
|
+
mv "$CFG" "$TD/route-real.json"; ln -s "$TD/route-real.json" "$CFG"
|
|
75
|
+
[ "$(run "$TEAM")" = 0 ] && ok "软链配置被读到" || { bad "软链未生效"; out; }
|
|
76
|
+
rm -f "$CFG"; setcfg "$CFG" '{}'
|
|
77
|
+
|
|
78
|
+
echo "== 8. 空列表**不误命中**(经典假阳性:空串匹配空行)=="
|
|
79
|
+
setcfg "$CFG" '{ "my_namespaces": [], "direct": [], "mr": [] }'
|
|
80
|
+
[ "$(run "$TEAM")" = 1 ] && ok "空名单 ⇒ MR" || { bad "空名单误放行"; out; }
|
|
81
|
+
|
|
82
|
+
echo "== 9. 坏 JSON ⇒ 不崩、按无配置处理并提示 =="
|
|
83
|
+
setcfg "$CFG" '{ this is not json'
|
|
84
|
+
RC="$(run "$TEAM")"
|
|
85
|
+
[ "$RC" = 1 ] && ok "退出码 1(安全侧)" || { bad "应 1(实得 ${RC})"; out; }
|
|
86
|
+
see "JSON 解析失败" && ok "提示解析失败" || { bad "缺解析失败提示"; out; }
|
|
87
|
+
|
|
88
|
+
echo "== 10. \$DIA_WT_ROUTE_CONFIG 显式指定 ⇒ 最高优先(忽略其余)=="
|
|
89
|
+
setcfg "$CFG" '{ "direct": ["myrepo"] }'
|
|
90
|
+
EX="$TD/explicit.json"; setcfg "$EX" '{ "direct": ["teamrepo"] }'
|
|
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; }
|
|
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; }
|
|
95
|
+
rm -f "$EX"
|
|
96
|
+
|
|
97
|
+
echo "== 11. worktree 路径输入 ⇒ 归主仓判定 =="
|
|
98
|
+
setcfg "$CFG" '{ "direct": ["teamrepo"] }'
|
|
99
|
+
git -C "$TEAM" worktree add -q "$TD/teamrepo-worktree--t1" -b t1 >/dev/null 2>&1
|
|
100
|
+
RC="$(run "$TD/teamrepo-worktree--t1")"
|
|
101
|
+
[ "$RC" = 0 ] && ok "worktree 归主仓 → 直推" || { bad "worktree 判定错(rc=${RC})"; out; }
|
|
102
|
+
see "teamrepo" && ok "报出主仓名" || { bad "未报主仓名"; out; }
|
|
103
|
+
|
|
104
|
+
echo "== 12. 非 git 目录 ⇒ 信息不足(rc=2)=="
|
|
105
|
+
mkdir -p "$TD/plain"
|
|
106
|
+
[ "$(run "$TD/plain")" = 2 ] && ok "退出码 2" || { bad "应 2"; out; }
|
|
107
|
+
see "不是 git 仓" && ok "给出理由" || { bad "缺理由"; out; }
|
|
108
|
+
|
|
109
|
+
echo
|
|
110
|
+
echo "── PASS $PASS · FAIL $FAIL ──"
|
|
111
|
+
rm -rf "$TD"
|
|
112
|
+
[ "$FAIL" = 0 ]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# dia wt-scope.test.sh —— `dia wt scope` 的回归测试(G2,2026-10-15)
|
|
3
|
+
#
|
|
4
|
+
# 覆盖:git 仓 ⇒ 派生 worktree 同级;**非 git 目录 ⇒ 不派生**(用户 2026-10-15 关键要求);
|
|
5
|
+
# worktree 路径 ⇒ 归主仓且 mainCheckout=false;scope.json 合法且原子;--show;
|
|
6
|
+
# ⚠️ 与 yan 权限层(core/perm/scope.mjs)的串联判定已移出本包——那是 yan 域,不是 dia wt 的契约。
|
|
7
|
+
# 用法:bash scripts/dia wt-scope.test.sh (退出码 0 = 全绿)
|
|
8
|
+
set -uo pipefail
|
|
9
|
+
|
|
10
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
WT="$HERE/../bin/dia-wt"
|
|
12
|
+
|
|
13
|
+
TD="$(mktemp -d)"; export DIA_WT_DATA_ROOT="$TD/dr"
|
|
14
|
+
PASS=0; FAIL=0
|
|
15
|
+
ok(){ echo " ✓ $1"; PASS=$((PASS+1)); }
|
|
16
|
+
bad(){ echo " ✗ $1"; FAIL=$((FAIL+1)); }
|
|
17
|
+
check(){ [ "$2" = "$3" ] && ok "$1(=$2)" || bad "$1:期望 $3,实得 $2"; }
|
|
18
|
+
jq_get(){ python3 -c "import json,sys;print(json.load(open(sys.argv[1])).get(sys.argv[2]))" "$1" "$2"; }
|
|
19
|
+
|
|
20
|
+
echo "== 1. git 仓库 ⇒ 派生 worktree 同级目录 =="
|
|
21
|
+
mkdir -p "$TD/repoA" && git -C "$TD/repoA" init -q
|
|
22
|
+
bash "$WT" scope "$TD/repoA" --feature demo >/dev/null
|
|
23
|
+
S_DIR="$DIA_WT_DATA_ROOT/tasks/scopes"
|
|
24
|
+
scope_of() { echo "$S_DIR/$(basename "$1").json"; }
|
|
25
|
+
S="$(scope_of "$TD/repoA")"
|
|
26
|
+
check "gitRepo" "$(jq_get "$S" gitRepo)" "True"
|
|
27
|
+
check "mainCheckout" "$(jq_get "$S" mainCheckout)" "True"
|
|
28
|
+
check "sibling 派生" "$(jq_get "$S" siblingWorktreeGlob)" "$TD/repoA-worktree--*"
|
|
29
|
+
check "writable 条目数" "$(python3 -c "import json;print(len(json.load(open('$S'))['writable']))")" "2"
|
|
30
|
+
|
|
31
|
+
echo "== 2. 非 git 目录 ⇒ **不派生**(关键回归:不会串到兄弟目录)=="
|
|
32
|
+
mkdir -p "$TD/plain/sub"
|
|
33
|
+
bash "$WT" scope "$TD/plain" >/dev/null
|
|
34
|
+
S="$(scope_of "$TD/plain")"
|
|
35
|
+
check "gitRepo=false" "$(jq_get "$S" gitRepo)" "False"
|
|
36
|
+
check "sibling 为空" "$(jq_get "$S" siblingWorktreeGlob)" "None"
|
|
37
|
+
check "writable 仅自身" "$(python3 -c "import json;print(len(json.load(open('$S'))['writable']))")" "1"
|
|
38
|
+
|
|
39
|
+
echo "== 3. worktree 路径 ⇒ 归主仓 + mainCheckout=false =="
|
|
40
|
+
git -C "$TD/repoA" worktree add -q "$TD/repoA-worktree--t1" -b t1 2>/dev/null
|
|
41
|
+
bash "$WT" scope "$TD/repoA-worktree--t1" --feature t1 >/dev/null
|
|
42
|
+
S="$(scope_of "$TD/repoA-worktree--t1")"
|
|
43
|
+
check "repo 归到主仓" "$(jq_get "$S" repo)" "$TD/repoA"
|
|
44
|
+
check "active 是 worktree" "$(jq_get "$S" active)" "$TD/repoA-worktree--t1"
|
|
45
|
+
check "mainCheckout=false" "$(jq_get "$S" mainCheckout)" "False"
|
|
46
|
+
|
|
47
|
+
echo "== 4. --show 按 cwd 打印适用声明 =="
|
|
48
|
+
# ⚠️ 2026-10-16 起 --show 按 **cwd** 选适用作用域 ⇒ 必须在被覆盖的目录里跑
|
|
49
|
+
out4="$(cd "$TD/plain" && bash "$WT" scope --show 2>/dev/null)"
|
|
50
|
+
echo "$out4" | grep -q '"repo"' && ok "--show 输出 JSON" || bad "--show 无输出(cwd=$TD/plain)"
|
|
51
|
+
echo "$out4" | python3 -c "import json,sys;json.load(sys.stdin)" && ok "--show 输出是合法 JSON" || bad "--show 不是合法 JSON"
|
|
52
|
+
|
|
53
|
+
echo "== 5. (跳过:yan 权限层串联判定归属 yan 域,不在 dia wt 契约内)=="
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
echo "== 6. 并发不覆盖:两个 worktree 各一份 scope(2026-10-16 修)=="
|
|
57
|
+
bash "$WT" scope "$TD/repoA" --feature A >/dev/null
|
|
58
|
+
bash "$WT" scope "$TD/plain" --feature B >/dev/null
|
|
59
|
+
n="$(ls -1 "$DIA_WT_DATA_ROOT/tasks/scopes"/*.json 2>/dev/null | wc -l | tr -d ' ')"
|
|
60
|
+
[ "$n" -ge 2 ] && ok "两份 scope 共存($n 份)" || bad "应至少 2 份,实得 $n"
|
|
61
|
+
check "A 的 scope 未被 B 覆盖(repo)" "$(jq_get "$(scope_of "$TD/repoA")" repo)" "$TD/repoA"
|
|
62
|
+
check "B 的 scope 也独立存在" "$(jq_get "$(scope_of "$TD/plain")" repo)" "$TD/plain"
|
|
63
|
+
(cd "$TD/repoA" && bash "$WT" scope --show | python3 -c "import json,sys;d=json.load(sys.stdin);print(d['repo'])") > /tmp/showcwd.txt
|
|
64
|
+
check "按 cwd 选用正确" "$(cat /tmp/showcwd.txt)" "$TD/repoA"
|
|
65
|
+
|
|
66
|
+
echo
|
|
67
|
+
echo "──────── 结果:$PASS 通过 / $FAIL 失败 ────────"
|
|
68
|
+
rm -rf "$TD"
|
|
69
|
+
[ "$FAIL" -eq 0 ]
|