@dsh-workbench/core 0.1.9 → 0.1.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.
Files changed (42) hide show
  1. package/bin/dsh-workbench +23 -0
  2. package/client.js +609 -84
  3. package/cordis.patch.yml +11 -3
  4. package/dist/client/client.body.js +609 -84
  5. package/dist/client/index.js +610 -85
  6. package/dist/host/index.js +406 -276
  7. package/dist/types/app/client.d.ts +140 -6
  8. package/dist/types/app/layouts/rightbar/BoardEmpty.d.ts +2 -1
  9. package/dist/types/app/layouts/rightbar/Rightbar.d.ts +1 -0
  10. package/dist/types/app/layouts/rightbar/board-plan.d.ts +2 -0
  11. package/dist/types/app/layouts/sidebar/ProjectSwitcher.d.ts +2 -2
  12. package/dist/types/app/layouts/sidebar/Sidebar.d.ts +1 -1
  13. package/dist/types/app/layouts/sidebar/WorkspaceRenameDialog.d.ts +8 -0
  14. package/dist/types/app/layouts/sidebar/WorkspaceRowAction.d.ts +7 -0
  15. package/dist/types/app/layouts/sidebar/WorkspaceRowLabel.d.ts +8 -0
  16. package/dist/types/app/layouts/sidebar/WorkspaceSearchField.d.ts +9 -0
  17. package/dist/types/app/layouts/sidebar/__tests__/workspace-name.test.d.ts +1 -0
  18. package/dist/types/app/layouts/sidebar/workspace-items.d.ts +11 -0
  19. package/dist/types/app/layouts/sidebar/workspace-name.d.ts +16 -0
  20. package/dist/types/app/units/__tests__/registry.test.d.ts +1 -0
  21. package/dist/types/app/units/fixture-unit-mock.d.ts +3 -1
  22. package/dist/types/modules/panels/__tests__/approval.test.d.ts +1 -0
  23. package/dist/types/modules/panels/model/approval.d.ts +2 -0
  24. package/dist/types/shared/api/diy.d.ts +3 -0
  25. package/dist/types/shared/api/diy.test.d.ts +1 -0
  26. package/dist/types/shared/api/host/__tests__/approval-legacy.test.d.ts +1 -0
  27. package/dist/types/shared/api/host/approval-legacy.d.ts +2 -0
  28. package/dist/types/shared/api/host/diy.d.ts +3 -0
  29. package/dist/types/shared/api/host/diy.test.d.ts +1 -0
  30. package/dist/types/shared/api/host/mount-units.d.ts +13 -6
  31. package/dist/types/shared/hooks/mock-mode.d.ts +1 -0
  32. package/package.json +13 -5
  33. package/preset/diy.patch.yml +156 -0
  34. package/scripts/seed-profile.sh +116 -0
  35. package/skills/workbench-module-diy/BUILD.md +179 -0
  36. package/skills/workbench-module-diy/DESIGN.md +81 -0
  37. package/skills/workbench-module-diy/LINKAGE.md +113 -0
  38. package/skills/workbench-module-diy/PANEL.md +124 -0
  39. package/skills/workbench-module-diy/REVIEW.md +31 -0
  40. package/skills/workbench-module-diy/SKILL.md +54 -0
  41. package/skills/workbench-module-diy/STEPS.md +93 -0
  42. package/skills/workbench-module-diy/TOOLS.md +112 -0
