@minonl/dia 0.0.8 → 0.0.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/README.md +12 -0
- package/dist/cli.js +425 -22
- package/docs/index.md +1 -1
- package/docs/msg-collab-m1.md +29 -0
- package/docs/overview.md +6 -0
- package/docs/service-noun.md +113 -3
- package/package.json +1 -1
- package/packages/wt/bin/dia-wt +237 -94
- package/packages/wt/tests/adopt.test.sh +80 -154
package/docs/msg-collab-m1.md
CHANGED
|
@@ -64,3 +64,32 @@ dia graph --json --task t-xxx
|
|
|
64
64
|
- `blocked_by` 只在 task 节点里做派生提示,不是边;
|
|
65
65
|
- 任务依赖成环:`cycles` 非空,退出码 **3**(参数错仍是 2);
|
|
66
66
|
- `--task` 只输出该任务的直接依赖/touches/claim 邻居,不做全图闭包。
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
# 只读 TUI 图(M2 第一步)
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
dia graph --tui # 打印一次(有环 exit 3)
|
|
74
|
+
dia graph --watch # 每 2s 刷新,Ctrl-C 退出
|
|
75
|
+
dia graph --tui --no-color # 关 ANSI 颜色
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- 按状态分组显示 task → deps / touches / blocked_by;claims 单列;
|
|
79
|
+
- 不启 HTTP/daemon,遵守框架“无网络模块”约束;
|
|
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 仍是可见性工具。
|
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
|
|
|
@@ -352,3 +357,108 @@ unknown(stale)**不单独返回**——`health` 是实时执行,不存在
|
|
|
352
357
|
|
|
353
358
|
1. 判据写成**可判定的检查**:`node scripts/docs-index.mjs --check && node scripts/docs-link-check.mjs`(+ 可选 checklist 计数);
|
|
354
359
|
2. 或者把它当**评审工具**用:接受 `escalated` 是预期结果,只取 `verify.json` 的 findings 作为 grill 产出。
|
|
360
|
+
|
|
361
|
+
## 14. P2:agent 型服务(entry = persona 会话)
|
|
362
|
+
|
|
363
|
+
> 状态:**已实现(PR #60)**。本节定义脚本型之外的 agent 型入口(`entry.agent` + `healthcheck.kind="acp"`);
|
|
364
|
+
> 实现与本节一致,差异见 §15。
|
|
365
|
+
|
|
366
|
+
### 14.1 与脚本型的关系
|
|
367
|
+
|
|
368
|
+
```mermaid
|
|
369
|
+
flowchart LR
|
|
370
|
+
S["service"] --> A["脚本型(已实现)<br/>entry = bin/*"]
|
|
371
|
+
S --> B["agent 型(P2)<br/>entry = ACP 会话"]
|
|
372
|
+
B --> J["job:把 ACP 端点拉起并保持<br/>(dsh --profile dia-acp / opencode acp)"]
|
|
373
|
+
B --> H["healthcheck:端点可应答 + 最近一轮成功<br/>(不判断“想得对不对”)"]
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**不变的**:`lifecycle`(job 托管)、`healthcheck` 契约(专用码域)、台账(`kind:"health"`)、`gen` 代际。
|
|
377
|
+
**不同的**:没有 `stdin` 事件契约 —— 输入是 ACP 的 `prompt`,输出是会话事件;所以 `contract` 换成 ACP 语义。
|
|
378
|
+
|
|
379
|
+
### 14.2 清单
|
|
380
|
+
|
|
381
|
+
```jsonc
|
|
382
|
+
{
|
|
383
|
+
"kind": "service",
|
|
384
|
+
"id": "example-agent",
|
|
385
|
+
"entry": {
|
|
386
|
+
"agent": {
|
|
387
|
+
"host": "dsh", // dsh | opencode
|
|
388
|
+
"profile": "dia-acp", // 由 persona/宿主提供;缺省用宿主默认
|
|
389
|
+
"persona": "yan" // 可选:仅做归属校验,不注入内容
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
"lifecycle": { "type": "service", "target": "local" },
|
|
393
|
+
"healthcheck": { "kind": "acp", "interval": 1800, "timeout": 60 },
|
|
394
|
+
"kits": []
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
| 字段 | 语义 |
|
|
399
|
+
|---|---|
|
|
400
|
+
| `entry.agent.host` | 用哪套 ACP 实现(`dsh --profile <p>` 或 `opencode acp`) |
|
|
401
|
+
| `entry.agent.profile` | 宿主的 profile 名(不复制 persona 内容,只引用) |
|
|
402
|
+
| `entry.agent.persona` | 可选;`verify` 时校验「该 profile 是否属于此 persona」,防止挂错人 |
|
|
403
|
+
| `healthcheck.kind: "acp"` | 走 ACP 健康检查(见 §14.3),**不再要求 `cmd`** |
|
|
404
|
+
|
|
405
|
+
### 14.3 健康检查(ACP 版)
|
|
406
|
+
|
|
407
|
+
| 检查 | 判据 | 失败归类 |
|
|
408
|
+
|---|---|---|
|
|
409
|
+
| 端点存活 | `initialize` 能返回 `protocolVersion`(JSON-RPC 1.0) | 连不上/超时 → `degraded` |
|
|
410
|
+
| 会话可用 | `session/new` 成功返回 `sessionId` | 返回错误 → `fail` |
|
|
411
|
+
| 最近一轮 | 台账里最近一次 `prompt` 的成功/失败(由调用方写事件) | 连续失败 `debounce` 次 → `degraded` |
|
|
412
|
+
|
|
413
|
+
**明确不做**:不判断「回答质量」、不重放 prompt、不在 healthcheck 里跑真实业务(成本 + 副作用)。
|
|
414
|
+
|
|
415
|
+
### 14.4 与脚本型的复用
|
|
416
|
+
|
|
417
|
+
- `healthView` / `transition` / `gen` / 台账格式**完全复用**(`state.jsonl` + `kind:"health"`)
|
|
418
|
+
- 差异只在「怎么产生一次 judgement」:`runHealth()` 换成 `runAcpHealth()`(`initialize` + `session/new`)
|
|
419
|
+
- `dia service health <id>` 对 agent 型走 `runAcpHealth`,退出码沿用 §5
|
|
420
|
+
|
|
421
|
+
### 14.5 边界
|
|
422
|
+
|
|
423
|
+
| 场景 | 行为 |
|
|
424
|
+
|---|---|
|
|
425
|
+
| 宿主未安装 / profile 不存在 | `add` 期 `E_BACKEND`(不装半套) |
|
|
426
|
+
| 端点活着但 `session/new` 失败 | `fail`(配置/鉴权问题,不是 infra 抖动) |
|
|
427
|
+
| 长任务(一轮 prompt 很久) | **不算失败**;只有「端点不可应答」才算 |
|
|
428
|
+
| 多实例 | `lifecycle.target` 声明归属;ACP 端点同机只允许一个 |
|
|
429
|
+
| 权限/凭据 | 由宿主 profile 自己管;`dia` 不读取、不注入 |
|
|
430
|
+
|
|
431
|
+
### 14.6 验收(P2)
|
|
432
|
+
|
|
433
|
+
- [ ] 单测:清单校验(`entry.agent` 与 `entry.cmd` **互斥**,同时给 → `E_MANIFEST`)
|
|
434
|
+
- [ ] 单测:`runAcpHealth` 用 `tests/fixtures/fake-acp.mjs` 覆盖 `initialize` 失败 / `session/new` 失败 / 正常三态
|
|
435
|
+
- [ ] 单测:`healthcheck.kind` 缺省时按脚本型(向后兼容)
|
|
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
|
@@ -1167,22 +1167,31 @@ wt_default_branch() {
|
|
|
1167
1167
|
printf '%s' "$b"
|
|
1168
1168
|
}
|
|
1169
1169
|
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
cat > "$hooksdir/$name" <<'HOOK'
|
|
1173
|
-
#!/usr/bin/env bash
|
|
1174
|
-
# dia-wt guard(managed by `dia wt adopt`;勿手改)
|
|
1170
|
+
wt_guard_body() {
|
|
1171
|
+
cat <<'HOOKBODY'
|
|
1175
1172
|
set -uo pipefail
|
|
1176
|
-
|
|
1173
|
+
# 钩子名:生成的钩子用 basename $0;`dia wt guard` 用环境变量指定
|
|
1174
|
+
name="${DIA_WT_HOOK_NAME:-$(basename "$0")}"
|
|
1177
1175
|
top="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
1178
1176
|
common="$(git rev-parse --git-common-dir 2>/dev/null || true)"
|
|
1179
|
-
case "$common" in /*) ;; *) common="$(cd "$top" && cd "$common" 2>/dev/null && pwd -P)" ;; esac
|
|
1177
|
+
case "$common" in /*) ;; *) common="$(cd "$top" 2>/dev/null && cd "$common" 2>/dev/null && pwd -P)" ;; esac
|
|
1178
|
+
[ -n "$common" ] || exit 0
|
|
1180
1179
|
gd="$(git rev-parse --git-dir 2>/dev/null || true)"
|
|
1181
|
-
case "$gd" in /*) ;; *) gd="$(cd "$top" && cd "$gd" 2>/dev/null && pwd -P)" ;; esac
|
|
1180
|
+
case "$gd" in /*) ;; *) gd="$(cd "$top" 2>/dev/null && cd "$gd" 2>/dev/null && pwd -P)" ;; esac
|
|
1182
1181
|
is_main=0; [ "$gd" = "$common" ] && is_main=1
|
|
1183
1182
|
branch="$(git symbolic-ref --short HEAD 2>/dev/null || true)"
|
|
1184
|
-
prot=" main master develop dev "
|
|
1185
1183
|
probe="${DIA_WT_GUARD_PROBE:-0}"
|
|
1184
|
+
|
|
1185
|
+
# 只对已 adopt 的仓生效;未 adopt → 纯透传(保持本机其它仓行为不变)
|
|
1186
|
+
cfg="$common/dia/config.json"
|
|
1187
|
+
adopted=0; [ -f "$cfg" ] && adopted=1
|
|
1188
|
+
def=""
|
|
1189
|
+
if [ "$adopted" = 1 ]; then
|
|
1190
|
+
def="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("defaultBranch") or "")' "$cfg" 2>/dev/null || true)"
|
|
1191
|
+
fi
|
|
1192
|
+
prot=" main master develop dev $def "
|
|
1193
|
+
prot="${prot// / }"
|
|
1194
|
+
|
|
1186
1195
|
wt_guard_scan() {
|
|
1187
1196
|
local ls="$1" rs="$2" range="" base="" hits=""
|
|
1188
1197
|
if [ -n "$rs" ] && [ "$rs" != "0000000000000000000000000000000000000000" ] && git cat-file -e "$rs" 2>/dev/null; then
|
|
@@ -1197,74 +1206,100 @@ hits="$(git diff -U0 "$range" -- . 2>/dev/null | grep -E '^\+' | grep -vE '^\+\+
|
|
|
1197
1206
|
if [ -n "$hits" ]; then
|
|
1198
1207
|
echo "[dia-wt] 推送包含敏感内容(已阻断):" >&2
|
|
1199
1208
|
printf '%s\n' "$hits" >&2
|
|
1200
|
-
echo "[dia-wt] 误报请修扫描规则(hooks 由 dia wt adopt
|
|
1209
|
+
echo "[dia-wt] 误报请修扫描规则(hooks 由 dia wt adopt 安装),不提供关闭开关" >&2
|
|
1201
1210
|
return 1
|
|
1202
1211
|
fi
|
|
1203
1212
|
}
|
|
1204
1213
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
[ "$is_main" = 1 ] &&
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
esac
|
|
1242
|
-
|
|
1214
|
+
if [ "$probe" = 1 ]; then
|
|
1215
|
+
case "$name" in
|
|
1216
|
+
pre-commit) { [ "$adopted" = 1 ] && [ "$is_main" = 1 ] && [[ "$prot" == *" $branch "* ]]; } && exit 1 || exit 0 ;;
|
|
1217
|
+
pre-push) { [ "$adopted" = 1 ] && [ "$is_main" = 1 ]; } && exit 1 || exit 0 ;;
|
|
1218
|
+
*) exit 0 ;;
|
|
1219
|
+
esac
|
|
1220
|
+
fi
|
|
1221
|
+
|
|
1222
|
+
if [ "$adopted" = 1 ]; then
|
|
1223
|
+
case "$name" in
|
|
1224
|
+
pre-commit)
|
|
1225
|
+
if [ "$is_main" = 1 ] && [[ "$prot" == *" $branch "* ]]; then
|
|
1226
|
+
echo "[dia-wt] 主 checkout 禁止在 $branch 直接提交;请先:dia wt create <repo> <feature>" >&2
|
|
1227
|
+
exit 1
|
|
1228
|
+
fi ;;
|
|
1229
|
+
pre-push)
|
|
1230
|
+
if [ "$is_main" = 1 ]; then
|
|
1231
|
+
echo "[dia-wt] 主 checkout 禁止直接 push;请走 worktree 分支 + MR" >&2
|
|
1232
|
+
exit 1
|
|
1233
|
+
fi
|
|
1234
|
+
# 先把 stdin 全读进来:既要逐条判 ref / 扫敏感内容,也要原样喂给仓库自己的钩子
|
|
1235
|
+
push_lines=()
|
|
1236
|
+
while IFS= read -r line; do push_lines+=("$line"); done
|
|
1237
|
+
for line in "${push_lines[@]}"; do
|
|
1238
|
+
read -r _lr ls rr rs <<<"$line"
|
|
1239
|
+
case "${rr:-}" in
|
|
1240
|
+
refs/heads/main|refs/heads/master|refs/heads/develop|refs/heads/dev)
|
|
1241
|
+
echo "[dia-wt] 禁止直接 push ${rr#refs/heads/};请走 MR" >&2; exit 1 ;;
|
|
1242
|
+
esac
|
|
1243
|
+
[ -n "${ls:-}" ] || continue
|
|
1244
|
+
wt_guard_scan "$ls" "${rs:-}" || exit 1
|
|
1245
|
+
done ;;
|
|
1246
|
+
post-checkout)
|
|
1247
|
+
if [ "$is_main" = 1 ] && [[ "$prot" != *" $branch "* ]]; then
|
|
1248
|
+
echo "[dia-wt] 提示:主 checkout 当前在 $branch(建议回到默认分支)" >&2
|
|
1249
|
+
fi ;;
|
|
1250
|
+
esac
|
|
1251
|
+
fi
|
|
1252
|
+
|
|
1253
|
+
# 链式:仓库自己的钩子(simple-git-hooks 等管理器生成在 <common>/hooks)
|
|
1243
1254
|
orig="$common/hooks/$name"
|
|
1244
|
-
if [ -x "$orig" ] && [ "$orig" != "$0" ]; then
|
|
1255
|
+
if [ -x "$orig" ] && [ "$orig" != "$0" ]; then
|
|
1256
|
+
if [ "$name" = "pre-push" ] && [ ${#push_lines[@]} -gt 0 ]; then
|
|
1257
|
+
printf '%s\n' "${push_lines[@]}" | "$orig" "$@"
|
|
1258
|
+
exit $?
|
|
1259
|
+
fi
|
|
1260
|
+
exec "$orig" "$@"
|
|
1261
|
+
fi
|
|
1245
1262
|
exit 0
|
|
1246
|
-
|
|
1263
|
+
HOOKBODY
|
|
1264
|
+
}
|
|
1265
|
+
|
|
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"
|
|
1247
1282
|
if [ -n "$chain" ]; then
|
|
1248
1283
|
python3 - "$hooksdir/$name" "$chain" <<'PYCHAIN'
|
|
1249
1284
|
import sys
|
|
1250
1285
|
p, chain = sys.argv[1], sys.argv[2]
|
|
1251
1286
|
s = open(p).read()
|
|
1252
|
-
s = s.replace('
|
|
1287
|
+
s = s.replace('orig="$common/hooks/$name"', 'orig="%s/$name"' % chain.replace('\\', '\\\\').replace('"', '\\"'))
|
|
1253
1288
|
open(p, "w").write(s)
|
|
1254
1289
|
PYCHAIN
|
|
1255
|
-
fi
|
|
1256
|
-
if [ -n "$extra" ]; then
|
|
1257
|
-
python3 - "$hooksdir/$name" "$extra" <<'PYGT'
|
|
1258
|
-
import sys, re
|
|
1259
|
-
p, extra = sys.argv[1], sys.argv[2]
|
|
1260
|
-
s = open(p).read()
|
|
1261
|
-
s = re.sub(r'^prot=" main master develop dev "$', f'prot=" main master develop dev {extra} "', s, flags=re.M)
|
|
1262
|
-
open(p, "w").write(s)
|
|
1263
|
-
PYGT
|
|
1264
1290
|
fi
|
|
1265
1291
|
chmod +x "$hooksdir/$name"
|
|
1266
1292
|
}
|
|
1267
1293
|
|
|
1294
|
+
cmd_guard() {
|
|
1295
|
+
local name="${1:-}"
|
|
1296
|
+
case "$name" in
|
|
1297
|
+
pre-commit|pre-push|post-checkout) ;;
|
|
1298
|
+
*) echo "usage: dia wt guard <pre-commit|pre-push|post-checkout>" >&2; return 2 ;;
|
|
1299
|
+
esac
|
|
1300
|
+
DIA_WT_HOOK_NAME="$name" eval "$(wt_guard_body)"
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1268
1303
|
cmd_adopt() {
|
|
1269
1304
|
local repo="" mode="dry" template="" def="" json=0 all=0
|
|
1270
1305
|
local -a orig=("$@")
|
|
@@ -1305,19 +1340,20 @@ cmd_adopt() {
|
|
|
1305
1340
|
local common cfgdir cfg hooksdir prev
|
|
1306
1341
|
common="$(wt_common_dir "$repo")" || die "无法解析 git-common-dir"
|
|
1307
1342
|
cfgdir="$common/dia"; cfg="$cfgdir/config.json"; hooksdir="$cfgdir/hooks"
|
|
1308
|
-
prev="$(git -C "$repo" config --get core.hooksPath 2>/dev/null || true)"
|
|
1343
|
+
prev="$(git -C "$repo" config --local --get core.hooksPath 2>/dev/null || true)"
|
|
1309
1344
|
[ -n "$template" ] || template='{{parent}}/{{repo}}-worktree--{{feature}}'
|
|
1310
1345
|
[ -n "$def" ] || def="$(wt_default_branch "$repo")"
|
|
1311
1346
|
|
|
1312
1347
|
if [ "$mode" = "revert" ]; then
|
|
1313
1348
|
[ -f "$cfg" ] || die "未 adopt(缺 $cfg)"
|
|
1314
|
-
local saved
|
|
1315
|
-
saved="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksPrevious") or "")' "$cfg")"
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1349
|
+
local saved mgr
|
|
1350
|
+
saved="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksPrevious") or "")' "$cfg" 2>/dev/null || true)"
|
|
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
|
|
1319
1356
|
fi
|
|
1320
|
-
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
|
|
1321
1357
|
rm -rf "$cfgdir"
|
|
1322
1358
|
echo "reverted:$cfgdir"
|
|
1323
1359
|
return 0
|
|
@@ -1331,16 +1367,28 @@ cmd_adopt() {
|
|
|
1331
1367
|
local cfgDef; cfgDef="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("defaultBranch") or "")' "$cfg" 2>/dev/null || true)"
|
|
1332
1368
|
[ -n "$cfgDef" ] && def="$cfgDef"
|
|
1333
1369
|
fi
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
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" = "simple-git-hooks" ]; then
|
|
1372
|
+
local rcf; rcf="$(wt_hooks_rc_dir)/simple-git-hooks-rc.sh"
|
|
1373
|
+
[ -f "$rcf" ] || { echo "FAIL D02 缺 simple-git-hooks RC:$rcf"; bad=1; }
|
|
1374
|
+
grep -rqs "SIMPLE_GIT_HOOKS_RC" "$HOME/.zshenv" || { echo "FAIL D02 shell 未 export SIMPLE_GIT_HOOKS_RC"; bad=1; }
|
|
1375
|
+
python3 - "$repo/package.json" <<'PYM' >/dev/null 2>&1 || { echo "FAIL D02 package.json 未启用 simple-git-hooks"; bad=1; }
|
|
1376
|
+
import json,sys
|
|
1377
|
+
d=json.load(open(sys.argv[1])); c=d.get("simple-git-hooks"); dev=d.get("devDependencies",{}) or {}
|
|
1378
|
+
raise SystemExit(0 if isinstance(c,dict) or "simple-git-hooks" in dev else 1)
|
|
1379
|
+
PYM
|
|
1380
|
+
if [ -n "$def" ] && wt_is_main_checkout "$repo" && [ "$(git -C "$repo" symbolic-ref --short HEAD 2>/dev/null)" = "$def" ]; then
|
|
1381
|
+
( cd "$repo" && DIA_WT_GUARD_PROBE=1 bash "$SELF" guard pre-commit >/dev/null 2>&1 ) \
|
|
1382
|
+
&& { echo "FAIL D03 守卫探针未拦截 $def 提交"; bad=1; }
|
|
1383
|
+
fi
|
|
1384
|
+
else
|
|
1385
|
+
case "$prev" in /*) ;; *) echo "FAIL D02 本地 core.hooksPath 非绝对($prev)"; bad=1 ;; esac
|
|
1386
|
+
for h in pre-commit pre-push post-checkout; do
|
|
1387
|
+
[ -x "$hooksdir/$h" ] || { echo "FAIL D02 缺 $h 钩子"; bad=1; }
|
|
1388
|
+
done
|
|
1389
|
+
if [ -n "$def" ] && wt_is_main_checkout "$repo" && [ "$(git -C "$repo" symbolic-ref --short HEAD 2>/dev/null)" = "$def" ]; then
|
|
1342
1390
|
( cd "$repo" && DIA_WT_GUARD_PROBE=1 "$hooksdir/pre-commit" >/dev/null 2>&1 ) \
|
|
1343
|
-
&& { echo "FAIL D03
|
|
1391
|
+
&& { echo "FAIL D03 守卫探针未拦截 $def 提交"; bad=1; }
|
|
1344
1392
|
fi
|
|
1345
1393
|
fi
|
|
1346
1394
|
if [ "$json" = 1 ]; then
|
|
@@ -1366,6 +1414,80 @@ cmd_adopt() {
|
|
|
1366
1414
|
|
|
1367
1415
|
[ -n "$def" ] || die "默认分支不可解析;请 --default-branch main"
|
|
1368
1416
|
mkdir -p "$hooksdir"
|
|
1417
|
+
# 安装/刷新本机全局钩子,并让本仓走全局钩子(不改 package.json、不占用仓库钩子目录)
|
|
1418
|
+
# ── 方案:每个仓本地钩子;simple-git-hooks 仓改用它的 RC 注入 ──────────────
|
|
1419
|
+
# 不再写任何全局 git 配置(避免外溢)。
|
|
1420
|
+
if [ -f "$repo/package.json" ] && python3 - "$repo/package.json" <<'PYCHK'
|
|
1421
|
+
import json,sys
|
|
1422
|
+
d=json.load(open(sys.argv[1]))
|
|
1423
|
+
cfg=d.get("simple-git-hooks")
|
|
1424
|
+
dev=d.get("devDependencies",{}) or {}
|
|
1425
|
+
raise SystemExit(0 if isinstance(cfg,dict) or "simple-git-hooks" in dev else 1)
|
|
1426
|
+
PYCHK
|
|
1427
|
+
then
|
|
1428
|
+
# simple-git-hooks:钩子由管理器生成到 .git/hooks,我们不碰;通过 SIMPLE_GIT_HOOKS_RC 注入守卫
|
|
1429
|
+
local rcdir rcf
|
|
1430
|
+
rcdir="$(wt_hooks_rc_dir)"; rcf="$rcdir/simple-git-hooks-rc.sh"
|
|
1431
|
+
mkdir -p "$rcdir"
|
|
1432
|
+
cat > "$rcf" <<'RC'
|
|
1433
|
+
# 由 simple-git-hooks 生成的钩子 source 本文件(SIMPLE_GIT_HOOKS_RC)
|
|
1434
|
+
# 只对已 adopt 的仓生效;未 adopt → 直接返回
|
|
1435
|
+
if command -v dia >/dev/null 2>&1; then
|
|
1436
|
+
dia wt guard "$(basename "$0")" || exit $?
|
|
1437
|
+
fi
|
|
1438
|
+
RC
|
|
1439
|
+
wt_ensure_env_export SIMPLE_GIT_HOOKS_RC "$rcf"
|
|
1440
|
+
python3 - "$cfg" "$repo" "$def" "$template" "$prev" <<'PYG'
|
|
1441
|
+
import json, sys, datetime
|
|
1442
|
+
cfg, repo, br, tmpl, prev = sys.argv[1:6]
|
|
1443
|
+
json.dump({
|
|
1444
|
+
"schema": 2, "repo": repo, "defaultBranch": br, "template": tmpl,
|
|
1445
|
+
"hooksManager": "simple-git-hooks", "hooksPrevious": prev or None,
|
|
1446
|
+
"adoptedAt": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
1447
|
+
}, open(cfg, "w"), ensure_ascii=False, indent=1)
|
|
1448
|
+
PYG
|
|
1449
|
+
echo "adopted:$repo(hook manager = simple-git-hooks;守卫经 SIMPLE_GIT_HOOKS_RC 注入)"
|
|
1450
|
+
return 0
|
|
1451
|
+
fi
|
|
1452
|
+
|
|
1453
|
+
# 其它仓:自己管 <common>/dia/hooks,并把本地 core.hooksPath 指过去。
|
|
1454
|
+
# 若该仓已有**别人**的本地 hooksPath(husky/.githooks 等),不抢占:只报告,交由转为 simple-git-hooks 处理。
|
|
1455
|
+
if [ -n "$prev" ] && [ "$prev" != "$hooksdir" ]; then
|
|
1456
|
+
python3 - "$cfg" "$repo" "$def" "$template" "$prev" <<'PYG'
|
|
1457
|
+
import json, sys, datetime
|
|
1458
|
+
cfg, repo, br, tmpl, prev = sys.argv[1:6]
|
|
1459
|
+
json.dump({
|
|
1460
|
+
"schema": 2, "repo": repo, "defaultBranch": br, "template": tmpl,
|
|
1461
|
+
"hooksManager": "external", "hooksPrevious": prev,
|
|
1462
|
+
"adoptedAt": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
1463
|
+
}, open(cfg, "w"), ensure_ascii=False, indent=1)
|
|
1464
|
+
PYG
|
|
1465
|
+
echo "[dia-wt] ⚠️ $repo 的 core.hooksPath=$prev 由其它管理器管理,未接管(守卫不生效);建议改用 simple-git-hooks(见 docs/worktree.md)" >&2
|
|
1466
|
+
return 0
|
|
1467
|
+
fi
|
|
1468
|
+
mkdir -p "$hooksdir"
|
|
1469
|
+
local savedPrev=""
|
|
1470
|
+
[ -f "$cfg" ] && savedPrev="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksPrevious") or "")' "$cfg" 2>/dev/null || true)"
|
|
1471
|
+
[ "$prev" = "$hooksdir" ] && prev="$savedPrev"
|
|
1472
|
+
local chain="$prev"
|
|
1473
|
+
[ -n "$chain" ] || chain="$common/hooks"
|
|
1474
|
+
[ -f "$cfg" ] && cp "$cfg" "$cfgdir/config.json.bak.$(date +%s)"
|
|
1475
|
+
wt_write_hook "$hooksdir" pre-commit "$def" "$chain"
|
|
1476
|
+
wt_write_hook "$hooksdir" pre-push "$def" "$chain"
|
|
1477
|
+
wt_write_hook "$hooksdir" post-checkout "$def" "$chain"
|
|
1478
|
+
git -C "$repo" config --local core.hooksPath "$hooksdir"
|
|
1479
|
+
python3 - "$cfg" "$repo" "$def" "$template" "$prev" "$chain" <<'PYG'
|
|
1480
|
+
import json, sys, datetime
|
|
1481
|
+
cfg, repo, br, tmpl, prev, chain = sys.argv[1:7]
|
|
1482
|
+
json.dump({
|
|
1483
|
+
"schema": 2, "repo": repo, "defaultBranch": br, "template": tmpl,
|
|
1484
|
+
"hooksManager": "local", "hooksPath": cfg.rsplit("/",1)[0] + "/hooks",
|
|
1485
|
+
"hooksPrevious": prev or None, "hooksChain": chain,
|
|
1486
|
+
"adoptedAt": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
1487
|
+
}, open(cfg, "w"), ensure_ascii=False, indent=1)
|
|
1488
|
+
PYG
|
|
1489
|
+
echo "adopted:$repo(守卫 = 仓库本地钩子 $hooksdir)"
|
|
1490
|
+
return 0
|
|
1369
1491
|
# 保留首次 adopt 记下的 hooksPrevious(重复 apply 时 prev 已是我们自己的 hooksdir)
|
|
1370
1492
|
local savedPrev=""
|
|
1371
1493
|
[ -f "$cfg" ] && savedPrev="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksPrevious") or "")' "$cfg" 2>/dev/null || true)"
|
|
@@ -1384,11 +1506,6 @@ data = {
|
|
|
1384
1506
|
}
|
|
1385
1507
|
json.dump(data, open(cfg, "w"), ensure_ascii=False, indent=1)
|
|
1386
1508
|
PY
|
|
1387
|
-
# 提示:simple-git-hooks / husky 的 postinstall 会重写 core.hooksPath 下的钩子,
|
|
1388
|
-
# 每次 `pnpm/npm install` 后可能把守卫冲掉 → 用 `dia wt doctor --fix` 或重跑 adopt --apply 修复。
|
|
1389
|
-
if grep -qs '"simple-git-hooks"\|"husky"\|lefthook' "$repo/package.json" "$repo/.lefthook.yml" 2>/dev/null; then
|
|
1390
|
-
echo "[dia-wt] 注意:该仓使用 hook manager(simple-git-hooks/husky/lefthook),install 后若守卫被重写,跑 dia wt doctor --fix" >&2
|
|
1391
|
-
fi
|
|
1392
1509
|
wt_write_hook "$hooksdir" pre-commit "$def" "$chain"
|
|
1393
1510
|
wt_write_hook "$hooksdir" pre-push "$def" "$chain"
|
|
1394
1511
|
wt_write_hook "$hooksdir" post-checkout "$def" "$chain"
|
|
@@ -1419,14 +1536,25 @@ cmd_doctor() {
|
|
|
1419
1536
|
git -C "$repo" rev-parse --git-dir >/dev/null 2>&1 || continue
|
|
1420
1537
|
local common cfgdir cfg hooksdir prev def
|
|
1421
1538
|
common="$(wt_common_dir "$repo")"; cfgdir="$common/dia"; cfg="$cfgdir/config.json"; hooksdir="$cfgdir/hooks"
|
|
1422
|
-
prev="$(git -C "$repo" config --get core.hooksPath 2>/dev/null || true)"
|
|
1539
|
+
prev="$(git -C "$repo" config --local --get core.hooksPath 2>/dev/null || true)"
|
|
1423
1540
|
def=""
|
|
1424
1541
|
if [ -f "$cfg" ]; then
|
|
1425
1542
|
def="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("defaultBranch") or "")' "$cfg" 2>/dev/null || true)"
|
|
1426
1543
|
fi
|
|
1427
1544
|
[ -n "$def" ] || def="$(wt_default_branch "$repo")"
|
|
1428
1545
|
# --fix:无损修复(重写三个守卫钩子 + hooksPath 绝对路径)。只对已 adopt 的仓生效。
|
|
1429
|
-
if [ "$fix" = 1 ] && [ -f "$cfg" ]; then
|
|
1546
|
+
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
|
|
1547
|
+
local rcdir rcf; rcdir="$(wt_hooks_rc_dir)"; rcf="$rcdir/simple-git-hooks-rc.sh"
|
|
1548
|
+
mkdir -p "$rcdir"
|
|
1549
|
+
cat > "$rcf" <<'RC'
|
|
1550
|
+
# 由 simple-git-hooks 生成的钩子 source 本文件(SIMPLE_GIT_HOOKS_RC)
|
|
1551
|
+
if command -v dia >/dev/null 2>&1; then
|
|
1552
|
+
dia wt guard "$(basename "$0")" || exit $?
|
|
1553
|
+
fi
|
|
1554
|
+
RC
|
|
1555
|
+
wt_ensure_env_export SIMPLE_GIT_HOOKS_RC "$rcf"
|
|
1556
|
+
[ "$json" = 0 ] && echo "FIX $repo simple-git-hooks RC 已重建"
|
|
1557
|
+
elif [ "$fix" = 1 ] && [ -f "$cfg" ]; then
|
|
1430
1558
|
local fixChain=""
|
|
1431
1559
|
fixChain="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksChain") or "")' "$cfg" 2>/dev/null || true)"
|
|
1432
1560
|
[ -n "$fixChain" ] || fixChain="$common/hooks"
|
|
@@ -1438,18 +1566,31 @@ cmd_doctor() {
|
|
|
1438
1566
|
prev="$hooksdir"
|
|
1439
1567
|
[ "$json" = 0 ] && echo "FIX $repo 钩子已重装(chain=$fixChain)"
|
|
1440
1568
|
fi
|
|
1441
|
-
local bad=0
|
|
1569
|
+
local bad=0 mcfg=""
|
|
1442
1570
|
[ -f "$cfg" ] || { echo "FAIL D01 $repo 未 adopt"; bad=1; }
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
if [ -n "$
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1571
|
+
[ -f "$cfg" ] && mcfg="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksManager") or "")' "$cfg" 2>/dev/null || true)"
|
|
1572
|
+
local ghp mgr2
|
|
1573
|
+
ghp="$(git config --global --get core.hooksPath 2>/dev/null || true)"
|
|
1574
|
+
# D20 全局配置残留:旧版把守卫装到全局 hooksPath;现在改为每仓本地,必须清掉
|
|
1575
|
+
if [ -n "$ghp" ] && [ -f "$ghp/pre-commit" ] && grep -q "dia-wt guard" "$ghp/pre-commit" 2>/dev/null; then
|
|
1576
|
+
echo "FAIL D20 全局 core.hooksPath 仍指向 dia 旧钩子目录($ghp);执行 git config --global --unset core.hooksPath"
|
|
1577
|
+
bad=1
|
|
1578
|
+
fi
|
|
1579
|
+
mgr2="$(python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get("hooksManager") or "")' "$cfg" 2>/dev/null || true)"
|
|
1580
|
+
if [ "$mgr2" = "simple-git-hooks" ]; then
|
|
1581
|
+
local rcf; rcf="$(wt_hooks_rc_dir)/simple-git-hooks-rc.sh"
|
|
1582
|
+
[ -f "$rcf" ] || { echo "FAIL D02 $repo 缺 simple-git-hooks RC:$rcf"; bad=1; }
|
|
1583
|
+
grep -rqs "SIMPLE_GIT_HOOKS_RC" "$HOME/.zshenv" || echo "WARN D02 $repo shell 未 export SIMPLE_GIT_HOOKS_RC(守卫不会执行)"
|
|
1584
|
+
if [ -n "$def" ] && wt_is_main_checkout "$repo" && [ "$(git -C "$repo" symbolic-ref --short HEAD 2>/dev/null)" = "$def" ]; then
|
|
1585
|
+
( cd "$repo" && DIA_WT_GUARD_PROBE=1 bash "$SELF" guard pre-commit >/dev/null 2>&1 ) \
|
|
1586
|
+
&& { echo "FAIL D03 $repo 守卫探针未拦截 $def 提交"; bad=1; }
|
|
1587
|
+
fi
|
|
1588
|
+
else
|
|
1589
|
+
case "$prev" in /*) ;; *) echo "FAIL D02 $repo 本地 core.hooksPath 非绝对($prev)"; bad=1 ;; esac
|
|
1590
|
+
for h in pre-commit pre-push post-checkout; do
|
|
1591
|
+
[ -x "$hooksdir/$h" ] || { echo "FAIL D02 $repo 缺 $h"; bad=1; }
|
|
1592
|
+
done
|
|
1593
|
+
if [ -n "$def" ] && wt_is_main_checkout "$repo" && [ "$(git -C "$repo" symbolic-ref --short HEAD 2>/dev/null)" = "$def" ]; then
|
|
1453
1594
|
( cd "$repo" && DIA_WT_GUARD_PROBE=1 "$hooksdir/pre-commit" >/dev/null 2>&1 ) \
|
|
1454
1595
|
&& { echo "FAIL D03 $repo 守卫未拦截 $def 提交"; bad=1; }
|
|
1455
1596
|
fi
|
|
@@ -1507,6 +1648,7 @@ case "${1:-}" in
|
|
|
1507
1648
|
status) shift; cmd_status "$@" ;;
|
|
1508
1649
|
check) shift; cmd_check "$@" ;;
|
|
1509
1650
|
adopt) shift; cmd_adopt "$@" ;;
|
|
1651
|
+
guard) shift; cmd_guard "$@" ;;
|
|
1510
1652
|
doctor) shift; cmd_doctor "$@" ;;
|
|
1511
1653
|
gate) shift; cmd_gate "$@" ;;
|
|
1512
1654
|
clean) shift; cmd_clean "$@" ;;
|
|
@@ -1522,6 +1664,7 @@ dia wt —— worktree 目录形式选型 + 创建(唯一实现)
|
|
|
1522
1664
|
dia wt status [repo-path] # ★ 清理前必查:唯一工作副本 + 改动重叠(只读)
|
|
1523
1665
|
dia wt check [<repo>...] [--all] [--deep] [--quiet]
|
|
1524
1666
|
# ★ 依赖体检(R20):软链/逃逸/悬空;--all 扫全部仓
|
|
1667
|
+
dia wt guard <pre-commit|pre-push|post-checkout> # 守卫本体(供 hook manager 调用)
|
|
1525
1668
|
dia wt adopt <repo>|--all [--apply|--check|--revert] [--template T] [--default-branch B] [--json]
|
|
1526
1669
|
# 配置路径模板 + 守卫钩子(默认 dry-run;--all 扫仓根)
|
|
1527
1670
|
# 守卫含推送前敏感扫描(不提供关闭开关;误报修规则)
|