@minonl/dia 0.0.9 → 0.0.11
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/README.md +12 -0
- package/dist/cli.js +339 -66
- package/docs/index.md +1 -1
- package/docs/msg-collab-m1.md +24 -0
- package/docs/overview.md +6 -0
- package/docs/service-noun.md +38 -4
- package/package.json +1 -1
- package/packages/wt/bin/dia-wt +157 -67
- package/packages/wt/tests/adopt.test.sh +46 -44
package/docs/index.md
CHANGED
|
@@ -31,7 +31,7 @@ Agent 入口:[`llms.txt`](https://github.com/minonl/dia/blob/main/llms.txt)(
|
|
|
31
31
|
| [价目表(tokens → 金额)](pricing.md) | 原则:没有价格就不编造;unpriced 明确记录,预算按 tokens 兜底。 |
|
|
32
32
|
| [目标提案(team next)](proposals.md) | 定位:团队可以提候选,不能定目标。 |
|
|
33
33
|
| [Sandbox 细化设计(dia)](security-sandbox.md) | 依据:dsh 本机源码实测、Hermes/Pi 取证、[/tmp/dia-sbx 实验]。 |
|
|
34
|
-
| [服务(service):可托管的执行体](service-noun.md) |
|
|
34
|
+
| [服务(service):可托管的执行体](service-noun.md) | 状态:已实现(v0.0.10,脚本型 + agent 型)。脚本型走 entry.cmd,agent 型走 entry.agent + healthcheck.kind="acp"; |
|
|
35
35
|
| [dia 状态页(单一事实源)](status.md) | 任何"现在做到哪了"的问题,以本页为准;其他文档是细节。 |
|
|
36
36
|
| [全自动团队:人只在验收介入](team-automation.md) | 目标:计划→实现→验证→对抗→修复全自动循环;人只在验收阶段拍板。 |
|
|
37
37
|
| [团队章程:谁来做 dia(开发团队)](team.md) | 2026-10-28 升级:开发团队执行模式改为全自动循环(计划→实现→验证→对抗→修复),人只在验收阶段介入; |
|
package/docs/msg-collab-m1.md
CHANGED
|
@@ -78,3 +78,27 @@ dia graph --tui --no-color # 关 ANSI 颜色
|
|
|
78
78
|
- 按状态分组显示 task → deps / touches / blocked_by;claims 单列;
|
|
79
79
|
- 不启 HTTP/daemon,遵守框架“无网络模块”约束;
|
|
80
80
|
- `--task t-...` 只显示该任务的直接邻居。
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
# handoff + claim 联动(M2)
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
dia claim src/collab.ts --reason "正在重构"
|
|
88
|
+
dia out --to ses_xxx --kind handoff --subject "交接" --body-file handoff.md \
|
|
89
|
+
--touches src/collab.ts,docs/msg-collab-m1.md
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- `--touches a,b`(也接受重复 `--touch`)写入消息 `refs.touches`;
|
|
93
|
+
- 发送时如果有他人活跃 claim,输出 `⚠ claim overlap`(advisory,仍发送);
|
|
94
|
+
- 收件方 `dia in` 会在消息下显示 `⚠ claim: <path> 由 <holder> 持有`;
|
|
95
|
+
- 不自动排队/阻断;claim 仍是可见性工具。
|
|
96
|
+
|
|
97
|
+
## 清理历史(可选)
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
dia claims --gc --days 7
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- 保留活跃 claim + 最近 7 天的记录,删除更早的已过期/已释放事件;
|
|
104
|
+
- 只压缩本地 `claims.jsonl`,不影响任何活跃 claim。
|
package/docs/overview.md
CHANGED
|
@@ -85,6 +85,12 @@ $ dia --persona yan kb scene S1 → 正常出场景卡
|
|
|
85
85
|
|
|
86
86
|
---
|
|
87
87
|
|
|
88
|
+
## 3.1 CI 口径(见 README「CI 矩阵」)
|
|
89
|
+
|
|
90
|
+
- required = `test (ubuntu-latest)` + `test (macos-latest)`(分支保护,`enforce_admins`)
|
|
91
|
+
- `windows-smoke` 非阻塞:Windows 后端未实现(`dia job install` rc=3),落地后再升 required
|
|
92
|
+
- **docs-only 快通道**:只改 `docs/**` / 根 `*.md` / `llms*.txt` 时跳过测试矩阵
|
|
93
|
+
|
|
88
94
|
## 4. 已知缺口(按优先级)
|
|
89
95
|
|
|
90
96
|
1. **yan 安装落后来源仓 1 个提交**(`83fbaa9` vs `8d10cc5`,内容是 dsh profile 根 `cordis.yml` 修复)。
|
package/docs/service-noun.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# 服务(service):可托管的执行体
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> 状态:**已实现(v0.0.10,脚本型 + agent 型)**。脚本型走 `entry.cmd`,agent 型走 `entry.agent` + `healthcheck.kind="acp"`;
|
|
4
|
+
> healthcheck 按专用码域 `0/20/21` 判定(外加 `124`=timeout、信号=crash),旧结果超新鲜度窗口即报 `unknown (stale …)`。
|
|
5
|
+
> **实现状态逐节对照见 §15。**
|
|
4
6
|
> 日期 2026-11-05(rev.2:按 [设计评审 B1–B5 / C1–C7 / D1–D4](review/service-noun-review-2026-11-05.md) 改稿;
|
|
5
|
-
> **rev.3**:按复核意见改「去抖单一写入方 / health 码域 `0/20/21` / timeout 映射 / 手动检查落台账 / adopted 口径」,见 §13
|
|
7
|
+
> **rev.3**:按复核意见改「去抖单一写入方 / health 码域 `0/20/21` / timeout 映射 / 手动检查落台账 / adopted 口径」,见 §13;
|
|
8
|
+
> **rev.4**:回填实现状态(PR #49 / #54 / #60)与 §15 对照表)。
|
|
6
9
|
> 关联:[`cli.md`](cli.md)(名词与动词矩阵 / 退出码)· [`persona-install.md`](persona-install.md)(`dia.json` schema)·
|
|
7
10
|
> [`kit-install.md`](kit-install.md) · [`acp-integration.md`](acp-integration.md)(agent 会话型服务)· `packages/job`(生命周期、状态台账)
|
|
8
11
|
|
|
@@ -243,9 +246,11 @@ unknown(stale)**不单独返回**——`health` 是实时执行,不存在
|
|
|
243
246
|
|
|
244
247
|
- `job` 仍是**唯一调度实现**(不新增第二套);健康检查任务本身就是一个 `job`(`periodic`)。
|
|
245
248
|
- `service` 只多一层「声明 + 校验 + 健康检查」。
|
|
246
|
-
- `dia job status` 的每行增加 `health=<ok|degraded|fail|unknown|n/a> (Ns ago)`(读 §4.5
|
|
249
|
+
- `dia job status` 的每行增加 `health=<ok|degraded|fail|unknown|n/a> (Ns ago)`(读 §4.5 缓存,不执行)。**✅ 已实现(PR #54)**:
|
|
250
|
+
仅对 `local.dia-job-svc-<id>` 行追加,其它行原样(向后兼容)。
|
|
247
251
|
- 对 `service` 型(常驻)任务:Linux 走 systemd `--user`,**常驻需 `loginctl enable-linger`**(`job doctor` 已提示,`dia service add` 一并转述);
|
|
248
252
|
对 `periodic` 任务,cron 后端**没有进程状态**,health 是唯一判据 → 新鲜度条款(§4.6)尤其关键。
|
|
253
|
+
- **性能**:只读台账、零执行;实测 N=50 时 `3.1ms`(阈值 200ms),基准见 `tests/service-health-status.perf.test.ts`。
|
|
249
254
|
|
|
250
255
|
## 7. 边界与异常
|
|
251
256
|
|
|
@@ -355,7 +360,8 @@ unknown(stale)**不单独返回**——`health` 是实时执行,不存在
|
|
|
355
360
|
|
|
356
361
|
## 14. P2:agent 型服务(entry = persona 会话)
|
|
357
362
|
|
|
358
|
-
>
|
|
363
|
+
> 状态:**已实现(PR #60)**。本节定义脚本型之外的 agent 型入口(`entry.agent` + `healthcheck.kind="acp"`);
|
|
364
|
+
> 实现与本节一致,差异见 §15。
|
|
359
365
|
|
|
360
366
|
### 14.1 与脚本型的关系
|
|
361
367
|
|
|
@@ -428,3 +434,31 @@ flowchart LR
|
|
|
428
434
|
- [ ] 单测:`runAcpHealth` 用 `tests/fixtures/fake-acp.mjs` 覆盖 `initialize` 失败 / `session/new` 失败 / 正常三态
|
|
429
435
|
- [ ] 单测:`healthcheck.kind` 缺省时按脚本型(向后兼容)
|
|
430
436
|
- [ ] e2e:装一个 agent 型 demo(fake ACP)→ `dia job status` 显示 `health=ok…`,杀掉端点后变 `degraded(stale)`
|
|
437
|
+
|
|
438
|
+
## 15. 实现状态(2026-11-06 回填)
|
|
439
|
+
|
|
440
|
+
> 规则:**设计先改、代码同批**(R35)。本节是「设计 ↔ 实现」的对照表;后续改动必须同批更新本表。
|
|
441
|
+
|
|
442
|
+
| 设计小节 | 状态 | 实现落点 |
|
|
443
|
+
|---|---|---|
|
|
444
|
+
| §2 构成(entry/lifecycle/contract/healthcheck) | ✅ | `src/service.ts`:`ServiceManifest` / `validateServiceManifest`(PR #49) |
|
|
445
|
+
| §3 清单与校验(entry 规则、`E_MANIFEST`) | ✅ | `checkEntry` / `checkAgentEntry` / `resolveEntry`(#49、#60) |
|
|
446
|
+
| §4.1 码域 `0/20/21` + `124` + 信号 | ✅ | `judgeHealth` / `judgeSpawnResult`;`124` 与 `137/143` 分流(#49;无 `timeout(1)` 的兜底见 #49 后续修复 `29a4341`) |
|
|
447
|
+
| §4.2 执行环境 / `E_BACKEND` | ✅ | `healthShell`、`checkCronInterval`(#49) |
|
|
448
|
+
| §4.3 去抖 + 单一写入方 + `gen` | ✅ | `transition`(`gen` 不同的行不计数)(#49) |
|
|
449
|
+
| §4.4 崩溃语义(保持上次状态 + `last_error`) | ✅ | `judgeSpawnResult` + `transition`(#49) |
|
|
450
|
+
| §4.5 台账(`state.jsonl` + `kind:"health"`) | ✅ | `appendHealthRecord` / `findLastHealth` / `readHealthLines`(#49、#54) |
|
|
451
|
+
| §4.6 新鲜度(`interval × 1.5` → stale) | ✅ | `healthView` / `staleAfter`(#54) |
|
|
452
|
+
| §5 CLI(add/remove/list/show/verify/health) | ✅ | `packages/cli/src/index.ts`(#49);`health` 的 agent 分支(#60) |
|
|
453
|
+
| §6 `dia job status` 追列 health | ✅ | `decorateStatus` + `runJobCapture`(#54) |
|
|
454
|
+
| §7 边界(`E_UNSTABLE_ENTRY` / adopted / three-state) | ✅ | `verify` / `healthView`(#49、#54) |
|
|
455
|
+
| §14 agent 型(`entry.agent` + `kind:"acp"`) | ✅ | `acpAgentCommand` / `runAcpHealth` / `recordHealth`(#60) |
|
|
456
|
+
| §6 性能基准(N=50 ≤200ms) | ✅ | 实测 **3.1ms**(单服务 0.1ms,macOS / bun 1.4.4);`tests/service-health-status.perf.test.ts` 断言 ≤200ms 且不随 N 线性增长 |
|
|
457
|
+
|
|
458
|
+
### 15.1 已知与设计的偏差
|
|
459
|
+
|
|
460
|
+
| 项 | 设计 | 实现 | 处置 |
|
|
461
|
+
|---|---|---|---|
|
|
462
|
+
| `healthcheck.kind` 缺省 | 未写 | 缺省 `script`(向后兼容) | 与 §14.6 一致,已写回 §14 |
|
|
463
|
+
| `dia service health` 的 agent 分支 | §5 未细化 | 先跑 ACP 探测再落 `source:"manual"` 行 | 与 §5/§14 语义一致,无需改设计 |
|
|
464
|
+
| `entry.agent` 与 `healthcheck.kind` 的强绑定 | §14.6 验收项 | 已实现为 `E_MANIFEST`(agent 型必须 acp;acp 只能配 agent 型) | 已写回 §14.2 校验说明 |
|
package/package.json
CHANGED
package/packages/wt/bin/dia-wt
CHANGED
|
@@ -1263,15 +1263,32 @@ exit 0
|
|
|
1263
1263
|
HOOKBODY
|
|
1264
1264
|
}
|
|
1265
1265
|
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
local
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1266
|
+
# simple-git-hooks RC 注入目录(跟随 dia 数据根)
|
|
1267
|
+
wt_hooks_rc_dir() { printf '%s/hooks' "${DIA_ROOT:-${DIA_DATA_DIR:-$HOME/.dia}}"; }
|
|
1268
|
+
# 在 ~/.zshenv 里确保 export KEY=VALUE(幂等;带 marker 便于移除)
|
|
1269
|
+
wt_ensure_env_export() {
|
|
1270
|
+
local key="$1" val="$2" rc="${DIA_WT_SHELL_RC:-$HOME/.zshenv}"
|
|
1271
|
+
local marker="# dia-wt: $key"
|
|
1272
|
+
[ -f "$rc" ] || : > "$rc"
|
|
1273
|
+
grep -q "^$marker" "$rc" 2>/dev/null && return 0
|
|
1274
|
+
printf '\n%s\nexport %s="%s"\n' "$marker" "$key" "$val" >> "$rc"
|
|
1275
|
+
}
|
|
1276
|
+
wt_write_hook() {
|
|
1277
|
+
local hooksdir="$1" name="$2" extra="${3:-}" chain="${4:-}"
|
|
1278
|
+
{
|
|
1279
|
+
printf '#!/usr/bin/env bash\n# dia-wt guard(managed by `dia wt adopt`;勿手改)\n'
|
|
1280
|
+
wt_guard_body
|
|
1281
|
+
} > "$hooksdir/$name"
|
|
1282
|
+
if [ -n "$chain" ]; then
|
|
1283
|
+
python3 - "$hooksdir/$name" "$chain" <<'PYCHAIN'
|
|
1284
|
+
import sys
|
|
1285
|
+
p, chain = sys.argv[1], sys.argv[2]
|
|
1286
|
+
s = open(p).read()
|
|
1287
|
+
s = s.replace('orig="$common/hooks/$name"', 'orig="%s/$name"' % chain.replace('\\', '\\\\').replace('"', '\\"'))
|
|
1288
|
+
open(p, "w").write(s)
|
|
1289
|
+
PYCHAIN
|
|
1290
|
+
fi
|
|
1291
|
+
chmod +x "$hooksdir/$name"
|
|
1275
1292
|
}
|
|
1276
1293
|
|
|
1277
1294
|
cmd_guard() {
|
|
@@ -1329,11 +1346,13 @@ cmd_adopt() {
|
|
|
1329
1346
|
|
|
1330
1347
|
if [ "$mode" = "revert" ]; then
|
|
1331
1348
|
[ -f "$cfg" ] || die "未 adopt(缺 $cfg)"
|
|
1332
|
-
local saved
|
|
1349
|
+
local saved mgr
|
|
1333
1350
|
saved="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksPrevious") or "")' "$cfg" 2>/dev/null || true)"
|
|
1334
|
-
|
|
1335
|
-
if [
|
|
1336
|
-
|
|
1351
|
+
mgr="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksManager") or "")' "$cfg" 2>/dev/null || true)"
|
|
1352
|
+
if [ "$mgr" = "local" ] || [ -z "$mgr" ]; then
|
|
1353
|
+
if [ "$prev" = "$hooksdir" ]; then
|
|
1354
|
+
if [ -n "$saved" ]; then git -C "$repo" config --local core.hooksPath "$saved"; else git -C "$repo" config --local --unset core.hooksPath 2>/dev/null || true; fi
|
|
1355
|
+
fi
|
|
1337
1356
|
fi
|
|
1338
1357
|
rm -rf "$cfgdir"
|
|
1339
1358
|
echo "reverted:$cfgdir"
|
|
@@ -1348,16 +1367,31 @@ cmd_adopt() {
|
|
|
1348
1367
|
local cfgDef; cfgDef="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("defaultBranch") or "")' "$cfg" 2>/dev/null || true)"
|
|
1349
1368
|
[ -n "$cfgDef" ] && def="$cfgDef"
|
|
1350
1369
|
fi
|
|
1351
|
-
local
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
[ "$
|
|
1355
|
-
|
|
1356
|
-
[ -
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1370
|
+
local mgr2; mgr2="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksManager") or "")' "$cfg" 2>/dev/null || true)"
|
|
1371
|
+
if [ "$mgr2" = "external" ]; then
|
|
1372
|
+
echo "WARN D02 $repo 由其它 hook manager 接管(core.hooksPath=$prev),守卫未生效;建议改用 simple-git-hooks"
|
|
1373
|
+
elif [ "$mgr2" = "simple-git-hooks" ]; then
|
|
1374
|
+
local rcf; rcf="$(wt_hooks_rc_dir)/simple-git-hooks-rc.sh"
|
|
1375
|
+
[ -f "$rcf" ] || { echo "FAIL D02 缺 simple-git-hooks RC:$rcf"; bad=1; }
|
|
1376
|
+
grep -rqs "SIMPLE_GIT_HOOKS_RC" "$HOME/.zshenv" || { echo "FAIL D02 shell 未 export SIMPLE_GIT_HOOKS_RC"; bad=1; }
|
|
1377
|
+
python3 - "$repo/package.json" <<'PYM' >/dev/null 2>&1 || { echo "FAIL D02 package.json 未启用 simple-git-hooks"; bad=1; }
|
|
1378
|
+
import json,sys
|
|
1379
|
+
d=json.load(open(sys.argv[1])); c=d.get("simple-git-hooks"); dev=d.get("devDependencies",{}) or {}
|
|
1380
|
+
raise SystemExit(0 if isinstance(c,dict) or "simple-git-hooks" in dev else 1)
|
|
1381
|
+
PYM
|
|
1382
|
+
if [ -n "$def" ] && wt_is_main_checkout "$repo" && [ "$(git -C "$repo" symbolic-ref --short HEAD 2>/dev/null)" = "$def" ]; then
|
|
1383
|
+
( cd "$repo" && DIA_WT_GUARD_PROBE=1 bash "$SELF" guard pre-commit >/dev/null 2>&1 ) \
|
|
1384
|
+
&& { echo "FAIL D03 守卫探针未拦截 $def 提交"; bad=1; }
|
|
1385
|
+
fi
|
|
1386
|
+
else
|
|
1387
|
+
case "$prev" in /*) ;; *) echo "FAIL D02 本地 core.hooksPath 非绝对($prev)"; bad=1 ;; esac
|
|
1388
|
+
for h in pre-commit pre-push post-checkout; do
|
|
1389
|
+
[ -x "$hooksdir/$h" ] || { echo "FAIL D02 缺 $h 钩子"; bad=1; }
|
|
1390
|
+
done
|
|
1391
|
+
if [ -n "$def" ] && wt_is_main_checkout "$repo" && [ "$(git -C "$repo" symbolic-ref --short HEAD 2>/dev/null)" = "$def" ]; then
|
|
1392
|
+
( cd "$repo" && DIA_WT_GUARD_PROBE=1 "$hooksdir/pre-commit" >/dev/null 2>&1 ) \
|
|
1393
|
+
&& { echo "FAIL D03 守卫探针未拦截 $def 提交"; bad=1; }
|
|
1394
|
+
fi
|
|
1361
1395
|
fi
|
|
1362
1396
|
if [ "$json" = 1 ]; then
|
|
1363
1397
|
python3 -c 'import json,sys;print(json.dumps({"repo":sys.argv[1],"ok":sys.argv[2]=="0","defaultBranch":sys.argv[3]},ensure_ascii=False,separators=(",",":")))' "$repo" "$bad" "$def"
|
|
@@ -1383,34 +1417,78 @@ cmd_adopt() {
|
|
|
1383
1417
|
[ -n "$def" ] || die "默认分支不可解析;请 --default-branch main"
|
|
1384
1418
|
mkdir -p "$hooksdir"
|
|
1385
1419
|
# 安装/刷新本机全局钩子,并让本仓走全局钩子(不改 package.json、不占用仓库钩子目录)
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1420
|
+
# ── 方案:每个仓本地钩子;simple-git-hooks 仓改用它的 RC 注入 ──────────────
|
|
1421
|
+
# 不再写任何全局 git 配置(避免外溢)。
|
|
1422
|
+
if [ -f "$repo/package.json" ] && python3 - "$repo/package.json" <<'PYCHK'
|
|
1423
|
+
import json,sys
|
|
1424
|
+
d=json.load(open(sys.argv[1]))
|
|
1425
|
+
cfg=d.get("simple-git-hooks")
|
|
1426
|
+
dev=d.get("devDependencies",{}) or {}
|
|
1427
|
+
raise SystemExit(0 if isinstance(cfg,dict) or "simple-git-hooks" in dev else 1)
|
|
1428
|
+
PYCHK
|
|
1429
|
+
then
|
|
1430
|
+
# simple-git-hooks:钩子由管理器生成到 .git/hooks,我们不碰;通过 SIMPLE_GIT_HOOKS_RC 注入守卫
|
|
1431
|
+
local rcdir rcf
|
|
1432
|
+
rcdir="$(wt_hooks_rc_dir)"; rcf="$rcdir/simple-git-hooks-rc.sh"
|
|
1433
|
+
mkdir -p "$rcdir"
|
|
1434
|
+
cat > "$rcf" <<'RC'
|
|
1435
|
+
# 由 simple-git-hooks 生成的钩子 source 本文件(SIMPLE_GIT_HOOKS_RC)
|
|
1436
|
+
# 只对已 adopt 的仓生效;未 adopt → 直接返回
|
|
1437
|
+
if command -v dia >/dev/null 2>&1; then
|
|
1438
|
+
dia wt guard "$(basename "$0")" || exit $?
|
|
1439
|
+
fi
|
|
1440
|
+
RC
|
|
1441
|
+
wt_ensure_env_export SIMPLE_GIT_HOOKS_RC "$rcf"
|
|
1442
|
+
python3 - "$cfg" "$repo" "$def" "$template" "$prev" <<'PYG'
|
|
1443
|
+
import json, sys, datetime
|
|
1444
|
+
cfg, repo, br, tmpl, prev = sys.argv[1:6]
|
|
1445
|
+
json.dump({
|
|
1446
|
+
"schema": 2, "repo": repo, "defaultBranch": br, "template": tmpl,
|
|
1447
|
+
"hooksManager": "simple-git-hooks", "hooksPrevious": prev or None,
|
|
1448
|
+
"adoptedAt": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
1449
|
+
}, open(cfg, "w"), ensure_ascii=False, indent=1)
|
|
1450
|
+
PYG
|
|
1451
|
+
echo "adopted:$repo(hook manager = simple-git-hooks;守卫经 SIMPLE_GIT_HOOKS_RC 注入)"
|
|
1452
|
+
return 0
|
|
1402
1453
|
fi
|
|
1403
|
-
|
|
1404
|
-
|
|
1454
|
+
|
|
1455
|
+
# 其它仓:自己管 <common>/dia/hooks,并把本地 core.hooksPath 指过去。
|
|
1456
|
+
# 若该仓已有**别人**的本地 hooksPath(husky/.githooks 等),不抢占:只报告,交由转为 simple-git-hooks 处理。
|
|
1457
|
+
if [ -n "$prev" ] && [ "$prev" != "$hooksdir" ]; then
|
|
1458
|
+
python3 - "$cfg" "$repo" "$def" "$template" "$prev" <<'PYG'
|
|
1405
1459
|
import json, sys, datetime
|
|
1406
1460
|
cfg, repo, br, tmpl, prev = sys.argv[1:6]
|
|
1407
1461
|
json.dump({
|
|
1408
1462
|
"schema": 2, "repo": repo, "defaultBranch": br, "template": tmpl,
|
|
1409
|
-
"hooksManager": "
|
|
1463
|
+
"hooksManager": "external", "hooksPrevious": prev,
|
|
1464
|
+
"adoptedAt": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
1465
|
+
}, open(cfg, "w"), ensure_ascii=False, indent=1)
|
|
1466
|
+
PYG
|
|
1467
|
+
echo "[dia-wt] ⚠️ $repo 的 core.hooksPath=$prev 由其它管理器管理,未接管(守卫不生效);建议改用 simple-git-hooks(见 docs/worktree.md)" >&2
|
|
1468
|
+
return 0
|
|
1469
|
+
fi
|
|
1470
|
+
mkdir -p "$hooksdir"
|
|
1471
|
+
local savedPrev=""
|
|
1472
|
+
[ -f "$cfg" ] && savedPrev="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksPrevious") or "")' "$cfg" 2>/dev/null || true)"
|
|
1473
|
+
[ "$prev" = "$hooksdir" ] && prev="$savedPrev"
|
|
1474
|
+
local chain="$prev"
|
|
1475
|
+
[ -n "$chain" ] || chain="$common/hooks"
|
|
1476
|
+
[ -f "$cfg" ] && cp "$cfg" "$cfgdir/config.json.bak.$(date +%s)"
|
|
1477
|
+
wt_write_hook "$hooksdir" pre-commit "$def" "$chain"
|
|
1478
|
+
wt_write_hook "$hooksdir" pre-push "$def" "$chain"
|
|
1479
|
+
wt_write_hook "$hooksdir" post-checkout "$def" "$chain"
|
|
1480
|
+
git -C "$repo" config --local core.hooksPath "$hooksdir"
|
|
1481
|
+
python3 - "$cfg" "$repo" "$def" "$template" "$prev" "$chain" <<'PYG'
|
|
1482
|
+
import json, sys, datetime
|
|
1483
|
+
cfg, repo, br, tmpl, prev, chain = sys.argv[1:7]
|
|
1484
|
+
json.dump({
|
|
1485
|
+
"schema": 2, "repo": repo, "defaultBranch": br, "template": tmpl,
|
|
1486
|
+
"hooksManager": "local", "hooksPath": cfg.rsplit("/",1)[0] + "/hooks",
|
|
1487
|
+
"hooksPrevious": prev or None, "hooksChain": chain,
|
|
1410
1488
|
"adoptedAt": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
1411
1489
|
}, open(cfg, "w"), ensure_ascii=False, indent=1)
|
|
1412
1490
|
PYG
|
|
1413
|
-
echo "adopted:$repo(守卫 =
|
|
1491
|
+
echo "adopted:$repo(守卫 = 仓库本地钩子 $hooksdir)"
|
|
1414
1492
|
return 0
|
|
1415
1493
|
# 保留首次 adopt 记下的 hooksPrevious(重复 apply 时 prev 已是我们自己的 hooksdir)
|
|
1416
1494
|
local savedPrev=""
|
|
@@ -1467,13 +1545,17 @@ cmd_doctor() {
|
|
|
1467
1545
|
fi
|
|
1468
1546
|
[ -n "$def" ] || def="$(wt_default_branch "$repo")"
|
|
1469
1547
|
# --fix:无损修复(重写三个守卫钩子 + hooksPath 绝对路径)。只对已 adopt 的仓生效。
|
|
1470
|
-
if [ "$fix" = 1 ] && [ -f "$cfg" ]; then
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1548
|
+
if [ "$fix" = 1 ] && [ -f "$cfg" ] && [ "$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksManager") or "")' "$cfg" 2>/dev/null || true)" = "simple-git-hooks" ]; then
|
|
1549
|
+
local rcdir rcf; rcdir="$(wt_hooks_rc_dir)"; rcf="$rcdir/simple-git-hooks-rc.sh"
|
|
1550
|
+
mkdir -p "$rcdir"
|
|
1551
|
+
cat > "$rcf" <<'RC'
|
|
1552
|
+
# 由 simple-git-hooks 生成的钩子 source 本文件(SIMPLE_GIT_HOOKS_RC)
|
|
1553
|
+
if command -v dia >/dev/null 2>&1; then
|
|
1554
|
+
dia wt guard "$(basename "$0")" || exit $?
|
|
1555
|
+
fi
|
|
1556
|
+
RC
|
|
1557
|
+
wt_ensure_env_export SIMPLE_GIT_HOOKS_RC "$rcf"
|
|
1558
|
+
[ "$json" = 0 ] && echo "FIX $repo simple-git-hooks RC 已重建"
|
|
1477
1559
|
elif [ "$fix" = 1 ] && [ -f "$cfg" ]; then
|
|
1478
1560
|
local fixChain=""
|
|
1479
1561
|
fixChain="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksChain") or "")' "$cfg" 2>/dev/null || true)"
|
|
@@ -1489,25 +1571,33 @@ cmd_doctor() {
|
|
|
1489
1571
|
local bad=0 mcfg=""
|
|
1490
1572
|
[ -f "$cfg" ] || { echo "FAIL D01 $repo 未 adopt"; bad=1; }
|
|
1491
1573
|
[ -f "$cfg" ] && mcfg="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksManager") or "")' "$cfg" 2>/dev/null || true)"
|
|
1492
|
-
local
|
|
1493
|
-
ghd="$(wt_global_hooks_dir)"
|
|
1574
|
+
local ghp mgr2
|
|
1494
1575
|
ghp="$(git config --global --get core.hooksPath 2>/dev/null || true)"
|
|
1495
|
-
# D20
|
|
1496
|
-
[ "$ghp"
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
done
|
|
1500
|
-
# D21 仓库级 hooksPath 外溢:本地设置会覆盖全局(husky/.githooks 等)
|
|
1501
|
-
if [ -n "$prev" ]; then
|
|
1502
|
-
if [ "$prev" = "$hooksdir" ]; then
|
|
1503
|
-
echo "FAIL D21 $repo 旧版本地 hooksPath 残留($prev);跑 dia wt adopt --apply 迁移到全局钩子"; bad=1
|
|
1504
|
-
else
|
|
1505
|
-
echo "WARN D21 $repo 本地 hooksPath=$prev 覆盖全局钩子,本仓守卫不生效"
|
|
1506
|
-
fi
|
|
1576
|
+
# D20 全局配置残留:旧版把守卫装到全局 hooksPath;现在改为每仓本地,必须清掉
|
|
1577
|
+
if [ -n "$ghp" ] && [ -f "$ghp/pre-commit" ] && grep -q "dia-wt guard" "$ghp/pre-commit" 2>/dev/null; then
|
|
1578
|
+
echo "FAIL D20 全局 core.hooksPath 仍指向 dia 旧钩子目录($ghp);执行 git config --global --unset core.hooksPath"
|
|
1579
|
+
bad=1
|
|
1507
1580
|
fi
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1581
|
+
mgr2="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksManager") or "")' "$cfg" 2>/dev/null || true)"
|
|
1582
|
+
if [ "$mgr2" = "external" ]; then
|
|
1583
|
+
echo "WARN D02 $repo 由其它 hook manager 接管(core.hooksPath=$prev),守卫未生效;建议改用 simple-git-hooks"
|
|
1584
|
+
elif [ "$mgr2" = "simple-git-hooks" ]; then
|
|
1585
|
+
local rcf; rcf="$(wt_hooks_rc_dir)/simple-git-hooks-rc.sh"
|
|
1586
|
+
[ -f "$rcf" ] || { echo "FAIL D02 $repo 缺 simple-git-hooks RC:$rcf"; bad=1; }
|
|
1587
|
+
grep -rqs "SIMPLE_GIT_HOOKS_RC" "$HOME/.zshenv" || echo "WARN D02 $repo shell 未 export SIMPLE_GIT_HOOKS_RC(守卫不会执行)"
|
|
1588
|
+
if [ -n "$def" ] && wt_is_main_checkout "$repo" && [ "$(git -C "$repo" symbolic-ref --short HEAD 2>/dev/null)" = "$def" ]; then
|
|
1589
|
+
( cd "$repo" && DIA_WT_GUARD_PROBE=1 bash "$SELF" guard pre-commit >/dev/null 2>&1 ) \
|
|
1590
|
+
&& { echo "FAIL D03 $repo 守卫探针未拦截 $def 提交"; bad=1; }
|
|
1591
|
+
fi
|
|
1592
|
+
else
|
|
1593
|
+
case "$prev" in /*) ;; *) echo "FAIL D02 $repo 本地 core.hooksPath 非绝对($prev)"; bad=1 ;; esac
|
|
1594
|
+
for h in pre-commit pre-push post-checkout; do
|
|
1595
|
+
[ -x "$hooksdir/$h" ] || { echo "FAIL D02 $repo 缺 $h"; bad=1; }
|
|
1596
|
+
done
|
|
1597
|
+
if [ -n "$def" ] && wt_is_main_checkout "$repo" && [ "$(git -C "$repo" symbolic-ref --short HEAD 2>/dev/null)" = "$def" ]; then
|
|
1598
|
+
( cd "$repo" && DIA_WT_GUARD_PROBE=1 "$hooksdir/pre-commit" >/dev/null 2>&1 ) \
|
|
1599
|
+
&& { echo "FAIL D03 $repo 守卫未拦截 $def 提交"; bad=1; }
|
|
1600
|
+
fi
|
|
1511
1601
|
fi
|
|
1512
1602
|
if wt_is_main_checkout "$repo"; then
|
|
1513
1603
|
[ -z "$(git -C "$repo" status --porcelain 2>/dev/null)" ] || echo "WARN D04 $repo 主 checkout 有未提交改动"
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# adopt / doctor / gate 回归测试(2026-11-06
|
|
3
|
-
# 覆盖:dry-run、apply(全局钩子)、check/doctor、主 checkout 守卫、链式调用仓库钩子、
|
|
4
|
-
# 非 adopt 仓透传、敏感扫描、adopt --all、gate、revert。
|
|
2
|
+
# adopt / doctor / gate 回归测试(2026-11-06:每仓本地钩子 + simple-git-hooks RC)
|
|
5
3
|
set -uo pipefail
|
|
6
4
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
5
|
WT="$HERE/../bin/dia-wt"
|
|
@@ -9,29 +7,29 @@ TD="$(mktemp -d)"; trap 'rm -rf "$TD"' EXIT
|
|
|
9
7
|
PASS=0; FAIL=0
|
|
10
8
|
ok(){ echo " ✓ $1"; PASS=$((PASS+1)); }; bad(){ echo " ✗ $1"; FAIL=$((FAIL+1)); }
|
|
11
9
|
|
|
12
|
-
#
|
|
10
|
+
# 隔离 HOME / 全局 git 配置 / 数据根 / shell rc(避免污染真实机器)
|
|
13
11
|
export HOME="$TD/home"; mkdir -p "$HOME"
|
|
14
12
|
export GIT_CONFIG_GLOBAL="$TD/gitconfig"; : > "$GIT_CONFIG_GLOBAL"
|
|
15
13
|
export GIT_CONFIG_NOSYSTEM=1
|
|
16
|
-
export
|
|
17
|
-
|
|
14
|
+
export DIA_ROOT="$TD/dia"
|
|
15
|
+
export DIA_WT_SHELL_RC="$HOME/.zshenv"
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
git -C "$R" config user.email t@example.com; git -C "$R" config user.name t
|
|
21
|
-
echo a > "$R/a.txt"; git -C "$R" add -A; git -C "$R" commit -qm init
|
|
17
|
+
mk_repo(){ local r="$1"; mkdir -p "$r"; git -C "$r" init -q -b main; git -C "$r" config user.email t@example.com; git -C "$r" config user.name t; echo a > "$r/a.txt"; git -C "$r" add -A; git -C "$r" commit -qm init; }
|
|
22
18
|
|
|
19
|
+
R="$TD/repo"; mk_repo "$R"
|
|
23
20
|
out="$(bash "$WT" adopt "$R" --json)"
|
|
24
21
|
printf '%s' "$out" | grep -q '"defaultBranch":"main"' && ok "dry-run 识别默认分支" || bad "dry-run: $out"
|
|
25
22
|
[ ! -f "$R/.git/dia/config.json" ] && ok "dry-run 不落盘" || bad "dry-run 落盘了"
|
|
26
23
|
|
|
27
24
|
bash "$WT" adopt "$R" --apply >/dev/null 2>&1 || bad "apply 失败"
|
|
28
25
|
[ -f "$R/.git/dia/config.json" ] && ok "config 已写" || bad "缺 config"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
python3 -c 'import json,sys;d=json.load(open(sys.argv[1]));sys.exit(0 if d.get("hooksManager")=="
|
|
34
|
-
&& ok "config 记录 hooksManager=
|
|
26
|
+
hp="$(git -C "$R" config --local --get core.hooksPath)"
|
|
27
|
+
case "$hp" in */repo/.git/dia/hooks) ok "仓库本地 hooksPath 指向 dia 目录" ;; *) bad "hooksPath=$hp" ;; esac
|
|
28
|
+
for h in pre-commit pre-push post-checkout; do [ -x "$R/.git/dia/hooks/$h" ] || bad "缺 $h"; done
|
|
29
|
+
ok "三个钩子可执行"
|
|
30
|
+
python3 -c 'import json,sys;d=json.load(open(sys.argv[1]));sys.exit(0 if d.get("hooksManager")=="local" else 1)' "$R/.git/dia/config.json" \
|
|
31
|
+
&& ok "config 记录 hooksManager=local" || bad "hooksManager 未记录"
|
|
32
|
+
[ -z "$(git config --global --get core.hooksPath 2>/dev/null || true)" ] && ok "未写全局 git 配置" || bad "仍写了全局 hooksPath"
|
|
35
33
|
|
|
36
34
|
bash "$WT" adopt "$R" --apply >/dev/null 2>&1 && ok "重复 apply 幂等" || bad "重复 apply 失败"
|
|
37
35
|
bash "$WT" adopt "$R" --check >/dev/null 2>&1 && ok "check 通过" || bad "check 失败"
|
|
@@ -41,51 +39,55 @@ echo b > "$R/b.txt"; git -C "$R" add b.txt
|
|
|
41
39
|
if git -C "$R" commit -qm should-block 2>/dev/null; then bad "主 checkout main 提交未被拦截"; else ok "主 checkout main 提交被拦截"; fi
|
|
42
40
|
git -C "$R" reset -q
|
|
43
41
|
|
|
44
|
-
# 链式:仓库自己的 .git/hooks/pre-commit
|
|
45
|
-
printf '#!/bin/sh\necho chained >> "$(git rev-parse --show-toplevel)/chain.log"\n' > "$R/.git/hooks/pre-commit"
|
|
46
|
-
chmod +x "$R/.git/hooks/pre-commit"
|
|
42
|
+
# 链式:仓库自己的 .git/hooks/pre-commit 仍要执行
|
|
43
|
+
printf '#!/bin/sh\necho chained >> "$(git rev-parse --show-toplevel)/chain.log"\n' > "$R/.git/hooks/pre-commit"; chmod +x "$R/.git/hooks/pre-commit"
|
|
47
44
|
git -C "$R" worktree add -q -b feat/y "$TD/repo-worktree--y" main
|
|
48
45
|
echo c > "$TD/repo-worktree--y/c.txt"; git -C "$TD/repo-worktree--y" add c.txt
|
|
49
46
|
git -C "$TD/repo-worktree--y" commit -qm feat-y >/dev/null 2>&1
|
|
50
47
|
[ -f "$TD/repo-worktree--y/chain.log" ] && ok "仓库自己的钩子被链式执行" || bad "仓库钩子未执行"
|
|
51
48
|
rm -f "$TD/repo-worktree--y/chain.log"
|
|
52
49
|
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
bash "$WT" adopt "$
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
# simple-git-hooks 仓:走 RC 注入,不碰仓内文件与 .git/hooks
|
|
51
|
+
RN="$TD/npmrepo"; mk_repo "$RN"
|
|
52
|
+
printf '{"name":"r","devDependencies":{"simple-git-hooks":"^2.11.1"},"simple-git-hooks":{"pre-commit":"npx lint-staged"}}\n' > "$RN/package.json"
|
|
53
|
+
git -C "$RN" add -A; git -C "$RN" commit -qm pkg
|
|
54
|
+
before="$(md5 -q "$RN/package.json" 2>/dev/null || md5sum "$RN/package.json" | cut -d' ' -f1)"
|
|
55
|
+
bash "$WT" adopt "$RN" --apply >/dev/null 2>&1 || bad "npm 仓 adopt 失败"
|
|
56
|
+
after="$(md5 -q "$RN/package.json" 2>/dev/null || md5sum "$RN/package.json" | cut -d' ' -f1)"
|
|
57
|
+
[ "$before" = "$after" ] && ok "不修改仓库 package.json" || bad "package.json 被改动"
|
|
58
|
+
[ -z "$(git -C "$RN" config --local --get core.hooksPath || true)" ] && ok "不占用仓库 hooksPath(交给管理器)" || bad "仍设置了 hooksPath"
|
|
59
|
+
[ -f "$DIA_ROOT/hooks/simple-git-hooks-rc.sh" ] && ok "写入 simple-git-hooks RC" || bad "缺 RC 文件"
|
|
60
|
+
grep -q "SIMPLE_GIT_HOOKS_RC" "$HOME/.zshenv" 2>/dev/null && ok "shell rc 注入 SIMPLE_GIT_HOOKS_RC" || bad "未注入环境变量"
|
|
61
|
+
bash "$WT" doctor "$RN" >/dev/null 2>&1 && ok "npm 仓 doctor 通过" || bad "npm 仓 doctor 失败"
|
|
62
|
+
(cd "$RN" && DIA_WT_GUARD_PROBE=1 bash "$WT" guard pre-commit >/dev/null 2>&1) && bad "guard 未拦截 main 提交" || ok "guard 拦截 main 提交"
|
|
61
63
|
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
# 全局配置残留检查:旧版全局钩子目录 → doctor FAIL
|
|
65
|
+
LEGACY="$TD/legacy-hooks"; mkdir -p "$LEGACY"
|
|
66
|
+
printf '#!/usr/bin/env bash\n# dia-wt guard(旧)\nexit 0\n' > "$LEGACY/pre-commit"; chmod +x "$LEGACY/pre-commit"
|
|
67
|
+
git config --global core.hooksPath "$LEGACY"
|
|
68
|
+
bash "$WT" doctor "$R" >/tmp/d20.log 2>&1; grep -q "D20" /tmp/d20.log && ok "doctor 报全局钩子残留" || bad "doctor 未报全局残留: $(head -3 /tmp/d20.log | tr "\n" " ")"
|
|
69
|
+
git config --global --unset core.hooksPath
|
|
67
70
|
|
|
68
|
-
#
|
|
71
|
+
# 未 adopt 的仓不受影响
|
|
72
|
+
R3="$TD/repo3"; mk_repo "$R3"; echo z > "$R3/z.txt"; git -C "$R3" add -A
|
|
73
|
+
git -C "$R3" commit -qm ok >/dev/null 2>&1 && ok "未 adopt 仓不受守卫影响" || bad "未 adopt 仓被误拦"
|
|
74
|
+
|
|
75
|
+
# 敏感扫描(worktree push)
|
|
69
76
|
BR="$TD/remote.git"; git init -q --bare "$BR"
|
|
70
77
|
git -C "$R" remote add origin "$BR" 2>/dev/null || true
|
|
71
78
|
git --git-dir="$BR" fetch -q "$R" main:main 2>/dev/null || true
|
|
72
79
|
git -C "$R" fetch -q origin 2>/dev/null || true
|
|
73
80
|
BAD="$(printf '/%s/alice/secret' Users)"; echo "$BAD" > "$TD/repo-worktree--y/bad.txt"
|
|
74
|
-
git -C "$TD/repo-worktree--y" add bad.txt
|
|
75
|
-
git -C "$TD/repo-worktree--y" commit -qm bad >/dev/null 2>&1
|
|
81
|
+
git -C "$TD/repo-worktree--y" add bad.txt; git -C "$TD/repo-worktree--y" commit -qm bad >/dev/null 2>&1
|
|
76
82
|
if git -C "$TD/repo-worktree--y" push -q origin feat/y 2>/dev/null; then bad "敏感推送未被拦截"; else ok "敏感推送被拦截"; fi
|
|
77
|
-
git -C "$TD/repo-worktree--y" reset -q --hard HEAD~1
|
|
83
|
+
git -C "$TD/repo-worktree--y" reset -q --hard HEAD~1
|
|
78
84
|
printf 'path=$TD/home/kits/dia.json\n' > "$TD/repo-worktree--y/vars.txt"
|
|
79
85
|
git -C "$TD/repo-worktree--y" add vars.txt && git -C "$TD/repo-worktree--y" commit -qm vars >/dev/null 2>&1
|
|
80
86
|
git -C "$TD/repo-worktree--y" push -q origin feat/y 2>/dev/null && ok "相对片段 home/ 不误报" || bad "相对片段被误判"
|
|
81
87
|
|
|
82
|
-
# adopt --all
|
|
88
|
+
# adopt --all
|
|
83
89
|
AR="$TD/allroot"; mkdir -p "$AR/a" "$AR/b"
|
|
84
|
-
for r in a b; do
|
|
85
|
-
git -C "$AR/$r" init -q -b main
|
|
86
|
-
git -C "$AR/$r" config user.email t@example.com; git -C "$AR/$r" config user.name t
|
|
87
|
-
echo x > "$AR/$r/x.txt"; git -C "$AR/$r" add -A; git -C "$AR/$r" commit -qm init
|
|
88
|
-
done
|
|
90
|
+
for r in a b; do mk_repo "$AR/$r"; done
|
|
89
91
|
git -C "$AR/a" worktree add -q -b feat/w "$AR/a-worktree--w" main
|
|
90
92
|
out="$(DIA_WT_REPOS_ROOT="$AR" bash "$WT" adopt --all --apply --json 2>&1)"
|
|
91
93
|
printf '%s' "$out" | grep -q '"repos":2,"ok":2,"fail":0' && ok "adopt --all 覆盖 2 个主 checkout" || bad "adopt --all: $out"
|
|
@@ -97,16 +99,16 @@ DIA_WT_REPOS_ROOT="$TD/empty-root" bash "$WT" adopt --all >/dev/null 2>&1
|
|
|
97
99
|
|
|
98
100
|
# gate
|
|
99
101
|
rm -f "$TD/repo-worktree--y/chain.log"
|
|
100
|
-
if bash "$WT" gate "$R" y >/tmp/gate.log 2>&1; then ok "gate 成功"; else bad "gate 失败: $(tail -2 /tmp/gate.log | tr
|
|
102
|
+
if bash "$WT" gate "$R" y >/tmp/gate.log 2>&1; then ok "gate 成功"; else bad "gate 失败: $(tail -2 /tmp/gate.log | tr '\n' ' ')"; fi
|
|
101
103
|
if bash "$WT" gate "$R" yb >/dev/null 2>&1; then bad "gate 子串误匹配"; else ok "gate 精确匹配 feature"; fi
|
|
102
104
|
echo dirty >> "$TD/repo-worktree--y/c.txt"
|
|
103
105
|
if bash "$WT" gate "$R" y >/dev/null 2>&1; then bad "worktree 脏时 gate 通过"; else ok "worktree 脏时 gate 失败"; fi
|
|
104
106
|
git -C "$TD/repo-worktree--y" checkout -q -- c.txt
|
|
105
107
|
|
|
106
|
-
# revert
|
|
108
|
+
# revert
|
|
107
109
|
bash "$WT" adopt "$R" --revert >/dev/null 2>&1 || bad "revert 失败"
|
|
108
110
|
[ ! -f "$R/.git/dia/config.json" ] && ok "revert 删除本仓配置" || bad "配置残留"
|
|
109
|
-
[ -
|
|
111
|
+
[ -z "$(git -C "$R" config --local --get core.hooksPath || true)" ] && ok "revert 还原本地 hooksPath" || bad "hooksPath 未还原"
|
|
110
112
|
git -C "$R" worktree remove --force "$TD/repo-worktree--y" >/dev/null 2>&1 || true
|
|
111
113
|
|
|
112
114
|
echo; echo "── PASS $PASS · FAIL $FAIL ──"
|