@@ -0,0 +1,179 @@
1
+ # 实现参考(DIY 域):把模块造成一个独立包,装进工坊
2
+
3
+ 本文只管"怎么落"。设计口径看 [DESIGN.md](DESIGN.md) 与 [STEPS.md](STEPS.md);
4
+ 门禁红了看 [REVIEW.md](REVIEW.md)。
5
+
6
+ ## 0 一个模块就是一个独立的 dsh 插件包
7
+
8
+ 它不是工作台源码的一部分,**不认识工作台源码**:它把自己插进 dsh 的 profile,
9
+ 外壳通过服务名 `workbenchUnits` 收下它。所以"造模块"只发生在一个目录里,不动外壳一个字节。
10
+
11
+ 它同时是两半:
12
+
13
+ | 半 | 文件 | 跑在哪 | 干什么 |
14
+ |---|---|---|---|
15
+ | 宿主半侧 | `src/host.ts` → `dist/host.js` | Node(dsh 宿主进程) | 注册服务、路由、agent 工具、长任务 |
16
+ | 浏览器半侧 | `src/client.tsx` → `client.js` | 页面里 | 画那张卡片、注册审批明细渲染器 |
17
+
18
+ 两侧不许互相 import。它们只通过外壳交出来的服务名相接。
19
+
20
+ ## 1 落点:工坊
21
+
22
+ ```
23
+ $DSH_HOME/workbench/diy/ ← 工坊(一个 pnpm workspace,依赖已经装好)
24
+ package.json pnpm-workspace.yaml .npmrc node_modules/
25
+ packages/
26
+ module-<unit>/ ← 你要造的模块住这儿
27
+ ```
28
+
29
+ 一个模块一个目录,一个目录一个包。**不要**把源码写到别处,也不要去动工坊根上的配置。
30
+
31
+ ## 2 从模板改,不从零写
32
+
33
+ ```bash
34
+ dsh-wb-module init <unit> --dir <工坊>/packages/module-<unit> --description "<一句话>"
35
+ ```
36
+
37
+ `<unit>` 是小写短横线标识(如 `my-thing`),它同时是单元 id、包名后缀、卡片样式前缀。
38
+ **它必须全局唯一** —— 和工作台里其它模块、和外壳里已有的单元都不能撞;撞了装上去当场抛。
39
+
40
+ 生成的骨架是**能直接跑的**(十条判据 + 三个脚本开箱全绿;门禁还在,模板改坏了当场报)。
41
+
42
+ **两个模板,按需要挑**(加 `--template <名字>`):
43
+
44
+ | 模板 | 是什么 | 什么时候用 |
45
+ |---|---|---|
46
+ | `minimal`(不写就是它) | 空壳:卡壳、文案、测试都接好了,但**没有数据源** | 你已经清楚要读什么、想从零写 |
47
+ | `diy` | 一张**读当前项目的样板卡**:宿主侧一条真读目录的只读路由、卡片三态、`?wbMock=1` 三档夹具 | 你要照着改出一个"真能出数据"的最小版本 —— **建议先用它** |
48
+
49
+ ⚠️ `diy` 那份是**给别人照抄的**,所以它自己也必须开箱全绿 —— 门禁会**两个模板各 init 一遍、各跑一遍全套**。
50
+
51
+ 十一个文件各有其职:
52
+
53
+ | 文件 | 职责 |
54
+ |---|---|
55
+ | `package.json` | 自述清单:包名、`dsh.bundle.patch`、`dsh.client`、`dsh.workbench.unit/faces`、四个脚本、peer 依赖 |
56
+ | `cordis.patch.yml` | 把自己插进 profile 的那一行 |
57
+ | `src/host.ts` | 宿主面:`manifest` + `mount()`(路由 / 工具) |
58
+ | `src/client.tsx` | 浏览器面:`manifest` + `card` |
59
+ | `src/messages.ts` | zh / en 文案;键必须带 `<unit>.` 前缀,两种语言同构 |
60
+ | `src/plugin.test.ts` | 单测:两侧都把自己的面注册进了外壳 |
61
+ | `vitest.config.ts` | 测试配置(`@/modules/<unit>/` 的别名) |
62
+ | `scripts/smoke.mjs` | `selfcheck`:把 `client.js` 当浏览器加载器跑一遍,看卡片真进没进外壳 |
63
+ | `tsconfig.json` | `paths` 里 `@/modules/<unit>/*` → `./src/*`,值必须是**相对路径** |
64
+ | `types/globals.d.ts` | 经典 JSX 运行时的全局类型垫片 |
65
+ | `README.md` | 这个模块自己的说明 |
66
+
67
+ 改哪一个:**画什么改 `src/client.tsx`,文案改 `src/messages.ts`**,需要能力再动 `src/host.ts`。
68
+ 模板已经把卡片套好了外壳的卡壳(`kit.components.Card`)、三态与测试;你只改里面。
69
+
70
+ ## 3 三个命令 + 四个脚本
71
+
72
+ ```bash
73
+ dsh-wb-module build # 出 client.js 与 dist/host.js
74
+ dsh-wb-module check # 十条判据,全跑
75
+ dsh-wb-module check --only cards # 单跑一条(contracts / bundles / source / primitives /
76
+ # kit / cards / standalone / markers / alias / style)
77
+ ```
78
+
79
+ `package.json` 里那四个脚本都要留着(契约判据会查):
80
+
81
+ ```bash
82
+ pnpm build # 构建
83
+ pnpm typecheck # tsc --noEmit
84
+ pnpm test # vitest
85
+ pnpm selfcheck # 构建 + 把 client.js 当加载器跑一遍
86
+ ```
87
+
88
+ **自己的门禁自己过,红了别抛给用户。** 十条判据在 [REVIEW.md](REVIEW.md) 里逐条翻了人话。
89
+
90
+ ## 4 两个面的形状
91
+
92
+ 宿主面(`src/host.ts`):
93
+
94
+ ```ts
95
+ export const name = '<包名>';
96
+ export const inject = ['workbenchUnits'];
97
+
98
+ export function apply(ctx) {
99
+ const units = ctx.get('workbenchUnits');
100
+ if (units === undefined) return; // 外壳没交这个服务:报一声,别静默
101
+ ctx.effect(() => units.register({ manifest, mount }), '<unit>: 注册');
102
+ }
103
+ ```
104
+
105
+ `mount()` 返回 `{ routes, registerTools, prefs? }`:
106
+ `routes` 是交给外壳去挂 `/workbench/api/<路由>` 的只读投影(只许 GET);
107
+ `registerTools` 交出这个模块的 agent 工具。**路由的键必须住自己那一格**(`<unit>` 或 `<unit>/…`),
108
+ 否则装配期直接抛;只读路由要表达"出错"时回 `{ ok: false, error: { code, message, hint? } }`(HTTP 仍是 200,外壳原样转发)。
109
+
110
+ 浏览器面(`src/client.tsx`):`units.registerUnit({ manifest, messages, card })`。
111
+ `card` 拿到 `{ t, world, refreshToken, onRefresh, onSummary }`(`world.current.workspace` 就是当前项目),
112
+ 返回一棵元素树。**三态用外壳给的组件,别自己画**:
113
+ 加载 → `kit.components.SkeletonRows`;空 → `kit.components.EmptyState`(不带按钮);
114
+ 出错 → `kit.components.BlockerState` + `kit.components.blockerOfError(error, t)`。
115
+ ⚠️ **`idle` 是独立的一格**(没工作区、或取数被 `enabled: false` 关掉时),不许画成"空" ——
116
+ "空"和"还没开始"是两件事,说错了他会去查他根本没配的东西。
117
+
118
+ **卡片必须在拿不到数据时也画得出来**(第一次读、卡在审批、平台没登录)。
119
+ 空数据下抛异常的卡片 = 用户眼里的"卡片一直不出来"。这是 `check:cards` 守的那条。
120
+
121
+ ## 5 依赖从哪拿
122
+
123
+ | 要什么 | 从哪拿 |
124
+ |---|---|
125
+ | 宿主侧要用的纯函数与常量(`param`、平台解析、工作区存储…) | `@dsh-workbench/shared/host`(写进 `dependencies`) |
126
+ | 浏览器侧要的一切(hooks、组件、图标、文案工具) | `@dsh-workbench/core/client` 的 `kit` |
127
+ | 类型 | `import type` 随便用(编译时擦除,不会进产物) |
128
+ | 外壳本体 | `@dsh-workbench/core`(peer 依赖,装的时候要比版本) |
129
+
130
+ ⚠️ **浏览器侧不许 `import` `@dsh-workbench/shared` 的运行值**(两个入口都不行):
131
+ 它**不在**客户端构建的 external 默认表里(`module-tools/src/build.mjs`),
132
+ import 运行值会被**打进你的包**(第二份实例),而且浏览器运行时也解析不到它。
133
+ 宿主侧正相反:`@dsh-workbench/shared` 与 `/host` 都在 external 里,直接 import 才对。
134
+
135
+ **单例只能有一份**:kit 里的 hooks 与容器组件**不许被复制进模块包**
136
+ (各打一份的症状不是报错,而是"agent 改完面板不自己刷新")。它们只能从 `kit` 拿。
137
+
138
+ ## 6 四条会被门禁拦住的写法
139
+
140
+ | 别这么写 | 为什么 |
141
+ |---|---|
142
+ | 从 DOM + CSS 手搓一个 dsh 已经有的东西(按钮、弹窗、输入框、图标) | 重画就是第二份材质与交互(悬停、禁用、焦点环、色板),迟早跟 dsh 漂。**先从 `kit` 拿**;kit 里没有再自己写,并在 spec 里记理由 |
143
+ | `import … from '@deepseek-ai/dsh-client-ui-primitives'` | 模块**不直接** import dsh 的包;dsh 原语由外壳经 `kit` 交给你。直接引会被判红(那是第二份实例) |
144
+ | `import … from '@/shared/…'`、`../../workbench/src/…` | 装你的人手上没有工作台的源码。`@/` 只许指**你自己**的 `src/` |
145
+ | 在公开包里写内网地址 / 内网域名 / 企业内部名 | 门禁会扫出来并指到行 |
146
+
147
+ 另外两条不是"被拦",是纪律:**样式自带前缀 `wb-<unit>-`**(外壳的 `wb-*` 是全局类名,覆盖它等于两份规则打架);
148
+ **`src/**` 里零注释** —— 要说的写进 `README.md` 或 spec。
149
+
150
+ ## 7 装上,看一眼
151
+
152
+ 模块造好、门禁绿了,**不要自己往 profile 里装**:工作台的 profile 目录不在你的可写根里。
153
+ 把这件事交给用户点一下「装上看看」—— 那一跳在宿主侧完成(装 + 顺手在面板里打开它)。
154
+
155
+ 两个必须记住的后果:
156
+
157
+ - **那一跳一辈子只该走一次。** 包名一旦在 profile 里,再装一次只会得到"要重启"
158
+ (同名重装一定要求重启,而且那一支连重载都不做)。
159
+ - **装 ≠ 上板**:面板默认是空的,得在面板配置里把它打开 —— 这就是那一跳顺手做掉的第二件事。
160
+
161
+ ## 8 改一行,看到它变
162
+
163
+ 装完之后**不要再装一遍**。改源码 → `dsh-wb-module build` → 用户面板上那张卡自己变,
164
+ 不重启、不刷新。为了让它成立:
165
+
166
+ - **构建必须原地覆写产物**(现在是)。别为了"原子写"改成临时文件 + rename ——
167
+ 宿主的监听只认 `change`,不认 `rename`,改了会**静默**不热换。
168
+ - **改 `cordis.patch.yml`、改 `package.json` 里的 `dsh.client` / `exports["./client"]` 需要重启。**
169
+ 这些是"模块怎么被装上"的声明,不是模块的内容。
170
+ - 重启操作只有在极少数情况下才需要用户做。真需要时,说清**为什么**、**要重启哪个**。
171
+
172
+ ## 9 发布
173
+
174
+ **工坊里的模块一律 `private: true`,不发到任何源。** 模型写的代码在用户的机器上执行,
175
+ 这是他自己要的东西、他自己的机器;把 `private` 摘掉是另一个决定,不属于 DIY。
176
+
177
+ 真要给别人用:把 `private` 去掉,并**显式**写上发布目标
178
+ (`publishConfig.access: "public"`,或指向内网源的 `publishConfig.registry`)——
179
+ 两样都不写等于打算发到公共源,门禁会拦。
@@ -0,0 +1,81 @@
1
+ # 设计口径:先说清他要什么,再谈怎么做
2
+
3
+ 这一步与落点无关 —— 无论模块最后落进工作台外壳里,还是落进一个独立模块包,
4
+ **问法、产出、验收都是同一套**。工程域与 DIY 域共用这一份。
5
+
6
+ ## 0 先判域
7
+
8
+ 做不成的不是"难",是"**不该在这里做**"。判完要给他**一句人话 + 下一步**,不是丢给他一个分类。
9
+
10
+ | 需求长这样 | 判 | 下一步 |
11
+ |---|---|---|
12
+ | 一张只读卡;数据从 HTTP GET / 一条命令 / 本地文件来 | **DIY 域** | 直接造([BUILD.md](BUILD.md)) |
13
+ | 卡片要能自己配(钉一个环境、选一个仓库) | **DIY 域** | 配置清单([PANEL.md](PANEL.md) §5) |
14
+ | 要 agent 替他做一两件**只读**的事 | **DIY 域** | 能力清单([TOOLS.md](TOOLS.md)) |
15
+ | 要 agent 动真东西(提交、发布、改配置) | **DIY 域**,但必须写明点头那一步 | 预览 + 确认两拍([TOOLS.md](TOOLS.md) §5–§7) |
16
+ | 要接一个新平台的账号 / 凭据 / 新命令 | **工程域** | 停,说清为什么,回工作台仓库走工程流程 |
17
+ | 一条流:多步、要等很久、中途要人、失败要能接着来 | **工程域** | 同上 |
18
+ | 要新建服务 / 新进程 / 新依赖 | **工程域** | 同上 |
19
+ | 要改工作台外壳,或改一份**已经发布**的模块 | **工程域** | 同上 |
20
+
21
+ 判域产出的是**他能听懂的一句话**("这件事要接一个新平台的账号,得按工程流程走"),
22
+ 以及"接下来会发生什么、还要多久"。不是"你这个需求超纲了"。
23
+
24
+ ## 1 两条分工(贯穿全程)
25
+
26
+ | 谁 | 管什么 |
27
+ |---|---|
28
+ | **用户** | 他看得见、能决定的事:要看到什么、要能自己配什么、要 agent 替他做什么、哪些要他点头、话怎么说、取舍 |
29
+ | **agent** | 一切实现:怎么落、叫什么、放在哪、用什么、怎么验 —— **这些不问用户** |
30
+
31
+ 四条对话纪律:
32
+
33
+ - 每个问题都**带推荐答案**,他一个字就能过("就这样");他不确定就按推荐走,标出来,别卡住。
34
+ - 任何**必须他参与**的决定,都翻译成"**他会看到什么**"再问(不问"用 A 还是 B"这类内部选择)。
35
+ - 每走完一步,给他看一个**他看得懂的东西**:一张表、一张画面、或一个能点的界面。**不贴代码、不讲实现**。
36
+ - 开工先列一份待办(问 → 三张表 → 做 → 验收),边做边更新;这是一件要跑很多轮的事。
37
+
38
+ ## 2 三个问题,三张表
39
+
40
+ | 步 | 问他什么 | 产出 | 表的行 × 列 |
41
+ |---|---|---|---|
42
+ | 1 面板 | 他要看到什么?要能自己配什么? | 展示清单 + 配置清单 + 状态矩阵 + 图例 | 每种状态(含每种缺失)×「他看到什么 / 他能做什么 / agent 能接着做什么」 |
43
+ | 2 agent | 他要 agent 替他做什么?哪几件他必须先点头? | 能力清单 + 全场景 + 点头场景 | 每件事 × 每种情况:agent 得到什么 / 用户看到什么 |
44
+ | 3 联动 | 他凭什么相信面板是新的? | 联动场景 | 每种「谁在动」×「面板什么时候变 / 变成什么样 / 用户看到什么」 |
45
+
46
+ **三张表既是要问清的需求,也是最后的验收单** —— 表里每一格,他都要能在界面上见过或点过。
47
+ 表里不许有空格,也不许有「其他」那一行。
48
+
49
+ 三步各自要问什么、要定什么、完成判据是什么:[STEPS.md](STEPS.md);
50
+ 每一步的细节:[PANEL.md](PANEL.md) · [TOOLS.md](TOOLS.md) · [LINKAGE.md](LINKAGE.md)。
51
+
52
+ ## 3 跟他沟通,尽量用 ASCII 画出来
53
+
54
+ **不要用句子描述界面,画给他看。** 一句话可以指向十种界面;一张 ASCII 就是那一种 ——
55
+ 他一眼就能说"这个对"或"这不对"。
56
+
57
+ - **界面**:卡片、每一种状态、设置面、审批弹窗,全部画 ASCII,图里用界面上真实的字。
58
+ - **工具**:贴真实的调用(参数 JSON)与真实的返回正文(照抄,不转述)。
59
+ - **联动**:把"变之前 / 变之后"也画出来(两张并排),别写"会自动刷新"。
60
+ - 要比较几种做法,就把几张图并排,别用形容词比。
61
+ - 矩阵左列用**图号**引用:面板那些图在步骤 1,联动那两张(变之前 / 变之后)在步骤 3。
62
+ - **不需要截图**:沟通靠 ASCII;实现完了他自己随时能打开看真的。
63
+
64
+ **每张表都要配图例,而图例就是把屏幕画出来。** 每种状态给一张小画面
65
+ (ASCII 小样:图标、那句话、按钮各在哪;空态有没有按钮;阻断是整张卡换掉还是只在上面加一条;
66
+ 骨架是几行灰条;弹窗里哪一行是谁给的),表里用**图号**引用(A / B / C…)。
67
+ **给状态起个名字、配个图标,都不算图例** —— 用户看不懂 `⛔` 是什么,
68
+ 但一眼看得懂"整张卡被换成了这一屏"。
69
+ 画不成画面的(动作、agent 的下一步、时间性的"不变 / 自己变")才写词,并把意思说清。
70
+
71
+ 定稿前扫一遍全文,凡是**描述"看到 / 听到 / 发生"的句子**,都问一句:**这句能不能画成 ASCII?** 能就画。
72
+ 画不成的只有这几类,写词(并说清意思):纯动作("点一下去平台")、agent 的下一步、
73
+ 时间性的词("不变 / 自己变 / 立刻")、纯映射表(能力名 ↔ 用户会怎么说)。
74
+
75
+ ## 4 做完的判据(都看用户那一侧)
76
+
77
+ - 全程**用户只说过需求相关的话**:没有一次被问"用什么、放在哪、叫什么、怎么测"。
78
+ - 三张表没有空格;表里每一格,他都在界面上见过或点过。
79
+ - 点头那条流程他真走过一次(允许或拒绝都算)。
80
+ - 门禁是绿的 —— 那是 agent 自己的门槛,不用他关心。
81
+ - 下次他再说一句"改一下",不用从头问:设计留下的记录还在。
@@ -0,0 +1,113 @@
1
+ # 联动:agent 做完,用户看到什么
2
+
3
+ 步骤 3 的参考。写的是用户什么时候看到哪一格变,不写这条链怎么接。
4
+
5
+ ## 1 什么时候面板该动
6
+
7
+ | 谁在动 | 用户看到什么 |
8
+ |---|---|
9
+ | agent **读**了面板这一格的数据 | **那一格**变(按最窄的那个切片重读),值跟对话里那份一样 |
10
+ | agent 读的是面板上没有的东西(明细、日志、某一档子集) | **不动**(面板没有那一格) |
11
+ | agent 只预览、还没真的做 | **不动**(预览不改任何东西) |
12
+ | agent 真的做了 | 相关的那一格**变成新值** |
13
+ | 他自己按的动作 | 那一格**立刻变**,并在原地看到"正在做 / 做完了" |
14
+ | 他自己改了偏好 | 保存后立刻按新的显示 |
15
+ | 别的窗口 / 另一个会话也在改 | 他这里也会变(不需要他刷新) |
16
+
17
+ ⚠️ **第一条是后加的口径(2026-10-01)**:以前写的是"agent 只是看一眼 → 不动"。改成"看一眼也让
18
+ **那一格**变",理由是用户口径:「agent 调用工具的时候面板数据也受益」—— agent 刚在对话里念了数字,
19
+ 面板得是同一份,否则他到底信哪个。三档说清:**读了这一格 → 变**;**读了面板没有的 → 不动**;
20
+ **只预览 → 不动**。机制与逐工具的表:工作台仓库里的 `docs/adr/0023-read-tools-claim-facts.md`
21
+ (工程域的实现落点见工作台仓库 `.agents/skills/module-design/IMPLEMENTATION.md` §5)。
22
+
23
+ **一次说清动的是哪一格**:整块板、某一张卡、还是某一行。写"刷新一下"不算说清 —— 那会让实现的人整卡重读。
24
+
25
+ **只动该动的那一格**:agent 在一个环境做了一次,不该让别的环境、别的列、整张卡一起重新读一遍(读一次很慢,他会看到整块退回"在读")。**颗粒度就是这句话在实现里的样子**:动的那一格要能落到"某条投影 + 最窄的那个参数"(`cmdb/builds?env=dev` 这种),写进联动表里。
26
+
27
+ ## 2 agent 正在干的时候
28
+
29
+ - 显示到**哪一步**("正在合并分支"这种他能读懂的一步,不是内部步骤名);
30
+ - 要**登录**就给他一条去登录的路,登录完这次继续,不用他重来;
31
+ - **失败**给他:卡在哪一步、一句能读的原因、原始输出(折叠起来),以及一条能照做的路;
32
+ - **不画进度条、不画流程控制台**:状态 + 链接 + 原始输出就够。
33
+
34
+ 一次做一半就停也不是问题:他回来说一句"接着做",能接着上一次继续,不用从头再来。
35
+
36
+ ## 3 他点"交给 agent"之后,agent 拿到什么
37
+
38
+ - **哪一类 + 哪个对象 + 多少**(他点的是哪一行,agent 就该拿到哪一类,不能四行点了都给它同一份全部);
39
+ - 加一句**该用哪个能力往下拿明细**(agent 打不开面板上那个 ↗,明细只能它自己再问一次);
40
+ - **明细本身不要塞进去**(一页几十行按会话付费,而 agent 拿到那句指引自己会去要);
41
+ - 他点的那一类要跟着走(点"圈复杂度"就只给圈复杂度,不能悄悄退回整份);
42
+ - 认不出的那一类要**当场说错**,不许默默退回整份(他要的是这一块,给全部比报错更糟)。
43
+
44
+ ## 4 收起模块意味着什么
45
+
46
+ 把一块面板收起来 = **这个项目里 agent 也做不了这件事**。
47
+
48
+ - 这一条要在**点头收起的时候**就说清("这个项目里它的能力也一起下线");
49
+ - 收起之后 agent 手上的能力清单会变,用户会在对话里看到一句"能力有更新";
50
+ - 别的项目不受影响。
51
+
52
+ ## 5 面板凭什么不旧
53
+
54
+ 三种情况都要有答案:
55
+
56
+ 1. **他切了项目** —— 看到的立刻是新项目的数据;
57
+ 2. **别的窗口 / 另一个会话也改了** —— 他这里会跟着变;
58
+ 3. **程序刚更新过(开发时最常见)** —— 不会因为"我比你还新"这种判断而永远收不到更新。
59
+
60
+ 允许有延迟(几秒),但延迟要说清;**不允许停在旧值上、只有手动点刷新才动**。
61
+
62
+ ## 6 联动场景表(步骤 3 的产出)
63
+
64
+ | 谁在动 | 面板什么时候变 | 哪一格变 | 用户看到什么 |
65
+ |---|---|---|---|
66
+ | agent 读了面板这一格的数据 | 读完自动 | 那一格(最窄那个切片) | 那一格出已有的加载态(约半秒)→ 与对话同值 |
67
+ | agent 读了面板上没有的那份 | 不变 | — | 对话里一段结果 |
68
+ | agent 预览 | 不变 | — | 对话里"将要做什么" |
69
+ | agent 真的做 | 做完之后自动 | 相关那一格 | 新值 |
70
+ | 他自己按动作 | 立刻 + 做完再更新 | 那一格 | 确认条 → "正在做" → 结果 |
71
+ | 他改偏好 | 保存后 | 整张卡按新偏好 | 回到看板、数据已换 |
72
+ | agent 改他的面板 | 自动 | 面板多了 / 少了 / 换了顺序 | 界面变了 + 对话里"能力有更新" |
73
+ | 装 / 卸能力 | 自动 | 相关的那几处 | 状态变 |
74
+ | 一件长事在跑 | 自动刷新进度 | 那张卡的进度那一行 | 到哪一步 / 要登录 / 失败原因 |
75
+ | 别的窗口也在改 | 自动 | 同"真的做" | 同"真的做" |
76
+ | 程序刚更新过 | 全部重读一次 | 整块 | 不会退回旧值 |
77
+ | 他自己点刷新 | 立刻 | 那一份 | 转圈,然后新值 |
78
+ | 他切项目 | 立刻 | 整块 | 新项目的数据 |
79
+ | 模块在这个项目被收起 | 自动 | 少一张卡 | 对话里"能力有更新" |
80
+
81
+ ## 7 读工具也要说清"它对应面板哪一格"
82
+
83
+ 用户会在对话里问到面板上就有的那格数字 —— 两处必须对得上(否则他得决定信哪个)。
84
+ 所以每把**读**工具在设计时就要回答三句:
85
+
86
+ - 它读的那份数据,是面板上**哪一格**?
87
+ - 那一格**最窄的切片**是什么(哪个环境的哪一列 / 哪一档筛选 / 就是整张卡)?
88
+ - 手里那份**是不是就是**那一格的值?
89
+
90
+ 第三种答案决定面板怎么动:
91
+
92
+ | 它读的是 | 面板 |
93
+ |---|---|
94
+ | 面板这一格范围内的数据 | 那一格重读;**手里那份就是那一格的值**时可以直接交出去(面板不再打一遍平台) |
95
+ | 面板上没有的东西(明细 / 日志 / 单条) | **不动**(面板没有那一格) |
96
+ | 面板某一格的**子集**(只读 TODO、limit 更小) | **不动**,而且**不许**把子集当那一格交出去(那是错值)—— 要对得上就让 agent 读全量那一档 |
97
+
98
+ 三句都写进能力清单(用户看的是"哪一格会跟着动")。
99
+
100
+ → 实现与逐工具那张表:本文 §1 · `docs/adr/0023-read-tools-claim-facts.md` §2.3
101
+ (工程域的实现落点见工作台仓库 `.agents/skills/module-design/IMPLEMENTATION.md` §5)。
102
+
103
+ ## 8 范例
104
+
105
+ - **读工具也让面板动**(哪一格动、颗粒度到哪、什么值能直接交、什么值只能点名):
106
+ `docs/adr/0023-read-tools-claim-facts.md` §2.3 是逐工具那张表;
107
+ `docs/adr/0021-agent-tools-hand-their-value-to-the-panel.md`(值交接),
108
+ `docs/adr/0022-refresh-has-a-visible-floor.md`(刷新要看得见)。
109
+ - 收起模块与"能力有更新"这件事的来龙去脉:`docs/adr/0020-tool-surface-narrows-at-assembly.md`。
110
+ - "改完面板为什么自己会新":`docs/adr/0012-facts-are-routes.md`。
111
+ - 用户视角的落地形态:`.scratch/tool-value-handoff/spec.md`(逐套件的联动验收)、
112
+ `.scratch/cmdb-module/spec.md` §6(联动;长事在跑的那一行在 §6.1、图 R–V 在 §6.2)、
113
+ `.scratch/coding-analysis/spec.md` §6(每一颗"交给 agent"各带哪一类 + 宿主注入的正文)。
@@ -0,0 +1,124 @@
1
+ # 面板:他看什么、他配什么、画成什么样
2
+
3
+ 步骤 1 的参考。全部用用户视角写 —— 这一层不出现实现。
4
+
5
+ ## 1 面板上该有的东西只有三类
6
+
7
+ | 类别 | 例子 | 谁来承接 |
8
+ |---|---|---|
9
+ | **导航** | 换项目、切面板、折叠 | 界面自己(只改他看到的视图) |
10
+ | **呈现** | 状态、数字、最近几条历史 | 只读呈现给他 |
11
+ | **交代** | "交给 agent"、预填一句指令、一个动作按钮 | 转给 agent |
12
+
13
+ **他不该在面板上做的事**:
14
+
15
+ - 平台上的写(评论、流转、发布)→ 交给 agent;
16
+ - 详情、评论、日志、翻页 → 给他一个"去平台看"的去处;
17
+ - 一次要动好几下、要等很久、中途要他登录或点头、失败还要接着来的 → **封成一个动作**,面板上只给他一个按钮 + 一个交代入口;中间那些来回留在对话里。
18
+
19
+ ## 2 判"封成一个动作"还是"交给 agent"
20
+
21
+ 问他四个问题,任一为"是"就封成一个动作:
22
+
23
+ 1. 一次要动平台上好几下?
24
+ 2. 要等很久(分钟级)?
25
+ 3. 中途需要他本人(登录、点头、撞上冲突)?
26
+ 4. 失败了要能接着来?
27
+
28
+ 都不是(单步、马上、能先看一遍)→ 交给 agent 就够了。
29
+
30
+ ## 3 什么算"配置"
31
+
32
+ 只有**他自己在这块面板上的偏好**算配置:看哪一份、显示哪些、钉哪个环境、绑定到哪个对象。
33
+
34
+ 碰平台的一律不是配置。
35
+
36
+ 配置的形态是一个面里**一次选完 + 保存前替他校验**;不做分页向导、不做好几步的流程(他看不到全局,出错也难查)。
37
+
38
+ ## 4 展示清单怎么写
39
+
40
+ 每条一行,就这三样:
41
+
42
+ | 写什么 | 判据 |
43
+ |---|---|
44
+ | 这一格是什么 | 一句他会说的话;出现平台内部术语就重写 |
45
+ | 一眼能得出什么结论 | 他要拿它做决定;说不出决定的格子就别放 |
46
+ | 点一下去哪 | 去平台看详情,或者交给 agent —— 两者至少有一个 |
47
+
48
+ - 一屏不要塞太多:他要的结论放最上面,历史、明细沉到下面。
49
+ - 同一列里几张卡要长得一样(标题、刷新、设置、"去平台"的位置一致)。
50
+ - 面板上的每个数字,agent 都要能通过自己的能力拿到;每个按钮都要有一句等价的话。做不到就别放。
51
+
52
+ ## 5 配置清单怎么写
53
+
54
+ 每项说清这七件事:
55
+
56
+ | 项 | 说清什么 |
57
+ |---|---|
58
+ | 管什么 | 一句话,用他的词 |
59
+ | 默认 | 不给偏好时他看到的会是什么 |
60
+ | 能选什么 | 候选从**现在真实存在**的里列;过期的候选不列 |
61
+ | 选错会怎样 | 拒绝并说清(还认的候选有哪些),不悄悄换成一个别的 |
62
+ | 记多久、记在哪 | 按项目分别记;换项目不串;换仓库/换对象时旧选择作废并让他重选 |
63
+ | 怎么生效 | 他按下保存才生效;保存成功直接回到看板并立刻按新的显示;保存失败留在原地告诉他为什么 |
64
+ | 怎么清 | 他自己显式清;收起或卸载这块面板**不删**他的配置 |
65
+
66
+ ## 6 界面的几个习惯
67
+
68
+ - **有不打扰的默认就别弹设置**:能用默认值读出他关心的那份,就直接显示,别先问他。真的分不清是哪一份,才把选择摆到他面前(不是先让他读一句话再点一次)。
69
+ - **能选的直接摆出来**:候选就两三条时,平铺一行一行给他点,别藏进下拉。
70
+ - **量词写在值旁边**("3 条"、"6 个文件"),别让他猜单位。
71
+ - **时间写成他看得懂的样子**,不要原始时间戳。
72
+ - **默认收起**:历史、明细折叠,露出最近一条;只有一条时不给折叠三角(没有可展开的东西)。
73
+ - **屏幕上的字越少越好**:他能看懂的(红点绿点、数字)就不用再写一段规则解释;删掉的东西不要留一行说明。
74
+ - **默认状态点**只有四种意思:在做 / 做完了 / 出错 / 没在跑。
75
+
76
+ ## 7 状态矩阵怎么写
77
+
78
+ 把每种状态都走一遍,一个不落:
79
+
80
+ 没有绑定 / 还没接入 / **分不清是哪一份** / 正在读 / 有数据 / 正在做 / 卡住了(要登录、没权限、超时、能力没装好)/ 读到了但是空 / 数字是上一轮的。
81
+
82
+ 每一格写三样:
83
+
84
+ | 列 | 写法 |
85
+ |---|---|
86
+ | 他看到什么 | 屏幕上是什么(哪句话、哪个画面、哪颗点) |
87
+ | 他能做什么 | 一个动作(去哪补、点哪个、重试) |
88
+ | agent 能接着做什么 | 一句 agent 能照做的话 |
89
+
90
+ 规矩:
91
+
92
+ - 每句都要**能照做**:缺什么、去哪补、谁补。
93
+ - **平台没给的那一格就不占位**(不留孤零零的字段名、不留空的一行)。
94
+ - **读不到就说读不到**,并说清为什么(例:手上这份能力太旧);**绝不显示 0**。
95
+ - 有确定答案就不打扰他(能按默认读出来就别弹设置面)。
96
+ - 真机上造不出来的状态也要写 —— 造出来是评审夹具的事。
97
+
98
+ ### 7.1 图例就是把屏幕画成 ASCII
99
+
100
+ **跟用户沟通主要就靠这些图。** 矩阵左列写**图号**(A / B / C…),图放在矩阵前面,一张 ASCII = 那一种状态在屏幕上是什么样。至少要有这几屏:
101
+
102
+ - **有数据**(正常那张卡)
103
+ - **空态**(图标 + 一句话,居中,**没有按钮**)
104
+ - **阻断 / 卡住**(**整张卡换成这一屏**:图标 + 一句话 + 按钮)
105
+ - **提示**(卡片照常画数据,只在最上面多一条细提示行)
106
+ - **骨架**(几行灰条:正在读、还没有数据)
107
+ - **在转**(列表照常在,只有卡片头那颗刷新在转)
108
+ - **一行里那些记号挨着的样子**(状态点 + 编号 + 元信息 + 行尾两颗)
109
+ - **状态点的几档**(只有颜色不同;**撞色的那两档要在图下点明靠什么分开**)
110
+ - **可点的筛选**(几颗、选中的那颗什么样)
111
+ - **只在某种模式出现的东西**(例:夹具条)
112
+ - **审批弹窗**(哪一行是套件给的、哪一行是系统给的、按钮在哪)
113
+
114
+ 三条写法:
115
+
116
+ - **用真字**:图里的文案用界面上真实那句(例「没有指派给你的缺陷。」),别写"一句空态文案"。
117
+ - **别拿图标代替画面**:给状态起个名字、配个 `⛔` 这类符号,用户还是分不清;一眼能认的是**形状**(整卡换掉 vs 多一行)与**位置**(行首的点、行尾的两颗)。
118
+ - **画不成的才写词**:动作、agent 的下一步、时间性的"不变 / 自己变",写成词并说清意思。
119
+ - **真机上造不出来的状态**:沟通时照样画(画的是设计意图);实现时用评审夹具把它造出来给人切。
120
+
121
+ ## 8 范例(看真实产出的形状)
122
+
123
+ - 卡片四态 + 最小确认条 + 设置面:`.scratch/cmdb-module/spec.md` §3.0 / §3.1(图 A–O)。
124
+ - 卡片即全部 + 设置面(平铺单选、显式保存、保存成功回看板)+ 缺态矩阵:`.scratch/coding-analysis/spec.md` §3.1(图 A–L)/ §4(设置面)/ §3.3(缺态矩阵)。
@@ -0,0 +1,31 @@
1
+ # 门禁红了怎么办:十条判据的人话对照表
2
+
3
+ 跑 `dsh-wb-module check`(或单跑一条 `--only <名字>`)。
4
+ **每一条红的都在讲一件真出过事的事**,不是形式主义。红了就照下面修,别抛给用户。
5
+
6
+ | 判据 | 它防的事故 | 红了的典型说法 | 怎么修 |
7
+ |---|---|---|---|
8
+ | `contracts` | dsh 组合阶段**不校验**你 `package.json` 里的声明 —— 错的 `external` 要到浏览器 `require` 那一刻才炸 | `单元 id "x" 与 … 撞了` / `faces.client 指的文件不存在` / `没有对 @dsh-workbench/core 的 peer 依赖` / `没有 typecheck 脚本` / `它不是 private,却既没声明 publishConfig.access=public…` | 按报的那一条改 `package.json`;`unit` 换个不撞的名字;四个脚本补全;私有不发就保持 `private: true` |
9
+ | `bundles` | 两个运行时互串:浏览器产物里带 node 内置、宿主产物里带 DOM / react | `浏览器产物里出现了 node 内置 …` / `宿主产物里出现了 react` | 宿主面别 import 浏览器侧的东西;浏览器面别 import `node:*`。两侧只通过服务名相接 |
10
+ | `source` | 非种子词的 `@deepseek-ai/*`、宿主侧碰 dsh 的浏览器包、`@/` 指到工作台源码 | `…在宿主侧可达` / `不是平台种子词` | 宿主侧只用 Node 侧的东西;dsh 原语一律走 `kit`;`@/` 只指自己的 `src/` |
11
+ | `primitives` | 声明了 `external` 却没有产物物证 ⇒ dsh 原语被**内联**成第二份实例 | `…用了却没声明 external` / `宿主产物里出现了` | 在 `dsh.client.external` 里显式声明它;确认产物里是 `require(...)` 而不是被打包进来 |
12
+ | `kit` | 用了 peer 下限那份外壳**还没有**的 kit 成员 ⇒ 卡片首帧 `Element type is invalid`,整块面板被 React 卸掉 | `kit.components.X 在这份外壳里没有` / `peer 下限比 devDependency 还旧` | 只用 peer 范围里真有的成员;把 `peerDependencies` 的下限提到你实际用的那档 |
13
+ | `cards` | 卡片拿**空数据**就抛 ⇒ 用户看到"卡片一直不出来 / 点一下白屏" | `空数据下卡片抛了` / `卡片只画出一个节点` / `卡片没套外壳的卡壳` | 让卡片在没数据时也画得出来(已有的加载 / 空 / 阻断态);外面套 `kit.components.Card` |
14
+ | `standalone` | "只有工作台源码在场才编得过" ⇒ 发布出去就散架 | `build 在外壳源码不在场时失败` / `typecheck 在外壳源码不在场时失败` | 别引用工作台源码;`tsconfig` 里 `paths` 的值必须是**相对路径**(`"./src/*"`,不是 `"src/*"`) |
15
+ | `markers` | 内网域名 / 企业内部名泄进要公开发布的包 | `命中内网 npm 源` / `命中内网域名` | 那些值不该写进包里;要配就落到模块自己的配置面,或走用户自己声明的来源 |
16
+ | `alias` | `@/` 伸进工作台源码 ⇒ 装的人手上没有那份源码 | `…指向外壳源码` | `@/` 只许指自己的 `src/`;跨包的东西走包名 |
17
+ | `style` | 模块定义 `.wb-*` 去覆盖外壳的类(全局类名,两份规则按注入顺序打架) | `定义了外壳前缀 wb-* 的 CSS 规则` / `借用了契约里没有的类` | 自己的类名前缀 `wb-<unit>-`;外壳给的类只能用契约里列出的那些 |
18
+
19
+ ## 什么时候该怀疑门禁本身
20
+
21
+ 判据自己也有正例与负例,正常不会误报。但有一种红是**模板/工具坏了**而不是你写错了:
22
+
23
+ - 骨架是刚 `init` 出来的、**一行都没改**,却红了 —— 那是模板的问题,不是你的。
24
+ 工作台仓库里有一条判据盯着这件事(生成一个骨架跑全套,红就是门禁坏了)。
25
+
26
+ ## 三条"不是判据、但更常踩"的
27
+
28
+ 1. **装上去没反应** —— 面板默认是空的,装了还要在面板配置里打开它(那一跳会顺手做掉)。
29
+ 2. **改完没热换** —— 你是不是把构建改成了"写临时文件再改名"?宿主的监听只认 `change`,不认 `rename`。
30
+ 另外:改 `cordis.patch.yml` 或 `package.json` 里的 `dsh.client` / `exports["./client"]` 本来就要重启。
31
+ 3. **"再装一遍"** —— 同名重装必然要求重启,而且不会重载。日常迭代只重建,不重装。
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: workbench-module-diy
3
+ description: 在工作台里凭需求造一个模块(面板上的一块内容 + 它背后的能力)。用在「我想在面板上看到 X」「加一块面板 / 一张卡 / 一个模块」「让 agent 替我读或写某个东西」「agent 动完面板哪一格跟着变」这类话上 —— 用户只说需求,实现由 agent 自己做、自己过门禁,最后给他一个能点开验收的东西。
4
+ ---
5
+
6
+ # 造一个工作台模块:他说需求,你做完给他看
7
+
8
+ 交付物是**面板上能点开的一块内容**,不是一份要他读的技术方案。
9
+
10
+ ## 六步
11
+
12
+ 1. **判域** —— 先读 [DESIGN.md](DESIGN.md) §0。不是所有需求都该在这里做;判完给他**一句人话 + 下一步**。
13
+ 2. **问三张表** —— 见 [STEPS.md](STEPS.md)。只问他看得见、能决定的事;**实现一律不问**。
14
+ 这一步的产出是 ASCII 画面 + 三张表,不是文字。
15
+ 3. **从模板改** —— [BUILD.md](BUILD.md) §1–§2:在工坊里 `dsh-wb-module init` 一个骨架,
16
+ 只改数据源与渲染。**不从零写。**
17
+ 4. **自己过门禁** —— `dsh-wb-module build` + `check`;红了自己修([REVIEW.md](REVIEW.md) 有对照),
18
+ 不抛给他、也不贴报错给他看。
19
+ 5. **让他看一眼** —— 先对着 ASCII 图确认,再**装上看看**(装是宿主侧那一跳,它会顺手在面板里打开它)。
20
+ **这一跳一辈子只走一次**:之后只重建、不重装。
21
+ 6. **验收** —— 逐条对着三张表走:「打开哪 → 看什么 → 点什么 → 应该看到什么」。
22
+ 他说哪不对就回对话改,改完让他看**同一处**;不给他看 diff、日志、代码。
23
+
24
+ ## 每个问题都带推荐答案
25
+
26
+ 他一个字就能过("就这样")。他不确定就按推荐走并标出来,别卡住。
27
+ 必须他拍板的决定,翻译成"**你会看到什么**"再问,不问"用 A 还是 B"。
28
+
29
+ ## 知识索引
30
+
31
+ | 什么时候读 | 读哪份 |
32
+ |---|---|
33
+ | 判这个需求该不该在这里做 | [DESIGN.md](DESIGN.md) §0 |
34
+ | 两条分工、三张表、怎么用 ASCII 跟他说话 | [DESIGN.md](DESIGN.md) |
35
+ | 步骤 1 / 2 / 3 各自问什么、什么时候算做完 | [STEPS.md](STEPS.md) |
36
+ | 面板上该有什么、状态矩阵怎么写、界面习惯 | [PANEL.md](PANEL.md) |
37
+ | 能力怎么划、传什么拿回什么、哪几件要点头 | [TOOLS.md](TOOLS.md) |
38
+ | agent 动完面板哪一格变、他正在干的时候看哪儿 | [LINKAGE.md](LINKAGE.md) |
39
+ | 落点、文件形状、命令、装与热换的纪律 | [BUILD.md](BUILD.md) |
40
+ | 门禁红了、装上去没反应、改完没热换 | [REVIEW.md](REVIEW.md) |
41
+
42
+ ## 找不到一件事的时候,按这个顺序找
43
+
44
+ 1. **先跑门禁**:`dsh-wb-module check --only <那一条>` 的报错原文最准。
45
+ 2. **再看模板**:已经有一个同形状、能跑的最小实现,比任何描述都准。
46
+ 3. **最后看工作台仓库**:`packages/module-hello` 是最小样本;契约口径在 `docs/ARCHITECTURE.md` 与 `docs/adr/`;
47
+ 自己包里的 `node_modules/@dsh-workbench/core` 就是外壳本体(可以读它的 `dist/types/**`)。
48
+
49
+ ## 三条不许松的
50
+
51
+ - **dsh 已经有的,先从 `kit` 拿**(`@dsh-workbench/core/client`),不许从 DOM + CSS 重画;
52
+ 也不直接 import dsh 的包 —— 直接引会被门禁拦,而且那是第二份实例。
53
+ - **工坊里的模块一律 `private: true`,不发到任何源。**
54
+ - **源码零注释**:要说的写进模块自己的 `README.md`。