@minonl/dia 0.0.15 → 0.0.17
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 +8 -0
- package/dist/cli.js +1966 -270
- package/docs/builtin-kit-design.md +65 -0
- package/docs/cli.md +3 -2
- package/docs/design/graph-prio-dag.md +85 -0
- package/docs/design/graph-ui-m1.md +106 -0
- package/docs/design/graph-web-m1.md +113 -0
- package/docs/design/graph-web-m2.md +92 -0
- package/docs/design/graph-web-mockup.html +49 -25
- package/docs/design/graph-web.md +9 -4
- package/docs/design/seat-scheduling-m2.md +138 -0
- package/docs/index.md +9 -1
- package/docs/install-guard-design.md +148 -0
- package/docs/kit-install.md +7 -0
- package/docs/kit-layer.md +78 -0
- package/docs/migrations/data-root-2026-11.md +3 -2
- package/docs/status.md +3 -0
- package/kits/git/dia.json +10 -0
- package/kits/git/knowledge/hooks.md +30 -0
- package/kits/git/knowledge/worktree.md +34 -0
- package/kits/node/dia.json +10 -0
- package/kits/node/knowledge/hook-managers.md +17 -0
- package/kits/node/knowledge/pnpm.md +187 -0
- package/package.json +1 -1
- package/packages/wt/bin/dia-wt +15 -0
- package/packages/wt/tests/adopt.test.sh +8 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# 内置能力的分层:dia-msg 不该让用户「装」
|
|
2
|
+
|
|
3
|
+
> 日期:2026-11-09 | 状态:**实现中(P0 已合,P1 本文档)** | 触发:用户质疑「dia 是命令入口,为什么内置的还要单独装?dia-msg 都算内置」
|
|
4
|
+
|
|
5
|
+
## 1. 问题
|
|
6
|
+
|
|
7
|
+
`dia-msg` 随包发布(`package.json files` 含 `kits/`),但运行时被当成第三方 kit:
|
|
8
|
+
|
|
9
|
+
- `default-msg.ts` 唯一的动作是 `kit.install(<install>/kits/dia-msg)` → 复制到 `~/.dia/kits/dia-msg/<ver>`、写 `kit.lock`、建 skill 软链
|
|
10
|
+
- 复制只在 `persona add/render` 时触发;漏了就 `⚠ missing-kit`,而且 **exit 0**(静默失去消息能力)
|
|
11
|
+
|
|
12
|
+
用户视角:装完 dia,消息能力「有时有、有时没有」,还要 `dia collab enable` 手动补。
|
|
13
|
+
|
|
14
|
+
## 2. 三层边界
|
|
15
|
+
|
|
16
|
+
| 层 | 内容 | 要不要「装」 | 理由 |
|
|
17
|
+
|---|---|---|---|
|
|
18
|
+
| **用户资产** | persona、第三方 kit | 要(lock / 内容哈希 / remove / 多版本) | 内容来自外部仓库,会漂移,必须能验、能回滚 |
|
|
19
|
+
| **内置能力** | dia-msg(未来 git/node kit) | **不要(用户零操作)** | 版本必须跟包走;复制只是「让运行时能读」的实现细节 |
|
|
20
|
+
| **宿主可见性** | skill 软链 / agent 入口 / 配置片段 | 首次运行生成,幂等可重建 | 不能随包发布,必须落到各自 HOME |
|
|
21
|
+
|
|
22
|
+
**结论**:内置能力≠复制一份到数据根,但也不等于直接读包内(pnpm/npx/换 Node 会断链)。折中:
|
|
23
|
+
|
|
24
|
+
## 3. 拍板方案:懒复制 + 版本戳(B)
|
|
25
|
+
|
|
26
|
+
- 复制仍发生,但**用户永远不用手装**:任何首次需要它的路径(`persona add/render`、`dia collab enable`)自动完成
|
|
27
|
+
- 副本用 **内容哈希**判断是否刷新(版本号常不 bump,只看 semver 会漂移)
|
|
28
|
+
- 写自举戳 `~/.dia/state/bootstrap.json`:`{pkg, kit, at}`;`dia doctor` 有 `bootstrap` 检查
|
|
29
|
+
- 无戳 → warn「首次 persona add/render 或 collab enable 会自动装」
|
|
30
|
+
- `stamp.pkg != 当前包版本` → warn「内置能力可能过期;修复:`dia collab enable`」
|
|
31
|
+
- `dia collab enable` 装失败 → **exit 32**(不再 exit 0 假成功)
|
|
32
|
+
|
|
33
|
+
### 为什么不选「内置只读(直接读包内)」
|
|
34
|
+
|
|
35
|
+
| 阻断 | 后果 |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `linkManaged` 只认 `~/.dia/kits` / `~/.dia/personas` | 指向包内的软链判 unmanaged → `E_CONFLICT` |
|
|
38
|
+
| persona 的 `kits[]` 只允许 persona 树内路径 | 内置 kit 无法被 persona 声明,只能留特例 |
|
|
39
|
+
| pnpm `.pnpm/<pkg@ver>/`、npx `_npx/<hash>`、换 Node 前缀 | 包路径会变,软链必然断,需每次升级重链 |
|
|
40
|
+
|
|
41
|
+
只读模式的收益(少一次复制)不值得新增三条契约;B 保留了 verify/remove/回滚。
|
|
42
|
+
|
|
43
|
+
## 4. P0:先修安全底座(本 PR 之前已完成)
|
|
44
|
+
|
|
45
|
+
| 项 | 之前 | 现在 |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `kit.install` 落盘 | `rm -rf target` 后 `cp`,中途失败旧版全没 | staging 目录 + `rename` 原子换入,失败回滚并保留旧版 |
|
|
48
|
+
| 并发安装 | 无锁,可能装成「有 lock 但内容残缺」 | 数据根级 `O_EXCL` 锁(`DIA_KIT_LOCK_MS`,默认 5s,陈旧锁 120s 自动清) |
|
|
49
|
+
| 版本选择 | 字典序(`0.1.9` > `0.1.10`) | semver 比较,隐藏目录(`.staging`/`.install.lock`)不参与 |
|
|
50
|
+
| `installDir()` | 环境变量指向不存在目录也照用 → missing-kit 谜之失败 | 校验存在性,告警并回退探测 |
|
|
51
|
+
| `collab enable` | 安装失败仍 exit 0 | exit 32 |
|
|
52
|
+
|
|
53
|
+
## 5. P2(下一步,未做)
|
|
54
|
+
|
|
55
|
+
- `install.sh` 文档降级为「开发 / 离线通道」,主推 `npm i -g @minonl/dia`;`install_shadow` 已从 fail 降为 warn
|
|
56
|
+
- `dia update` 文案统一成「npm/bun 全局升级」,不再引导 `bash install.sh`
|
|
57
|
+
- 若未来出现第二个内置 kit:抽 `builtin kits` 注册表,避免每加一个都改 `default-msg`
|
|
58
|
+
|
|
59
|
+
## 6. 判据
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
bun test tests/kit-atomic.test.ts # 原子安装 / 锁 / semver / installDir
|
|
63
|
+
dia doctor --json | grep -q '"bootstrap"' # 自举戳可见
|
|
64
|
+
dia collab enable; echo $? # 装不上 → 32
|
|
65
|
+
```
|
package/docs/cli.md
CHANGED
|
@@ -99,12 +99,13 @@ dia persona remove dabo
|
|
|
99
99
|
| `env list / check / run` | **已实现** | 白名单注入、不回显值、`--redact`、密钥不进 argv;清单由 persona 声明(`src/env.ts` + `tests/env.test.ts`) |
|
|
100
100
|
| `update [--check]` | **已实现** | 查 npm `dist-tags`;`--check` 有新版 exit 1、领先/最新 exit 0;**按安装方式分流**:npm 全局包 → `npm i -g`;脚本安装 → 提示 `git pull && bun run build && bash install.sh`(避免装到不在 PATH 的 npm prefix) |
|
|
101
101
|
| `deps list / record` | **已实现** | `list` = 登记 + 实测派生视图(required 缺失 exit 2);`record` = 只追加一行(`--id/--kind/--version` 必填);`src/deps.ts` + `tests/deps*.test.ts` |
|
|
102
|
-
| `doctor` | **已实现** | 组合自检:persona verify / **persona.render(宿主产物只读漂移,warn + 修复命令)** / default_agent / kit(**只查该 persona 引用的**,孤儿 kit 不拖红)/ knowledge(**索引新鲜度三态**,过期给重建命令)/ dsh preset / **deps 登记校验** / **platform 版本(读 `docs/platforms/registry.json`)** / wt
|
|
102
|
+
| `doctor` | **已实现** | 组合自检:persona verify / **persona.render(宿主产物只读漂移,warn + 修复命令)** / default_agent / kit(**只查该 persona 引用的**,孤儿 kit 不拖红)/ knowledge(**索引新鲜度三态**,过期给重建命令)/ dsh preset / **deps 登记校验** / **platform 版本(读 `docs/platforms/registry.json`)** / wt / **install_shadow(遮蔽提示,warn)** / **bootstrap(内置能力是否跟随包版本)**(脚本安装 vs 包管理器全局安装互相遮挡:双向一致判定,不一致 fail + 一行修复命令;shim 形态按内容识别)**;exit 0/1/2。不指定 persona 且装了多个 → **逐个自检**(条目加 `<id>.` 前缀),不再整体 `E_SCOPE` |
|
|
103
103
|
| `wt …` | **已实现(需安装包)** | 转发 `packages/wt/bin/dia-wt`(create/adopt/doctor/gate/list/status/check/clean/prune-merged;详见 [worktree.md](worktree.md));未装时 `E_WT`(exit 33),`install.sh` 会分发 |
|
|
104
104
|
| `kb index/search/query/get/lint/path/review` | **已实现** | persona 作用域下索引过期自动重建;显式 `--root` 保持「缺索引即报错」 |
|
|
105
105
|
| `kb <域命令>` | **已实现(委派)** | 未命中内置 → persona `commands` → 委派;argv 透传、exit code 原样 |
|
|
106
106
|
| `<persona-cmd>` / `cmd <name>` | **已实现** | 一级委派(内置优先);`dia cmd` 只查外部命令,可绕开同名内置 |
|
|
107
|
-
| `task add/list/status/done` | **已实现** | done 需 judge + evidence |
|
|
107
|
+
| `task add/list/link/plan/split/status/done` | **已实现** | done 需 judge + evidence;`plan` 写计划态(prio/start/due/progress);`split` 父 depends on 子(单条原子事件,judge 继承) |
|
|
108
|
+
| `graph [--json] [--task T] [--tui …] [--export-html] [--next/--explain/--lint]` | **已实现** | 图 JSON(schema 1 + capabilities);节点带 prio/start/due/progress + 派生 `eff_prio/ready/rank/unblocks/critical` 等;`--export-html` 单文件 HTML;`--next [n]` / `--explain <id>` / `--lint` 只读编排(Kahn-v1,见 [graph-prio-dag.md](design/graph-prio-dag.md)) |
|
|
108
109
|
| `team run --mock / --acp / --dsh`、`team gate/accept/reject` | **已实现** | 状态机 + 门禁;成功/失败路径均跑通 |
|
|
109
110
|
| `job …` | **已实现** | 转发 `packages/job`(launchd/cron/schtasks) |
|
|
110
111
|
| `patch …` | **未实现** | 当前 `unknown command`;设计见 `docs/patching.md` |
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# 优先级 × 依赖:任务编排语义(M1)
|
|
2
|
+
|
|
3
|
+
> 状态:已实现(2026-11-06)|代码:`src/plan.ts`(派生纯函数)、`src/task.ts`(split)、`src/graph.ts`(契约)
|
|
4
|
+
> 相关:`graph-web-m1.md`(导出)、`graph-ui-m1.md`(界面派生量)、`seat-scheduling-m2.md`(席位)、`msg-collab-m1-design.md`(任务账本)
|
|
5
|
+
|
|
6
|
+
## 1. 三个概念,别混
|
|
7
|
+
|
|
8
|
+
| 概念 | 回答 | 谁定 | 可否违反 |
|
|
9
|
+
|---|---|---|---|
|
|
10
|
+
| `prio`(声明优先级) | 这件事本身多重要 | 人 | 可随时改(`plan` 事件留痕) |
|
|
11
|
+
| `deps`(依赖) | 谁必须先完成 | 事实/拆分结果 | **不可违反**(硬约束) |
|
|
12
|
+
| `rank`(执行顺序) | 现在先做哪件 | 由前两者派生 | 建议,不写账本 |
|
|
13
|
+
|
|
14
|
+
结论:**被依赖 ≠ 优先级更高;优先级高 ≠ 先执行**。高优先级任务被卡住时,上游会获得「有效优先级」,否则 P0 永远开不了工。
|
|
15
|
+
|
|
16
|
+
## 2. 有效优先级(eff)
|
|
17
|
+
|
|
18
|
+
```mermaid
|
|
19
|
+
flowchart LR
|
|
20
|
+
C["C: P0(声明)"] --> B["B: P2(声明)"]
|
|
21
|
+
B --> A["A: P3(声明)"]
|
|
22
|
+
C -. "eff P0 传播" .-> B
|
|
23
|
+
C -. "eff P0 传播(2 跳)" .-> A
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- 只从**声明 P0/P1** 的非终态任务向**全部祖先**传播;
|
|
27
|
+
- **不衰减、不累加**(fan-in 取最小/最高;跳数只展示,不参与计算);
|
|
28
|
+
- 终态(done/accepted/dropped/rejected)下游不产生传播;
|
|
29
|
+
- 输出 `eff_prio` + `prio_inherited` + `prio_source{id,declared,distance}`,声明值不变。
|
|
30
|
+
|
|
31
|
+
## 3. 就绪与排序
|
|
32
|
+
|
|
33
|
+
- `satisfied = done | accepted | dropped`(`dropped` 释放下游,但产出 `released_by` 提示「前提失效」);
|
|
34
|
+
- `ready` = 非终态、不在环里、所有 deps 已 satisfied 且无 dangling;
|
|
35
|
+
- `startable` = `ready && status === "todo"`(`doing` 是已开工,`blocked` 是停着,都不进 `--next` 候选);
|
|
36
|
+
- `rank`:**Kahn + 优先队列**(不是先分层再排),key 升序:
|
|
37
|
+
`eff → due(无 due 排末尾)→ critical → slice → -unblocks → depth → created_at → id`;
|
|
38
|
+
- 终态与环内任务 `rank = null`;`--next` 默认取前 5,doing 置顶;
|
|
39
|
+
- **rank 不含「今天」**,保证 `graph --json` 两次运行字节相等;到期紧迫度只在导出时展示。
|
|
40
|
+
|
|
41
|
+
## 4. 关键路径与拆分
|
|
42
|
+
|
|
43
|
+
- `critical` = 非终态声明 P0/P1 的所有祖先(图里橙色高亮);
|
|
44
|
+
- `split`:父 depends on 子(父 = 集成收口),**单条原子 `split` 事件**落账;
|
|
45
|
+
- 子继承父 `judge`(`judge_inherited`),否则 done 门禁会卡死每个切片;
|
|
46
|
+
- `slice` 标记最小可用切片,仅做同级 tiebreak;
|
|
47
|
+
- 组成关系只做元数据(`split_from/split_children`),**绝不作为图边**参与环/排序;
|
|
48
|
+
- 子任务的 eff 由父的声明优先级自然传播得到,不复制 prio(避免 P0 灌水)。
|
|
49
|
+
|
|
50
|
+
## 5. 检查(advisory,exit 0;有环 exit 3)
|
|
51
|
+
|
|
52
|
+
| kind | 含义 | 处置 |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `dangling_dep` | 依赖的 id 不存在 | 补 `link` 或删依赖;不视为就绪 |
|
|
55
|
+
| `released_by_drop` | 依赖已 dropped | 确认下游是否还需要做 |
|
|
56
|
+
| `long_chain` | 声明 P0/P1 的依赖链 ≥3 跳 | 提示拆分(真正的拆分触发点) |
|
|
57
|
+
| `cycle` | 依赖环 | 必须修(exit 3) |
|
|
58
|
+
|
|
59
|
+
## 6. 命令
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
dia task split <id> --into "最小可用,完整实现" [--slice 最小可用] [--force]
|
|
63
|
+
dia graph --next [n] [--json] # 按 rank 的开工建议(doing 置顶)
|
|
64
|
+
dia graph --explain <id> [--json] # 声明/有效/来源/被谁卡/解锁谁/rank
|
|
65
|
+
dia graph --lint [--json] # 编排检查(advisory)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`graph --json` 新增节点字段:`eff_prio` / `prio_inherited` / `prio_source` / `critical` / `ready` /
|
|
69
|
+
`startable` / `released_by` / `dangling_deps` / `depth` / `unblocks` / `rank` / `split_from` /
|
|
70
|
+
`split_children` / `slice`;顶层 `ranking.policy = "kahn-v1"`,`capabilities` 追加 `eff-prio/ready/rank/split`。
|
|
71
|
+
|
|
72
|
+
## 7. 边界
|
|
73
|
+
|
|
74
|
+
- **席位(并发数)不在本模型里**:rank 是单席位理想序;多席位 + 上下文亲和见 [seat-scheduling-m2.md](seat-scheduling-m2.md)(M2,未实现);
|
|
75
|
+
- `claim` 仍是唯一并发信号;eff/rank 只回答「先做什么」,不回答「谁能做」;
|
|
76
|
+
- 派生量全部只读,不写回账本,不进任何门禁。
|
|
77
|
+
|
|
78
|
+
## 8. 反例(自测覆盖)
|
|
79
|
+
|
|
80
|
+
- 链式:A(P3)←B(P2)←C(P0) → A/B eff P0,rank A<B<C;
|
|
81
|
+
- 终态:C 已完成 → 不传播,B 保持 P2;
|
|
82
|
+
- fan-in:一个 P3 被两个 P1 依赖 → eff P1;
|
|
83
|
+
- 悬空:依赖不存在 → not ready + lint;
|
|
84
|
+
- 环:rank null,`--next/--lint` exit 3;
|
|
85
|
+
- P10 vs P2:数字比较(`P2` 在前),无 prio 视为 `P99`。
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# 关系图界面:派生量上屏(M1 UI)
|
|
2
|
+
|
|
3
|
+
> 状态:设计定稿(2026-11-06)|实现:`src/graph-html/*`(待接)|上游:`graph-prio-dag.md`
|
|
4
|
+
> 原则:**排序只在 TS 算一次**(`src/plan.ts`),前端只渲染;导出件仍无网络。
|
|
5
|
+
|
|
6
|
+
## 1. 目标 / 非目标
|
|
7
|
+
|
|
8
|
+
**目标**:把 `eff /ready/rank/critical/blocked/slice` 变成可读、可验证的视觉与交互;让「该不该提优先级、先做哪件」在图上直接确认。
|
|
9
|
+
|
|
10
|
+
**非目标**:不做席位分配(见 `seat-scheduling-m2.md`);不做实时刷新(见 `graph-web-m2.md`);不在前端重算排序。
|
|
11
|
+
|
|
12
|
+
## 2. 数据契约(导出 payload v1.1,additive)
|
|
13
|
+
|
|
14
|
+
| 字段 | 类型 | 说明 |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `eff` | `P0`–`P?` | 有效优先级;`P?` = 未排期(内部 99) |
|
|
17
|
+
| `inherited` | bool | `eff` 严格高于声明值 |
|
|
18
|
+
| `prio_source` | `{id,declared,distance}` | 继承来源(min eff → min distance → min id) |
|
|
19
|
+
| `declared` | `P0`–`P?` | 声明优先级 |
|
|
20
|
+
| `rank` | int \| null | Kahn-v1 全局序号;终态/环内为 null |
|
|
21
|
+
| `rank_key` | `(string\|number)[]` | 8 元组 `[eff,due,critical,slice,-unblocks,depth,created_at,id]`,由 `plan.rankKey()` 生成 |
|
|
22
|
+
| `rank_reason` | string | 首个分胜负的键(如 `eff: P0 < P2`) |
|
|
23
|
+
| `critical` | bool | 非终态声明 P0/P1 的祖先(关键闭包) |
|
|
24
|
+
| `ready` / `startable` | bool | 结构就绪 / 可开工(todo 且 ready) |
|
|
25
|
+
| `blocked_by` / `released_by` / `dangling` | string[] | 未满足依赖 / dropped 释放 / 不存在的依赖 |
|
|
26
|
+
| `due_offset` / `created_offset` | int \| null | 相对 `today` 的天数(前端不做日期算术) |
|
|
27
|
+
| `unblocks` / `depth` | int | 传递下游数 / 最长依赖链 |
|
|
28
|
+
| `slice` / `split_from` / `split_children` | — | 拆分元数据(不是图边) |
|
|
29
|
+
| `next` | `{policy,n,items[]}` | 与 `dia graph --next n` 同源同序 |
|
|
30
|
+
|
|
31
|
+
`schema` 仍为 1;`capabilities` 追加 `eff-lane`、`next-panel`、`rank-factors`。
|
|
32
|
+
**白名单**:不导出 evidence/closure_reason/blocked_reason;`--redact` 时 owner → 短 hash、touches 只留文件名。
|
|
33
|
+
|
|
34
|
+
## 3. 泳道口径
|
|
35
|
+
|
|
36
|
+
- 默认 **按 `eff`** 分行;工具栏 `声明 / 有效` 切换,URL `pl=eff|declared`(非默认才写);
|
|
37
|
+
- `laneOf(t) = pl==='declared' ? declared : eff`;泳道按 `prioIndex` 升序,`P?` 恒在最后;
|
|
38
|
+
- 切到「声明」时,被继承抬升的卡片显示 `↑P0`,让人看见「它其实更急」;
|
|
39
|
+
- **迁移说明**:默认从 declared 改 eff 属可见变更,导出件页头标注「泳道=有效优先级」,并更新既有截图基线。
|
|
40
|
+
|
|
41
|
+
## 4. 卡片徽标(多槽,不是单槽竞争)
|
|
42
|
+
|
|
43
|
+
| 槽 | 内容 | 规则 |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| 描边 | `critical` | 紫色描边(不与 P1 橙冲突);环内任务改用红色虚线描边 |
|
|
46
|
+
| 角标 | `#rank` / `#—` | 常驻;终态 `#✓`;环内 `#⚠`;过滤视图标「全局」 |
|
|
47
|
+
| 徽标行 | `↑P0`、`🔒2`、`slice` | 最多 2 枚 + `+N` 溢出;`🔒n` = 未满足依赖数(dangling 计入,tooltip 标 missing) |
|
|
48
|
+
| 状态 | `⏸ 人工 blocked` | 与依赖阻塞分开:`status==='blocked'` 但 deps 全满足时只显示这个 |
|
|
49
|
+
|
|
50
|
+
`tooltip` 固定模板(可 DOM 断言):
|
|
51
|
+
- `有效 P0 ← P0@t-3c1f(1 跳)· 声明 P3`
|
|
52
|
+
- `等 2 个前置:t-a(todo)、t-b(missing)`
|
|
53
|
+
- `解锁 3 个下游 · 关键闭包`
|
|
54
|
+
|
|
55
|
+
点击 `↑P0`:清空 `sel`、必要时切到「全部时间」、滚动居中并打开来源任务抽屉;来源被截断/时间窗隐藏时只 toast「来源不在当前视图」,不静默改视图。
|
|
56
|
+
|
|
57
|
+
## 5. 右侧「下一步」面板
|
|
58
|
+
|
|
59
|
+
- 与 `dia graph --next 5` **同源**(共用 `buildNext()`),顺序 `doing(rank 升序)→ startable(rank 升序)`;
|
|
60
|
+
- 每条显示 `#rank 标题 · eff · 理由(解锁 N / 关键闭包 / 继承来源)`;
|
|
61
|
+
- doing 超过 5 个 → 前 5 + 「另有 N 个进行中」;环内 doing 单列「异常」区;
|
|
62
|
+
- 空态:区分「账本为空」与「时间窗内无任务」(后者给「放宽到全部时间」)。
|
|
63
|
+
|
|
64
|
+
## 6. 任务抽屉「四问」
|
|
65
|
+
|
|
66
|
+
1. **优先级**:声明 / 有效 / 来源链(几跳、原始声明);
|
|
67
|
+
2. **等谁**:`blocked_by` 明细(各自 status;missing 标红);`released_by` 单独提示「前提已失效」;
|
|
68
|
+
3. **解锁谁**:直接 dependents 列表 + 传递计数 `unblocks`;
|
|
69
|
+
4. **为什么这个序**:`rank_key` 8 项逐项取值 + `rank_reason`(首个分胜负项)。
|
|
70
|
+
|
|
71
|
+
命令页保持只读(复制 `dia task plan/claim/msg`),不假装能发送。
|
|
72
|
+
|
|
73
|
+
## 7. 关键闭包(原“关键路径”)
|
|
74
|
+
|
|
75
|
+
- `critical` = 非终态声明 P0/P1 的**祖先闭包**(不是最长路,菱形会多分支)——文案统一写「关键闭包」,避免过度承诺;
|
|
76
|
+
- 节点:紫色描边;边:`depends_on && (from.critical || 声明 P0/P1)` 且 `to.critical`,描边同色;
|
|
77
|
+
- 与选中/阻塞叠加:描边只占一层,选中用外发光,阻塞用左侧锁条。
|
|
78
|
+
|
|
79
|
+
## 8. 截断与规模
|
|
80
|
+
|
|
81
|
+
| 可见卡片 | 行为 |
|
|
82
|
+
|---|---|
|
|
83
|
+
| ≤ 120 | 全功能(详细模式可用) |
|
|
84
|
+
| 121–200 | 强制简约;顶部黄条「已进入大图模式」 |
|
|
85
|
+
| 201–500 | 仅 `layout=dep` 且禁用「详细」;`--all` 才能突破 |
|
|
86
|
+
| > 500 | 拒绝渲染(exit 2),提示缩小时间窗或 `--limit` |
|
|
87
|
+
|
|
88
|
+
- 截断顺序:rank 非空升序 → 终态按 `closed_at` 降序保留前 20 → 声明优先级 → id;
|
|
89
|
+
- **deps 与 edges 一律按 `shownIds` 过滤**,卡片标 `deps_truncated`,避免「有依赖没卡片/没边」;
|
|
90
|
+
- `truncated={shown,total,hidden_ids≤20,policy:'rank-first'}`;`--limit N` 显式覆盖阈值但记录警告。
|
|
91
|
+
|
|
92
|
+
## 9. URL 与深链
|
|
93
|
+
|
|
94
|
+
`#/graph?layout=priority|dep&pl=eff|declared&w=7|14|30|0&sel=a,b&task=t-x&tab=task|intervene`
|
|
95
|
+
- 互斥:改 `w` 清 `from/to`;`layout=dep` 时 `pl` 保留但不生效;
|
|
96
|
+
- `go()`/`toggleSel()` 重建 hash 必须保留未知键(`pl`);
|
|
97
|
+
- `from/to` 偏移以 payload 的 `today` 为基准(删除模板里 2026-11-06 常量)。
|
|
98
|
+
|
|
99
|
+
## 10. 验收
|
|
100
|
+
|
|
101
|
+
- DOM:继承卡片有 `↑P0` 且 tooltip 含来源 id/跳数;`pl=eff` 与 `pl=declared` 泳道标签不同;
|
|
102
|
+
- 一致性:`payload.next.items` 与 `dia graph --next 5 --json` 逐项相等;
|
|
103
|
+
- 抽屉:四问非空,`rank_key` 8 项;`blocked_by` 含 missing 标注;
|
|
104
|
+
- 降级:环 / 悬空 / 全终态 / 空账本 / 时间窗空 五态不抛错、各有文案;
|
|
105
|
+
- 确定性:同一 `--now` 两次导出字节一致;无 `fetch(`/外链;
|
|
106
|
+
- 截图 5 张:有效泳道、声明泳道、下一步面板、抽屉四问、时间线(关键闭包+开放端)。
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# dia graph Web(M1)设计:真实数据 → 单文件静态导出
|
|
2
|
+
|
|
3
|
+
> 状态:M1 设计(2026-11-06)|前置:`graph-web.md`(视觉稿 v0.3)、`graph-web-mockup.html`(交互原型)
|
|
4
|
+
> 交付形态拍板:**先 A 后 B** —— M1 只做 `dia graph --export-html`;M2 再做 loopback 实时服务(见 [graph-web-m2.md](graph-web-m2.md));界面派生量见 [graph-ui-m1.md](graph-ui-m1.md)。
|
|
5
|
+
|
|
6
|
+
## 1. 目标 / 非目标
|
|
7
|
+
|
|
8
|
+
**目标**
|
|
9
|
+
|
|
10
|
+
- `dia graph --export-html [path]` 把真实账本渲染成**单文件 HTML**(CSS/JS/数据全内联),`file://` 打开即用;
|
|
11
|
+
- 视觉与交互沿用 mockup:优先级泳道 / 依赖视图、时间线、搜索 token、邻域聚焦、任务抽屉;
|
|
12
|
+
- 数据缺口显式降级,不伪造:缺字段有兜底并标注;
|
|
13
|
+
- 全程无网络:无端口、无外链、无 `fetch`。
|
|
14
|
+
|
|
15
|
+
**非目标(M1 不做)**
|
|
16
|
+
|
|
17
|
+
- 实时刷新、介入消息真正发送(M2 live 服务);
|
|
18
|
+
- 服务端过滤/鉴权、多人共享;
|
|
19
|
+
- 把 mockup 的演示数据(t-3/t-4、claims=3、live data)带进导出件。
|
|
20
|
+
|
|
21
|
+
## 2. 流水线
|
|
22
|
+
|
|
23
|
+
```mermaid
|
|
24
|
+
flowchart LR
|
|
25
|
+
L[账本 ledger.jsonl] --> F[task.fold<br/>add/link/plan/status]
|
|
26
|
+
F --> T[Task[]<br/>prio/start/due/progress/closed_at]
|
|
27
|
+
T --> G[graph.build<br/>schema 1 + capabilities]
|
|
28
|
+
G --> R[graph-html.render<br/>白名单 + JSON 转义]
|
|
29
|
+
R --> H[单文件 HTML<br/>file:// 打开]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- 模板以 **TS 字符串常量**进 `src/graph-html/template.ts`,被 `bun build` 打进 `dist/cli.js`(`install.sh` 只拷 `dist`,不得运行时读文件);
|
|
33
|
+
- 渲染函数 `src/graph-html/render.ts` 纯函数:`render(graph, {now, redact}) -> string`,无 IO、无时钟(时间由调用方注入)。
|
|
34
|
+
|
|
35
|
+
## 3. 数据模型(Task 扩展)
|
|
36
|
+
|
|
37
|
+
| 字段 | 类型 | 语义 | 缺失时 |
|
|
38
|
+
|---|---|---|---|
|
|
39
|
+
| `prio` | `P0`–`P9` | 计划优先级(与 status 正交) | 归入 `P?` 末行泳道 |
|
|
40
|
+
| `start` | `YYYY-MM-DD` | **计划**开始(本地日) | 回填 `created_at` 的本地日,卡片标「按创建」 |
|
|
41
|
+
| `due` | `YYYY-MM-DD` | **计划**结束/截止 | 未完成 → 时间线画开放端;已完成 → 用 `closed_at` |
|
|
42
|
+
| `progress` | `0–100` | 人工进度 | 按 status 派生(todo 0 / doing 50 / review 80 / done 100 / blocked 0),标「估算」 |
|
|
43
|
+
| `closed_at` | ISO ts(派生) | 进入 done/accepted 的时间 | 旧账本:无(开放端) |
|
|
44
|
+
|
|
45
|
+
**账本事件**:新增 `{"event":"plan","id",prio?,start?,due?,progress?}`,只覆盖出现的字段;
|
|
46
|
+
`fold` 对 `plan` 单独分支(不得落入 status 分支,避免 plan 编辑伪造 `updated_at` 的语义);
|
|
47
|
+
`status` 事件在进入 `done|accepted` 时写 `closed_at`,回到进行态时清除。
|
|
48
|
+
|
|
49
|
+
**旧账本兼容**:additive,无需迁移;无字段走 §3 兜底,UI 不得因缺 `prio/progress` 抛错。
|
|
50
|
+
|
|
51
|
+
## 4. Graph 契约(schema 1,additive)
|
|
52
|
+
|
|
53
|
+
`GraphNode` 增加(全部可选):`prio`、`start`、`due`、`progress`、`progress_derived`、`created_at`、`updated_at`、`closed_at`、`deps`、`touches`。
|
|
54
|
+
`Graph` 顶层增加 `capabilities: ["prio","timeline","progress"]`。
|
|
55
|
+
`schema` 仍为 `1`;输出必须**确定性**(不写生成时间、不依赖 locale、稳定排序),满足现有「两次 `--json` 字节相等」测试。
|
|
56
|
+
补 `tests/fixtures/graph.v1.json` 作为 additive 回归网。
|
|
57
|
+
|
|
58
|
+
## 5. 渲染与安全
|
|
59
|
+
|
|
60
|
+
- 数据注入:`<script type="application/json" id="dia-graph">`,序列化时 `JSON.stringify(x).replace(/</g,'\\u003c')`;
|
|
61
|
+
- 模板内 JS 只读该 JSON,不拼接未转义的 label/owner;`progress` 一律 `Number()` 钳位;
|
|
62
|
+
- `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data:">`;
|
|
63
|
+
- **字段白名单**:只导出 id/title/status/prio/start/due/progress/deps/touches/owner;**永不导出** `evidence` / `closure_reason` / `blocked_reason`;
|
|
64
|
+
- `--redact`:owner 变短 hash、touches 只保留文件名;
|
|
65
|
+
- 默认输出 `~/.dia/export/graph-<YYYYMMDD-HHmm>.html`,权限 `0600`,stdout 只打印路径;`--out <path>` 可覆盖(拒绝覆盖已存在文件,除非 `--force`)。
|
|
66
|
+
|
|
67
|
+
## 6. 交互(沿用 mockup,去掉演示态)
|
|
68
|
+
|
|
69
|
+
- 两种布局:优先级泳道(P 数字升序,`P?` 最后)/ 依赖视图(深度从左到右);
|
|
70
|
+
- **不折叠**;默认时间窗「近 7 天开始(按 start,缺失=始终可见),结束不限」;可选 14/30/全部;
|
|
71
|
+
- 搜索:选中回显 tag(`sel` 多选),`↑↓/Enter/Esc/Backspace/×`;
|
|
72
|
+
- 聚焦:邻域 1/2/全链路;**sel 与时间窗取交集**(避免 URL 表达矛盾;空交集给出提示);
|
|
73
|
+
- 时间线:左侧固定列、缩放 14–84px/天、开放端用渐隐箭头;日期刻度按可用宽度动态取步长;
|
|
74
|
+
- 抽屉:只读详情;原「介入」区改成**复制命令**(`dia msg send …` / `dia task plan …`),不假装能发送。
|
|
75
|
+
|
|
76
|
+
## 7. 异常与边界
|
|
77
|
+
|
|
78
|
+
- 空账本 → 空态页(说明如何 `dia task add`),不渲染空泳道;
|
|
79
|
+
- 依赖环 → 顶部警示条,环内任务正常渲染;
|
|
80
|
+
- 超大账本(>500)→ 渲染前 N 条 + 顶部「已截断 N 条,用 `--all` 渲染全部」;时间线行高固定、后续再虚拟化;
|
|
81
|
+
- 时区:所有日粒度换算用本地日(手拼 `YYYY-MM-DD`),禁止 `toISOString()` 参与日粒度比较;
|
|
82
|
+
- `from/to` 深链与 `w` 互斥:改 `w` 时清空 `from/to`。
|
|
83
|
+
|
|
84
|
+
## 8. 测试 / 验收
|
|
85
|
+
|
|
86
|
+
- 单测:plan 往返、plan 不覆盖未提供字段、旧账本可读、graph 确定性、fixture 兼容、HTML 注入转义(`</script>`)、无外链/无 `fetch(`/无 `node:http`、空账本、截断、红action;
|
|
87
|
+
- 视觉:Edge headless 截图 4 张(优先级/依赖/详情/时间线)人工验收;
|
|
88
|
+
- judge:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
bun test tests && bash tests/smoke.sh && \
|
|
92
|
+
T=$(mktemp -d) && DIA_ROOT=$T bun packages/cli/src/index.ts graph --export-html --out $T/g.html --now 2026-11-06T10:00:00Z >/dev/null && \
|
|
93
|
+
grep -q '__DIA_GRAPH__\|id="dia-graph"' $T/g.html && ! grep -Eq 'fetch\(|node:http|src="http|@import' $T/g.html
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 9. 发布顺序(每步可独立 MR)
|
|
97
|
+
|
|
98
|
+
1. **M1a 数据**:`task.ts`(字段 + plan + closed_at)+ CLI `dia task plan` + 单测;
|
|
99
|
+
2. **M1b 契约**:`graph.ts` 增字段 + `capabilities` + fixture + TUI 兼容(未知字段忽略);
|
|
100
|
+
3. **M1c 渲染**:`src/graph-html/{template,render}.ts` + 测试;
|
|
101
|
+
4. **M1d 入口**:`--export-html/--out/--redact/--now/--all` + smoke + 文档;
|
|
102
|
+
5. M2 live 服务另立设计(`packages/graph-web`,loopback + token + 只读 API)。
|
|
103
|
+
|
|
104
|
+
## 10. 风险
|
|
105
|
+
|
|
106
|
+
| 风险 | 缓解 |
|
|
107
|
+
|---|---|
|
|
108
|
+
| 模板塞进 TS 后难维护 | mockup 保留为设计源;模板只做数据换源;结构 marker 测试 |
|
|
109
|
+
| 缺 start 的旧账本被「近 7 天」隐藏 | 缺失 = 始终可见(§3) |
|
|
110
|
+
| 派生 progress 被当真实进度 | `progress_derived` + 斜纹 + 「估算」标注 |
|
|
111
|
+
| 导出件含内部信息 | 白名单 + 0600 + 文档警示;`--redact` |
|
|
112
|
+
| 不折叠 → 1000 任务画布 23 万 px | 默认截断 + 提示;M2 再做虚拟化 |
|
|
113
|
+
| 双击/单击手势冲突 | 单击=抽屉,长按/⌘ 点击=选中;或抽屉改「详情」按钮(M1c 定稿前实测) |
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# graph live 服务(M2 设计,可选包)
|
|
2
|
+
|
|
3
|
+
> 状态:设计定稿(2026-11-06,未实现)|前置:`graph-web-m1.md`(静态导出)、`graph-ui-m1.md`、`seat-scheduling-m2.md`
|
|
4
|
+
> 定位:本机**只读**实时预览;不写账本、不做多人共享、不暴露公网。
|
|
5
|
+
|
|
6
|
+
## 1. 目标 / 非目标
|
|
7
|
+
|
|
8
|
+
**目标**:把 M1 的静态快照变成「自动刷新」的只读页面,复用同一套 payload 与 UI。
|
|
9
|
+
|
|
10
|
+
**非目标**:多人协作/鉴权体系;写操作(介入、plan、claim);守护进程/开机自启;公网/局域网访问。
|
|
11
|
+
|
|
12
|
+
## 2. 包边界(为什么不在 `src/`)
|
|
13
|
+
|
|
14
|
+
- `tests/static.test.ts` 的意图是「框架核心零网络」;`src/**` 永远不出现 `node:http`;
|
|
15
|
+
- 服务实现放 `packages/graph-web/`(与 `packages/job`、`packages/wt` 同构),独立 `bin/dia-graph-web`;
|
|
16
|
+
- `dia graph --serve` 只做 **spawn**(`child_process`),由 `src`/CLI 转发,不 import http——保持静态约束与测试不变;
|
|
17
|
+
- 未安装该包时,`--serve` 输出明确安装命令(`bash install.sh`),不静默失败。
|
|
18
|
+
|
|
19
|
+
## 3. 进程与生命周期
|
|
20
|
+
|
|
21
|
+
- 前台阻塞运行,`Ctrl-C` 退出;不自动重启、不写 pid 文件、不依赖 launchd/systemd;
|
|
22
|
+
- 一次运行 = 一个浏览器会话;重启即换 token 与端口;
|
|
23
|
+
- `--no-open`:只打印 URL;默认尝试 `open`/`xdg-open`/`start`,失败则仅打印提示。
|
|
24
|
+
|
|
25
|
+
## 4. 网络面与 token
|
|
26
|
+
|
|
27
|
+
- 只绑 `127.0.0.1`;默认 `--port 0`(内核分配),启动后从 `server.address()` 取真实端口;
|
|
28
|
+
- 拒绝 `0.0.0.0` 与 IPv6 通配;显式 `--port` 也只绑 loopback;
|
|
29
|
+
- **token**:32B 随机 base64url,进程内有效(**不是单次**),所有 `/api/*` 要求 `X-Dia-Token`,比较用 `timingSafeEqual`;
|
|
30
|
+
- 传递:URL fragment `#t=<token>` → 启动时读取 → `sessionStorage` → `history.replaceState` 立即抹掉;**不能**与路由 hash 抢位(先剥离 token 再交给 `#/graph`);
|
|
31
|
+
- 输出:仅 TTY 打印完整 URL;非 TTY 打码,并把完整 URL 写入 `~/.dia/export/serve-<pid>.url`(0600),打印路径;
|
|
32
|
+
- 401:页面显示「会话已失效,请回到终端重新打开」+ 可复制的重开命令。
|
|
33
|
+
|
|
34
|
+
## 5. API(只读)
|
|
35
|
+
|
|
36
|
+
| 方法 | 路径 | 返回 |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| GET | `/api/graph?rev=` | `{schema,rev,payload}`(与导出件同一 `buildPayload`) |
|
|
39
|
+
| GET | `/api/next?n=5&seats=S` | 复用 `buildNext` / 席位分配(`seats` 缺省 1) |
|
|
40
|
+
| GET | `/api/meta` | `{version, capabilities, seats_default, presence_ttl_s, readonly:true}` |
|
|
41
|
+
| GET | `/api/health` | `{ok:true}`(同样鉴权,不泄露版本细节) |
|
|
42
|
+
|
|
43
|
+
- 非 GET → 405;未知路径 → 404;错误 envelope `{error:{code,message}}`,不返回堆栈;
|
|
44
|
+
- 响应头:`Cache-Control: no-store`、`X-Content-Type-Options: nosniff`、`Cross-Origin-Resource-Policy: same-origin`;
|
|
45
|
+
- `rev = sha1(ledger mtimeMs + size + tasks 数 + claims 数)` 前 12 位;**不含** `generated_at/today/presence_at`;
|
|
46
|
+
- 带 `If-None-Match` 或 `?rev=` 相同 → `304`(不重传 payload)。
|
|
47
|
+
|
|
48
|
+
## 6. 刷新
|
|
49
|
+
|
|
50
|
+
- 默认 **2s 轮询** `/api/graph?rev=<当前>`;`304` 只更新「已检查」时间;页面隐藏时暂停(`visibilitychange`);
|
|
51
|
+
- 失败:保留上一帧 + 顶部「离线,最后更新 HH:mm:ss」;连续失败退避 2→4→8s(上限 30s);
|
|
52
|
+
- SSE 推迟到 M2.1:单机只读场景收益小于长连接复杂度;
|
|
53
|
+
- `generated_at` 用**服务端**时间,前端不猜;快照语义与导出件一致。
|
|
54
|
+
|
|
55
|
+
## 7. 安全
|
|
56
|
+
|
|
57
|
+
- `Host` 必须精确等于 `127.0.0.1:<port>`(或 `localhost:<port>`),否则 421(防 DNS rebinding);
|
|
58
|
+
- `Origin` 存在时必须同源;`Sec-Fetch-Site` 非 `same-origin`/`none` 拒绝;
|
|
59
|
+
- **不发 CORS 头**;`OPTIONS` 一律 405;
|
|
60
|
+
- CSP:`default-src 'none'; connect-src 'self'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; frame-ancestors 'none'`(内联脚本是当前模板现状,迁 nonce 列入 M2.1);
|
|
61
|
+
- 日志:只记 `{ts, method, path, status}`,不记 token/query/owner/路径;错误不回显 `req.url`;
|
|
62
|
+
- 文档警示:带 token 的 URL 仍会进浏览器历史/终端 scrollback/截图,别外发。
|
|
63
|
+
|
|
64
|
+
## 8. 分发与安装
|
|
65
|
+
|
|
66
|
+
- `packages/graph-web/{package.json,src/,bin/}`;根 `files` 增该目录;`install.sh` 用 `[ -d packages/graph-web ]` 保护拷贝(旧树不报错);
|
|
67
|
+
- `dia doctor` 增一项「graph-web 可选包」,缺包只 warn + 修复命令;
|
|
68
|
+
- 兼容:`dia graph --export-html` 始终可用,作为服务不可用时的兜底。
|
|
69
|
+
|
|
70
|
+
## 9. 失败回退
|
|
71
|
+
|
|
72
|
+
| 情况 | 行为 |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `--port` 被占 | 不自动换端口;`E_PORT` + 提示去掉 `--port` 用随机端口 |
|
|
75
|
+
| 浏览器打不开 | 打印 URL 与 `--no-open` 提示 |
|
|
76
|
+
| 无 bun/node | `E_RUNTIME` + 安装命令(node ≥ engines 下限) |
|
|
77
|
+
| 账本不可读/坏行 | 坏行按 fold 容忍;整文件不可读 → 503 + 页面错误条,不白屏 |
|
|
78
|
+
| 模板缺失 | `E_TEMPLATE`(打包把模板文本内联,理论不发生) |
|
|
79
|
+
| 账本被删/重建 | `rev` 变化触发全量重渲染,不复用旧 id |
|
|
80
|
+
|
|
81
|
+
## 10. 测试与发布顺序
|
|
82
|
+
|
|
83
|
+
1. **M2a 骨架**:包 + bin + 只绑 loopback + `/api/health`(单测:Host/端口/token 401);`
|
|
84
|
+
2. **M2b API**:`/api/graph` 与导出件同源对比、`rev/304`;
|
|
85
|
+
3. **M2c 安全**:Host/Origin/CSP/日志负例;静态测试仍全绿;
|
|
86
|
+
4. **M2d 分发**:install.sh 旧树/新树、doctor、`--serve` 未装提示。
|
|
87
|
+
|
|
88
|
+
判据:`bun test tests`(含新包测试)+ `bash tests/smoke.sh` + 手工 `curl` 负例 + Edge headless 一帧。
|
|
89
|
+
|
|
90
|
+
## 11. 待定(实现前确认)
|
|
91
|
+
|
|
92
|
+
- SSE 是否 M2.1;写接口是否永远不做(当前结论:只读 + 复制命令)。
|