@dsh-workbench/core 0.3.1 → 0.3.3
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/client.js +177 -107
- package/dist/client/client.body.js +177 -107
- package/dist/client/index.js +177 -107
- package/dist/host/index.js +832 -1461
- package/dist/types/app/client.d.ts +15 -3
- package/dist/types/app/layouts/panel-hint.d.ts +3 -0
- package/dist/types/app/layouts/rightbar/market-hint.d.ts +16 -0
- package/dist/types/app/layouts/rightbar/market-rows.d.ts +2 -3
- package/dist/types/modules/panels/model/diy-browser-act.d.ts +2 -39
- package/dist/types/modules/panels/model/diy-browser-tools.d.ts +39 -0
- package/dist/types/modules/panels/model/diy-browser.d.ts +1 -14
- package/dist/types/modules/panels/model/diy-observers.d.ts +11 -0
- package/dist/types/modules/panels/model/diy-observers.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-page.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-scripts.d.ts +0 -63
- package/dist/types/modules/panels/model/diy-spec.d.ts +5 -0
- package/dist/types/modules/panels/model/diy-spec.test.d.ts +1 -0
- package/dist/types/modules/panels/unit.d.ts +13 -11
- package/dist/types/shared/api/host/diy-recon.d.ts +10 -0
- package/package.json +5 -5
- package/scripts/seed-workshop.sh +3 -1
- package/skills/workbench-module-diy/BUILD.md +37 -16
- package/skills/workbench-module-diy/DATASOURCE.md +11 -2
- package/skills/workbench-module-diy/DESIGN.md +15 -8
- package/skills/workbench-module-diy/KIT.md +2 -0
- package/skills/workbench-module-diy/PANEL.md +3 -2
- package/skills/workbench-module-diy/REVIEW.md +115 -48
- package/skills/workbench-module-diy/SKILL.md +33 -9
- package/skills/workbench-module-diy/STEPS.md +1 -1
- package/skills/workbench-module-diy/TOOLING.md +6 -3
- package/skills/workbench-module-diy/TOOLS.md +90 -204
- package/skills/workbench-module-diy/WIRING.md +7 -5
- package/skills/workbench-module-diy/WRITES.md +90 -0
- package/dist/types/modules/panels/model/diy-act.d.ts +0 -4
- package/dist/types/modules/panels/model/diy-look.d.ts +0 -15
- package/dist/types/modules/panels/model/diy-net.d.ts +0 -3
- /package/dist/types/modules/panels/model/{diy-act.test.d.ts → diy-browser-act.test.d.ts} +0 -0
- /package/dist/types/modules/panels/model/{diy-look.test.d.ts → diy-browser-tools-shot.test.d.ts} +0 -0
- /package/dist/types/modules/panels/model/{diy-net.test.d.ts → diy-browser-tools.test.d.ts} +0 -0
|
@@ -18,22 +18,24 @@ export declare const DIY_LIST_TOOL = "workbench_diy_list";
|
|
|
18
18
|
export declare const DIY_STATUS_TOOL = "workbench_diy_status";
|
|
19
19
|
export declare const DIY_PANEL_TOOL = "workbench_diy_panel";
|
|
20
20
|
export declare const DIY_RECON_TOOL = "workbench_diy_recon";
|
|
21
|
+
export declare const DIY_SPEC_TOOL = "workbench_diy_spec";
|
|
21
22
|
export declare const DIY_PROBE_TOOL = "workbench_diy_probe";
|
|
22
|
-
export declare const DIY_LOOK_TOOL = "workbench_diy_look";
|
|
23
23
|
export declare const DIY_INSTALL_TOOL = "workbench_diy_install";
|
|
24
24
|
export declare const DIY_SHOW_TOOL = "workbench_diy_show";
|
|
25
|
+
export declare const DIY_STATE_TOOL = "workbench_diy_state";
|
|
26
|
+
export declare const DIY_HTML_TOOL = "workbench_diy_html";
|
|
27
|
+
export declare const DIY_FIND_TOOL = "workbench_diy_find";
|
|
25
28
|
export declare const DIY_CLICK_TOOL = "workbench_diy_click";
|
|
29
|
+
export declare const DIY_HOVER_TOOL = "workbench_diy_hover";
|
|
26
30
|
export declare const DIY_TYPE_TOOL = "workbench_diy_type";
|
|
27
31
|
export declare const DIY_FILL_TOOL = "workbench_diy_fill";
|
|
28
32
|
export declare const DIY_KEYS_TOOL = "workbench_diy_keys";
|
|
29
|
-
export declare const DIY_HOVER_TOOL = "workbench_diy_hover";
|
|
30
|
-
export declare const DIY_SELECT_TOOL = "workbench_diy_select";
|
|
31
33
|
export declare const DIY_SCROLL_TOOL = "workbench_diy_scroll";
|
|
32
|
-
export declare const
|
|
33
|
-
export declare const
|
|
34
|
-
export declare const
|
|
35
|
-
export declare const
|
|
36
|
-
export declare const
|
|
34
|
+
export declare const DIY_WAIT_TOOL = "workbench_diy_wait";
|
|
35
|
+
export declare const DIY_EVAL_TOOL = "workbench_diy_eval";
|
|
36
|
+
export declare const DIY_SCREENSHOT_TOOL = "workbench_diy_screenshot";
|
|
37
|
+
export declare const DIY_NETWORK_TOOL = "workbench_diy_network";
|
|
38
|
+
export declare const DIY_DIALOG_TOOL = "workbench_diy_dialog";
|
|
37
39
|
export declare const DIY_LOGS_WATCH_TOOL = "workbench_diy_logs_watch";
|
|
38
40
|
export declare const DIY_LOGS_READ_TOOL = "workbench_diy_logs_read";
|
|
39
41
|
export declare const DIY_LOGS_STOP_TOOL = "workbench_diy_logs_stop";
|
|
@@ -41,10 +43,10 @@ export declare const DIY_DEBUG_START_TOOL = "workbench_diy_debug_start";
|
|
|
41
43
|
export declare const DIY_DEBUG_PAUSE_TOOL = "workbench_diy_debug_pause";
|
|
42
44
|
export declare const DIY_DEBUG_RESUME_TOOL = "workbench_diy_debug_resume";
|
|
43
45
|
export declare const DIY_DEBUG_STOP_TOOL = "workbench_diy_debug_stop";
|
|
44
|
-
export declare const ALL_TOOLS: readonly ["workbench_panels_list", "workbench_panels_show", "workbench_panels_hide", "workbench_panels_move", "workbench_panels_reset", "workbench_panels_follow", "workbench_modules_list", "workbench_modules_install", "workbench_modules_update", "workbench_modules_uninstall", "workbench_modules_install_bundle", "workbench_modules_update_dependency", "workbench_modules_update_plugin", "workbench_modules_uninstall_plugin", "workbench_modules_update_workbench", "workbench_diy_list", "workbench_diy_status", "workbench_diy_panel", "workbench_diy_recon", "
|
|
46
|
+
export declare const ALL_TOOLS: readonly ["workbench_panels_list", "workbench_panels_show", "workbench_panels_hide", "workbench_panels_move", "workbench_panels_reset", "workbench_panels_follow", "workbench_modules_list", "workbench_modules_install", "workbench_modules_update", "workbench_modules_uninstall", "workbench_modules_install_bundle", "workbench_modules_update_dependency", "workbench_modules_update_plugin", "workbench_modules_uninstall_plugin", "workbench_modules_update_workbench", "workbench_diy_list", "workbench_diy_status", "workbench_diy_panel", "workbench_diy_recon", "workbench_diy_spec", "workbench_diy_probe", "workbench_diy_install", "workbench_diy_show", "workbench_diy_state", "workbench_diy_html", "workbench_diy_find", "workbench_diy_click", "workbench_diy_hover", "workbench_diy_type", "workbench_diy_fill", "workbench_diy_keys", "workbench_diy_scroll", "workbench_diy_wait", "workbench_diy_eval", "workbench_diy_screenshot", "workbench_diy_network", "workbench_diy_dialog", "workbench_diy_logs_watch", "workbench_diy_logs_read", "workbench_diy_logs_stop", "workbench_diy_debug_start", "workbench_diy_debug_pause", "workbench_diy_debug_resume", "workbench_diy_debug_stop"];
|
|
45
47
|
export type PanelsToolName = (typeof ALL_TOOLS)[number];
|
|
46
|
-
export declare const READ_TOOLS: readonly ["workbench_panels_list", "workbench_modules_list", "workbench_diy_list", "workbench_diy_status", "workbench_diy_panel", "workbench_diy_recon", "
|
|
48
|
+
export declare const READ_TOOLS: readonly ["workbench_panels_list", "workbench_modules_list", "workbench_diy_list", "workbench_diy_status", "workbench_diy_panel", "workbench_diy_recon", "workbench_diy_spec", "workbench_diy_probe", "workbench_diy_state", "workbench_diy_html", "workbench_diy_find", "workbench_diy_click", "workbench_diy_hover", "workbench_diy_type", "workbench_diy_fill", "workbench_diy_keys", "workbench_diy_scroll", "workbench_diy_wait", "workbench_diy_eval", "workbench_diy_screenshot", "workbench_diy_network", "workbench_diy_dialog", "workbench_diy_logs_watch", "workbench_diy_logs_read", "workbench_diy_logs_stop"];
|
|
47
49
|
export declare const WRITE_TOOLS: readonly ["workbench_panels_show", "workbench_panels_hide", "workbench_panels_move", "workbench_panels_reset", "workbench_panels_follow", "workbench_modules_install", "workbench_modules_update", "workbench_modules_uninstall", "workbench_modules_install_bundle", "workbench_modules_update_dependency", "workbench_modules_update_plugin", "workbench_modules_uninstall_plugin", "workbench_modules_update_workbench", "workbench_diy_install", "workbench_diy_show", "workbench_diy_debug_start", "workbench_diy_debug_pause", "workbench_diy_debug_resume", "workbench_diy_debug_stop"];
|
|
48
|
-
export declare const DIY_TOOLS: readonly ["workbench_diy_list", "workbench_diy_status", "workbench_diy_panel", "workbench_diy_recon", "
|
|
50
|
+
export declare const DIY_TOOLS: readonly ["workbench_diy_list", "workbench_diy_status", "workbench_diy_panel", "workbench_diy_recon", "workbench_diy_spec", "workbench_diy_probe", "workbench_diy_install", "workbench_diy_show", "workbench_diy_state", "workbench_diy_html", "workbench_diy_find", "workbench_diy_click", "workbench_diy_hover", "workbench_diy_type", "workbench_diy_fill", "workbench_diy_keys", "workbench_diy_scroll", "workbench_diy_wait", "workbench_diy_eval", "workbench_diy_screenshot", "workbench_diy_network", "workbench_diy_dialog", "workbench_diy_logs_watch", "workbench_diy_logs_read", "workbench_diy_logs_stop"];
|
|
49
51
|
export declare const DIY_DEBUG_TOOLS: readonly ["workbench_diy_debug_start", "workbench_diy_debug_pause", "workbench_diy_debug_resume", "workbench_diy_debug_stop"];
|
|
50
52
|
export declare const panelsUnit: UnitManifest;
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export interface ReconArg {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly type: string;
|
|
4
|
+
readonly required: boolean;
|
|
5
|
+
readonly value: string;
|
|
6
|
+
readonly help: string;
|
|
7
|
+
}
|
|
1
8
|
export interface ReconCommand {
|
|
2
9
|
readonly command: string;
|
|
3
10
|
readonly site: string;
|
|
@@ -9,6 +16,8 @@ export interface ReconCommand {
|
|
|
9
16
|
readonly browser: boolean;
|
|
10
17
|
readonly example: string;
|
|
11
18
|
readonly columns: readonly string[];
|
|
19
|
+
readonly args: readonly ReconArg[];
|
|
20
|
+
readonly preview: boolean;
|
|
12
21
|
}
|
|
13
22
|
export interface ReconSite {
|
|
14
23
|
readonly site: string;
|
|
@@ -28,6 +37,7 @@ export interface ReconAnswer {
|
|
|
28
37
|
readonly next: string;
|
|
29
38
|
readonly text: string;
|
|
30
39
|
}
|
|
40
|
+
export declare function invocableOf(command: ReconCommand): string;
|
|
31
41
|
export declare function manifestCommandsOf(raw: string): readonly ReconCommand[];
|
|
32
42
|
export declare function pluginNamesOf(entries: readonly string[]): readonly string[];
|
|
33
43
|
export declare function pluginCommandsOf(files: readonly string[]): readonly string[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "个人工作台:Agent 工作台外壳(左栏 + 常驻对话 + 自研右侧数据面板)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"icon": "./icon.svg",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"title": "Workbench",
|
|
43
43
|
"description": "个人工作台"
|
|
44
44
|
},
|
|
45
|
-
"//scripts": "包内不跑 check:docs —— 那份文档管理块住在仓库根的 docs/ARCHITECTURE.md,由根 `pnpm run check` 用 --config 指到本包的 arch.config.mjs 校验。check 与 prepublishOnly 都走 -w 跑根那一整套:包内自己串一份会漏掉 lint(react-hooks 那几条规则只在这里跑)、check:docs
|
|
45
|
+
"//scripts": "包内不跑 check:docs —— 那份文档管理块住在仓库根的 docs/ARCHITECTURE.md,由根 `pnpm run check` 用 --config 指到本包的 arch.config.mjs 校验。check 与 prepublishOnly 都走 -w 跑根那一整套:包内自己串一份会漏掉 lint(react-hooks 那几条规则只在这里跑)、check:docs 与模块工具包的十三条判据 —— 2026-10-16 实测踩到(渲染期写 ref,包内全绿、根上一跑就红)。",
|
|
46
46
|
"//dsh.bundle.patch": "一份 preset 就是一个补丁层:它 insert 一行 @deepseek-ai/dsh-agent-preset,config.plugins 是整表插件清单。diy.patch.yml 是**生成的**(scripts/gen-diy-preset.mjs,从 dsh 出厂那份 standard.patch.yml 派生),selfcheck 有一条判据盯着它有没有过期。",
|
|
47
47
|
"dsh": {
|
|
48
48
|
"bundle": {
|
|
@@ -87,15 +87,15 @@
|
|
|
87
87
|
"access": "public"
|
|
88
88
|
},
|
|
89
89
|
"dependencies": {
|
|
90
|
-
"@dsh-workbench/shared": "0.3.
|
|
90
|
+
"@dsh-workbench/shared": "0.3.3"
|
|
91
91
|
},
|
|
92
92
|
"scripts": {
|
|
93
93
|
"build": "node scripts/build.mjs && node scripts/build-types.mjs",
|
|
94
94
|
"build:host": "node scripts/build.mjs host",
|
|
95
95
|
"build:client": "node scripts/build.mjs client",
|
|
96
|
-
"typecheck": "tsc --noEmit",
|
|
96
|
+
"typecheck": "tsc --noEmit --incremental --tsBuildInfoFile node_modules/.cache/tsconfig.tsbuildinfo",
|
|
97
97
|
"arch": "arch-guard",
|
|
98
|
-
"selfcheck": "node scripts/selfcheck.mjs",
|
|
98
|
+
"selfcheck": "node scripts/selfcheck/index.mjs",
|
|
99
99
|
"check:runtimes": "node scripts/check-runtimes.mjs",
|
|
100
100
|
"check:release-artifacts": "node scripts/check-release-artifacts.mjs",
|
|
101
101
|
"check": "pnpm -w run check",
|
package/scripts/seed-workshop.sh
CHANGED
|
@@ -77,7 +77,9 @@ SYNC_STATE="$DIY_DIR/.sync.json"
|
|
|
77
77
|
SYNC_LOCK="$DIY_DIR/.sync.lock"
|
|
78
78
|
SYNC_BACKOFF_SECONDS=3600
|
|
79
79
|
SYNC_LOCK_STALE_MINUTES=15
|
|
80
|
-
|
|
80
|
+
# 撞锁(目标是另一代)时等一轮的秒数。可覆盖:只有探针会调小它——串行那版靠这个 sleep 排队,
|
|
81
|
+
# 18 格跑 54 秒。生产默认为 5,行为不变。
|
|
82
|
+
SYNC_LOCK_WAIT_SECONDS="${SYNC_LOCK_WAIT_SECONDS:-5}"
|
|
81
83
|
|
|
82
84
|
# 基准版本:只有**显式给了基准**、且它有可读的 package.json 时才有值。空 = 这一轮不做比对。
|
|
83
85
|
shell_version() {
|
|
@@ -51,7 +51,7 @@ pnpm exec dsh-wb-module init <unit> --dir <工坊>/packages/module-<unit> --temp
|
|
|
51
51
|
`<unit>` 是小写短横线标识(如 `my-thing`),它同时是单元 id、包名后缀、卡片样式前缀。
|
|
52
52
|
**它必须全局唯一** —— 和工作台里其它模块、和外壳里已有的单元都不能撞;撞了装上去当场抛。
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
生成的骨架是**能直接跑的**(十三条判据 + 六个脚本开箱全绿;门禁还在,模板改坏了当场报)。
|
|
55
55
|
|
|
56
56
|
**两个模板,按需要挑**(加 `--template <名字>`):
|
|
57
57
|
|
|
@@ -62,25 +62,36 @@ pnpm exec dsh-wb-module init <unit> --dir <工坊>/packages/module-<unit> --temp
|
|
|
62
62
|
|
|
63
63
|
⚠️ `diy` 那份是**给别人照抄的**,所以它自己也必须开箱全绿 —— 门禁会**两个模板各 init 一遍、各跑一遍全套**。
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
骨架是**分层多文件**的(不是单文件):目录**按需要出现** —— 空壳可以只有 `components/`,
|
|
66
|
+
有状态逻辑才需要 `model/`(`diy` 两样都有,`minimal` 不硬造 `model/`)。骨架里的文件各有其职
|
|
67
|
+
(`minimal` 是空壳那几份,`diy` 多一份 `design.json`、`src/model/`、`src/components/OverviewCard.test.ts` 与 `types/styles.d.ts`,见下面那张 ⚠️):
|
|
66
68
|
|
|
67
69
|
| 文件 | 职责 |
|
|
68
70
|
|---|---|
|
|
69
|
-
| `package.json` | 自述清单:包名、`dsh.bundle.patch`、`dsh.client`、`dsh.workbench.unit`、`dsh.workbenchFaces{host,client}
|
|
71
|
+
| `package.json` | 自述清单:包名、`dsh.bundle.patch`、`dsh.client`、`dsh.workbench.unit`、`dsh.workbenchFaces{host,client}`(工具链字段,门禁必填)、六个脚本、peer 依赖 |
|
|
72
|
+
| `arch.config.mjs` | 模块自己的**架构方言**:**只有一行**工厂调用 `moduleArch()`(从 `@dsh-workbench/module-tools/arch` 来)。层序表 / 两个运行时 / 阈值只有那一份,随工具链同代 |
|
|
73
|
+
| `eslint.config.mjs` | 模块自己的**语法卫生方言**:**只有两行** —— `moduleLint({ rootDir: import.meta.dirname })`(从 `@dsh-workbench/module-tools/lint` 来;`rootDir` 把 tsconfig 根钉在这个模块上,多份配置并存时 typescript-eslint 才猜得出)。规则 / ignores 只有那一份,随工具链同代;要放行某条就从 `moduleLint({ rules, ignores })` 开口子,别改方言那些块(判据比整份 8 块,逐块点名),也别手写一份自己的(自己 import 方言再照抄整份则分不出来) |
|
|
70
74
|
| `cordis.patch.yml` | 把自己插进 profile 的那一行 |
|
|
71
|
-
| `src/
|
|
72
|
-
| `src/
|
|
75
|
+
| `src/model/**` | 纯逻辑:类型、换算、状态机(`diy` 是 `overview.ts` + 配对测试)。两个运行时都能读,不碰 react / kit / dsh / `node:*` |
|
|
76
|
+
| `src/components/**` | 浏览器侧的视图件,**样式与它同目录**(`X.module.css`;`diy` 是 `OverviewCard.tsx`)。整片算 client 面,宿主侧碰不得 |
|
|
77
|
+
| `src/host.ts` | 宿主面:`manifest` + `mount()`(路由 / 工具);类型从 `src/model/` 引,同一个概念不留两处真相 |
|
|
78
|
+
| `src/client.tsx` | 浏览器面:`manifest` + `card` —— **只剩面**(注册 + 把 props 交给 `components/` 那个组件) |
|
|
73
79
|
| `src/messages.ts` | zh / en 文案;键必须带 `<unit>.` 前缀,两种语言同构 |
|
|
74
|
-
| `src/plugin.test.ts` |
|
|
80
|
+
| `src/plugin.test.ts` | 单测:两侧都把自己的面注册进了外壳(`diy` 还多一条宿主路由) |
|
|
81
|
+
| `src/components/X.test.ts` | 组件自己的渲染断言(`diy` 把 `OverviewCard` 的五态各画一遍拆在这儿。**测试文件不吃方言的 S16「文件 200 行」**(2026-10-19 起内置豁免,后缀式与 `__tests__/` 两种命名都算),所以并回一个文件也不会红 —— 拆开只为读着顺) |
|
|
75
82
|
| `vitest.config.ts` | 测试配置(`@/modules/<unit>/` 的别名) |
|
|
76
83
|
| `scripts/smoke.mjs` | `selfcheck`:把 `client.js` 当浏览器加载器跑一遍,看卡片真进没进外壳 |
|
|
77
84
|
| `tsconfig.json` | `paths` 里 `@/modules/<unit>/*` → `./src/*`,值必须是**相对路径** |
|
|
78
85
|
| `types/globals.d.ts` | 经典 JSX 运行时的全局类型垫片 |
|
|
86
|
+
| `types/styles.d.ts` | CSS Modules 的类名表声明(`declare module '*.module.css'`)。`globals.d.ts` 顶层有 import、是模块,那里的 `declare module` 会变成模块增强而不是环境声明 —— 用了 `X.module.css` 就必须有这一份(`diy` 有) |
|
|
79
87
|
| `design.json` | 三张表 + 状态矩阵 + 数据源记录 + 验收清单(**唯一真相**;README 里那一段由它生成) |
|
|
80
88
|
| `README.md` | 这个模块自己的说明 |
|
|
81
89
|
|
|
82
|
-
改哪一个:**画什么改 `src/
|
|
83
|
-
|
|
90
|
+
改哪一个:**画什么改 `src/components/*.tsx` + 它同目录的 `*.module.css`,纯逻辑(换算 / 状态机 / 类型)
|
|
91
|
+
改 `src/model/**`,文案改 `src/messages.ts`;`src/client.tsx` 只留注册那一层**,需要能力再动 `src/host.ts`。
|
|
92
|
+
模板已经把组件套好了外壳的卡壳(`kit.components.Card`)、三态与测试;你只改里面。
|
|
93
|
+
**宿主侧不许 import `src/components/**`** —— 那是跨运行时,`arch` 判据当场红(层号拦不住它:
|
|
94
|
+
components 第 2 层、host 第 10 层)。
|
|
84
95
|
|
|
85
96
|
⚠️ **三张表落在 `design.json`**(`diy` 骨架自带一份填好的,`minimal` 里没有 —— 用它就照
|
|
86
97
|
[DESIGN.md](DESIGN.md) §5 建一个):把它改成你刚问出来的那份,**再改代码**。
|
|
@@ -90,9 +101,10 @@ README 里 `<!-- dsh-wb:design:start/end -->` 之间那一段**由它生成**(
|
|
|
90
101
|
|
|
91
102
|
```bash
|
|
92
103
|
dsh-wb-module build # 出 client.js 与 dist/host.js
|
|
93
|
-
dsh-wb-module check #
|
|
94
|
-
dsh-wb-module check --only cards # 单跑一条(contracts / bundles / source /
|
|
95
|
-
# kit / cards / standalone / markers /
|
|
104
|
+
dsh-wb-module check # 十三条判据,全跑
|
|
105
|
+
dsh-wb-module check --only cards # 单跑一条(contracts / protocol / bundles / source / arch / lint /
|
|
106
|
+
# primitives / kit / cards / standalone / markers / style / alias)
|
|
107
|
+
dsh-wb-module lint [--fix] # 跑模块自己那份 eslint 配置;--fix 写回
|
|
96
108
|
```
|
|
97
109
|
|
|
98
110
|
三张表的填/查/生成/打勾见 [DESIGN.md](DESIGN.md) §5。
|
|
@@ -100,25 +112,34 @@ dsh-wb-module check --only cards # 单跑一条(contracts / bundles / sourc
|
|
|
100
112
|
⚠️ 写成 `pnpm exec dsh-wb-module …`(工坊里 PATH 上也有同名入口,由启动器挂);
|
|
101
113
|
模块目录里更省事的是那几个脚本(`pnpm build` / `pnpm selfcheck`)。
|
|
102
114
|
|
|
103
|
-
`package.json`
|
|
115
|
+
`package.json` 里那前四个脚本都要留着(契约判据会查);`check` 是第五条(`arch` 方言的 M09 要这个脚本名);`lint` 是第六条(`lint` 判据要它):
|
|
104
116
|
|
|
105
117
|
```bash
|
|
106
118
|
pnpm build # 构建
|
|
107
119
|
pnpm typecheck # tsc --noEmit
|
|
108
120
|
pnpm test # vitest
|
|
109
121
|
pnpm selfcheck # 构建 + 把 client.js 当加载器跑一遍(`diy` 骨架里还多一条 `design check`)
|
|
122
|
+
pnpm check # dsh-wb-module check:十三条判据
|
|
123
|
+
pnpm lint # dsh-wb-module lint:语法卫生(`pnpm lint -- --fix` 写回)
|
|
110
124
|
```
|
|
111
125
|
|
|
112
|
-
**自己的门禁自己过,红了别抛给用户。**
|
|
126
|
+
**自己的门禁自己过,红了别抛给用户。** 十三条判据在 [REVIEW.md](REVIEW.md) 里逐条翻了人话。
|
|
113
127
|
⚠️ **`check` 不替你跑 `pnpm test`**(它只看形状与产物):状态级的渲染、换算、边界都在你自己的
|
|
114
128
|
`test` 里 —— 骨架开箱就带一份,改了数据源与渲染就顺手把它跑一遍,别让红线留在包里。
|
|
115
129
|
⚠️ **工坊里恒有两条是「不适用」,那不是「通过」**:`独立构建` 要外壳源码在场才跑得起来,
|
|
116
130
|
`公开包无内部标识` 只扫要发出去的包(DIY 模块一律 `private`)。汇总行下面会把
|
|
117
|
-
这次没查的那几条逐条列出来;所以「8 通过 · 2
|
|
131
|
+
这次没查的那几条逐条列出来;所以「8 通过 · 2 不适用」不等于十三条全绿 —— 别把这句话念给用户听。
|
|
118
132
|
|
|
119
|
-
装上板那一步不是命令,是一套 agent
|
|
133
|
+
装上板那一步不是命令,是一套 agent 工具:`workbench_diy_list` 看工坊里有什么、
|
|
120
134
|
`workbench_diy_status`(`unit=<单元 id>`)看它现在走到哪一步、`workbench_diy_install` 装上并上板、
|
|
121
|
-
`workbench_diy_show` 只上板 —— 见 §7
|
|
135
|
+
`workbench_diy_show` 只上板 —— 见 §7。**上完板自己逐条走一遍(这就是验收那一拍)**:浏览器那 14 把
|
|
136
|
+
(`state` / `get html` / `find` / `click` / `hover` / `type` / `fill` / `keys` / `scroll` / `wait` / `eval` /
|
|
137
|
+
`screenshot` / `network` / `dialog`,argv 与结果原样转发,全表见 [TOOLS.md](TOOLS.md) §13)打的是
|
|
138
|
+
**用户当前那一页**上工坊那块板里的那张卡 —— 用户那一页不会被换掉,但他得把工作台切到前台。
|
|
139
|
+
**看屏 / 动屏那一族(浏览器那 14 把 / `logs_*`)要先 `workbench_diy_debug_start`**(`unit=…`;
|
|
140
|
+
会请他点头一次)—— 没有这场约定它们一律当场拒;不看屏了先 `_pause`、收工 `_stop`。
|
|
141
|
+
卡片有没有报错先看 `workbench_diy_panel` 的「崩过 / 读到了」;真要这一页的控制台,用
|
|
142
|
+
`workbench_diy_logs_watch` → 做那件事 → `workbench_diy_logs_read`(钩子有上限,见 [REVIEW.md](REVIEW.md) 四步取证的 4)。
|
|
122
143
|
|
|
123
144
|
## 4 两个面的形状
|
|
124
145
|
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
- **一张卡背后可以有多条读**(甚至跨站点):design.json 用 `reads: [ … ]` 一条一行写全,
|
|
32
32
|
每条各自的"参数 / 凭据 / 没登录长什么样 / 超时长什么样"都别省 —— 卡片上要按条给失败态。
|
|
33
33
|
② **再看这台机器另装的插件**:recon 会一并列出来(另一面是 `opencli plugin list`)。用
|
|
34
|
-
`opencli <插件> <命令>`,参数用 `--help`
|
|
34
|
+
`opencli <插件> <命令>`,参数用 `--help` 看 —— 也可以 `workbench_diy_spec`(`command='<site> <name>'`)
|
|
35
|
+
一次问全:它把 `--help -f json` **原样**回(插件与内置都能问)。
|
|
35
36
|
③ **官方有、这台机器没有** → 才考虑装:社区插件 `opencli plugin install github:user/repo`、
|
|
36
37
|
外部 CLI `opencli external install <名字>`,或在模块清单里声明 `manifest.env.plugins`
|
|
37
38
|
(`installSource`:npm 包 / git / 本地)。**装是这台机器上的持久变更、而且会跑别人的代码 → 要用户
|
|
@@ -64,6 +65,11 @@
|
|
|
64
65
|
固化读的完整形状是**两拍**:不在站点上 → `open` 那个页 → 等一会儿(约 1 秒)再 eval 一遍。
|
|
65
66
|
每一拍都由 shared 的 `backgroundLine` 拼(前缀 `env OPENCLI_WINDOW=background`)—— 别绕开它直接拼
|
|
66
67
|
`opencli browser …`:漏一拍就是把用户的 Chrome 顶到面前一次。
|
|
68
|
+
- **读自己一条会话,屏是另一条 —— 这两条不许同名**。`browserRead` / `browserFind` 的默认会话是
|
|
69
|
+
`BROWSER_READ_SESSION`(`workbench-read`);`BROWSER_SCREEN_SESSION`(`workbench`)是
|
|
70
|
+
**工坊看/动族给"用户当前那一页"上租约**用的那条。两条同名的后果是"一读就把用户那一页导航走"
|
|
71
|
+
(`open` 那一拍落在租约那个标签上)—— 2026-10-19「套餐余额」卡片一挂载就跳 `/wallet` 就是这个。
|
|
72
|
+
想给自己一条专线(不跟别的模块抢同一个 tab)就传自己的名字:`session: "<unit>"`。
|
|
67
73
|
- **页面里没有接口时(DOM 读)不要用 `eval` 去 `document.querySelectorAll`**:产物边界判据会拦
|
|
68
74
|
(宿主产物里不许出现 `document.` / `window.` 字样),而且那样解析只能窝在字符串里、测不了。
|
|
69
75
|
探的时候 `workbench_diy_probe`(`url=…, css=<选择器>`)就是这个口子;
|
|
@@ -80,10 +86,13 @@
|
|
|
80
86
|
4. **固化进模块** —— 先把它照抄进 `design.json`(只有一条读写 `datasource{}`;一条卡有好几条读写 `reads[]`,一条一行;字段一样:哪条读 / 参数 / 凭据 /
|
|
81
87
|
没登录长什么样 / 超时长什么样 / 当初那次调用的原文),再写进模块的**宿主半侧**:用 `@dsh-workbench/shared/host` 导出的
|
|
82
88
|
`browserRead({ shell, script, url, loginUrl })`(**接口读**:探明的那一段脚本原样搬进去就行 ——
|
|
83
|
-
两拍、重试、平台码映射、`message` / `hint` / `detail` / `helpUrl`
|
|
89
|
+
两拍、重试、平台码映射、`message` / `hint` / `detail` / `helpUrl` 四个字段都在它里面;
|
|
90
|
+
会话不传就落在读那条上,想给自己一条专线再传 `session: "<unit>"`)或
|
|
84
91
|
`browserFind({ shell, css, url, session })`(**DOM 读**:它替你问"现在停在哪一页"(`browser get url`),
|
|
85
92
|
不在就 `open` 再读一遍,失败时把 opencli 自己说的原因带出来),或者只读路由里的一个 HTTP GET。之后卡片只依赖**这一条**;渲染怎么改都不动它。
|
|
86
93
|
|
|
94
|
+
这一步只管**读**。要 agent 真去**改**东西(提交 / 发布 / 建单 / 改配置),那半边走 [WRITES.md](WRITES.md)。
|
|
95
|
+
|
|
87
96
|
`opencli` 是**侦察兵,不是日常通道**:探明之后,日常读必须是一条固定的、便宜的、能解释的调用。
|
|
88
97
|
|
|
89
98
|
## 固化时一并写掉三态
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# 设计口径:先说清他要什么,再谈怎么做
|
|
2
2
|
|
|
3
3
|
这一步与落点无关 —— 无论模块最后落进工作台外壳里,还是落进一个独立模块包,
|
|
4
|
-
|
|
4
|
+
**问法、产出是同一套;验收归谁按域分**(见 §4)。工程域与 DIY 域共用这一份。
|
|
5
5
|
|
|
6
6
|
## 0 先判域
|
|
7
7
|
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
| 谁 | 管什么 |
|
|
28
28
|
|---|---|
|
|
29
29
|
| **用户** | 他看得见、能决定的事:要看到什么、要能自己配什么、要 agent 替他做什么、哪些要他点头、话怎么说、取舍 |
|
|
30
|
-
| **agent** |
|
|
30
|
+
| **agent** | 一切实现与验收(验收按域分,见 §4):怎么落、叫什么、放在哪、用什么、怎么在真卡片上逐条验 —— **这些不问用户** |
|
|
31
31
|
|
|
32
32
|
四条对话纪律:
|
|
33
33
|
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
| 2 agent | 他要 agent 替他做什么?哪几件他必须先点头? | 能力清单 + 全场景 + 点头场景 | 每件事 × 每种情况:agent 得到什么 / 用户看到什么 |
|
|
45
45
|
| 3 联动 | 他凭什么相信面板是新的? | 联动场景 | 每种「谁在动」×「面板什么时候变 / 变成什么样 / 用户看到什么」 |
|
|
46
46
|
|
|
47
|
-
**三张表既是要问清的需求,也是最后的验收单** ——
|
|
47
|
+
**三张表既是要问清的需求,也是最后的验收单** —— 表里每一格,都要能在真卡片上见过或点过
|
|
48
|
+
(DIY 域由 agent 自己走,工程域请他看一次,见 §4)。
|
|
48
49
|
表里不许有空格,也不许有「其他」那一行。
|
|
49
50
|
|
|
50
51
|
它的落点是模块里的 `design.json`(命令与形状见本文 §5)—— **只留在对话里等于没定**。
|
|
@@ -74,12 +75,17 @@
|
|
|
74
75
|
画不成的只有这几类,写词(并说清意思):纯动作("点一下去平台")、agent 的下一步、
|
|
75
76
|
时间性的词("不变 / 自己变 / 立刻")、纯映射表(能力名 ↔ 用户会怎么说)。
|
|
76
77
|
|
|
77
|
-
## 4
|
|
78
|
+
## 4 做完的判据
|
|
78
79
|
|
|
79
80
|
- 全程**用户只说过需求相关的话**:没有一次被问"用什么、放在哪、叫什么、怎么测"。
|
|
80
|
-
- 三张表没有空格(`dsh-wb-module design check`
|
|
81
|
+
- 三张表没有空格(`dsh-wb-module design check` 绿);表里每一格,都在真卡片上见过或点过,
|
|
81
82
|
验收清单逐条打上了勾(`design accept`)。
|
|
82
|
-
-
|
|
83
|
+
- **验收归谁按域分**:**DIY 域(工坊会话)的验收是 agent 自己走的** —— 调试族
|
|
84
|
+
(`workbench_diy_debug_start` + 浏览器那 14 把枚举工具 + `logs_*`,全表见 [TOOLS.md](TOOLS.md) §13)
|
|
85
|
+
就是取证面;用户不必在场、也不必逐条点头,走完把"装好了、面板上哪一格"告诉他即可。
|
|
86
|
+
**工程域没有那一族**,仍按「打开 :3081 → 右栏第 N 张卡 → 点 X → 应该看到 Y」请他看一次。
|
|
87
|
+
- 要用户点头的**写能力(审批)那一拍仍是他的**:他真用一次时点,agent 代不了 ——
|
|
88
|
+
清单里写清"这条要你真用一次",别替他打勾。
|
|
83
89
|
- 门禁是绿的 —— 那是 agent 自己的门槛,不用他关心。
|
|
84
90
|
- 下次他再说一句"改一下",不用从头问:设计留下的记录还在。
|
|
85
91
|
|
|
@@ -91,13 +97,14 @@
|
|
|
91
97
|
```bash
|
|
92
98
|
pnpm exec dsh-wb-module design check # 空着的格?README 落后了?都在这儿报(红 = 还有没定的事)
|
|
93
99
|
pnpm exec dsh-wb-module design render # 改完 design.json,把 README 那一段重新生成
|
|
94
|
-
pnpm exec dsh-wb-module design accept #
|
|
100
|
+
pnpm exec dsh-wb-module design accept # agent 自己在真卡片上逐条走通之后,把验收清单打勾
|
|
95
101
|
```
|
|
96
102
|
|
|
97
103
|
形状:`unit` / `name` / `oneLine` + `panel[]`(这一格 / 结论 / 去哪)+ `states[]`(**五态一个都不能少**,
|
|
98
104
|
key 固定 `idle|loading|ready|empty|unreadable`)+ `config[]` + `capabilities[]` + `linkage[]` +
|
|
99
|
-
`datasource{}`(只有一条读)或 `reads[]`(多条读,一条一行;字段一样)+ `acceptance[]`。
|
|
105
|
+
`datasource{}`(只有一条读)或 `reads[]`(多条读,一条一行;字段一样)+ `writes[]`(**写通道**:一个动作可以打几条写,一条一行;只有一条写也可写 `write{}`;**没有写通道的模块不写就行**)+ `acceptance[]`。
|
|
100
106
|
`config` / `capabilities` **允许空数组**("这份模块故意没有能力"是一种决定);其余空着就是没定的事。
|
|
107
|
+
(写通道的字段与取证口径见 [WRITES.md](WRITES.md);README 里那一节叫「写通道探明记录」,位置在「数据源探明记录」与「验收」之间。)
|
|
101
108
|
`link` 这类**可选格**留空渲染成 `—`,不算空格。
|
|
102
109
|
|
|
103
110
|
`design.json` 是**设计底稿**:只装人话。"这条能力在哪些工作区出现"**哪里都别写** ——
|
|
@@ -85,6 +85,8 @@ h(Field, { label: t('myunit.mode'), hint: t('myunit.modeHint') },
|
|
|
85
85
|
**`ChoiceGroup`** —— 单选 / 多选(`multi` 区分;`value` 或 `values` + `onChange`;选项 `{ value, label, description? }`;`disabled` 在整组上)。
|
|
86
86
|
**`SelectField`** —— 下拉:默认是**常规下拉框**(跟 `TextField` 一套),无边框那种给 `variant: 'ghost'`;
|
|
87
87
|
搜索、分组、禁用项、加载态、空态、页脚都是它的 prop。
|
|
88
|
+
(它是 dsh 那套按钮 + portal 菜单,**不是原生 `<select>`** —— 验收时 agent 要驱动它得走两拍:
|
|
89
|
+
卡内点触发器 → `scope: page` + `--role menuitem` 按名字点;`workbench_diy_select` 那把工具已经不在了。)
|
|
88
90
|
**`SaveBar`** —— 设置面底下那条"取消 / 保存"(`busy` / `disabled` / `labels` / `onCancel` / `onSave`)。
|
|
89
91
|
**`FormActions`** —— **操作区**(一组按钮):`actions: { key, label, kind?: 'default' | 'danger' | 'primary', onClick, disabled?, busy? }[]`。
|
|
90
92
|
**顺序 / 间距 / 换行都归它** —— `primary` 永远在右、`danger` 紧挨它左侧、一个区只许一颗 `primary`、
|
|
@@ -199,9 +199,10 @@ agent 那一侧也要能通过模块自己的能力读到同一份偏好。
|
|
|
199
199
|
**先从 kit 拿**(`@dsh-workbench/core/client`,`check --only kit` 会列全这份外壳有什么)。
|
|
200
200
|
kit 里确实没有、而你要的又是标准控件(确认条、选择器、设置区块那种)时,可以引 dsh 的客户端原语:
|
|
201
201
|
|
|
202
|
-
* 只在**浏览器侧**引(`src/client.tsx` 那一侧);
|
|
202
|
+
* 只在**浏览器侧**引(`src/client.tsx` 与 `src/components/**` 那一侧);
|
|
203
203
|
* 在 `package.json` 的 `dsh.client.external` 里**声明** `@deepseek-ai/dsh-client-ui-primitives`;
|
|
204
|
-
* 宿主侧(`src/host.ts`)**一个字都不许碰** —— `check`
|
|
204
|
+
* 宿主侧(`src/host.ts`)**一个字都不许碰** —— `check` 的源码边界判据会红;宿主侧 import
|
|
205
|
+
`src/components/**` 还会被 `arch` 按跨运行时(S23)判红 —— 组件整片算 client 面。
|
|
205
206
|
|
|
206
207
|
理由不是"能省事":装机成熟模块里有一半的界面是这么搭的。但**能拿 kit 的时候别自己画** —— kit 那套跟着外壳一起换主题、跟着语言走,
|
|
207
208
|
自己画的不会。
|
|
@@ -1,60 +1,127 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
跑 `dsh-wb-module check`(或单跑一条 `--only <名字>`)。
|
|
4
|
-
**每一条红的都在讲一件真出过事的事**,不是形式主义。红了就照下面修,别抛给用户。
|
|
5
|
-
|
|
6
|
-
| 判据 | 它防的事故 | 红了的典型说法 | 怎么修 |
|
|
7
|
-
|---|---|---|---|
|
|
8
|
-
| `contracts` | dsh 组合阶段**不校验**你 `package.json` 里的声明 —— 错的 `external` 要到浏览器 `require` 那一刻才炸;清单与代码、底稿与清单之间漂了也没人拦 | `单元 id "x" 与 … 撞了` / `dsh.workbenchFaces.client 指的文件不存在` / `dsh.workbenchFaces.host 没声明` / `没有对 @dsh-workbench/core 的 peer 依赖` / `没有 typecheck 脚本` / `它不是 private,却既没声明 publishConfig.access=public…` / `src/declared-tools.ts 与清单对不上(生成物过期…)` / `工具名的字面量又写回了源码` / `…没有 key` / `dsh.workbench 声明的能力与代码真正注册的对不上` / `design.json 的能力行里有「x」,清单的 tools[] 里却没有它` | 按报的那一条改 `package.json`(生成物对不上就跑 `dsh-wb-module sync`;工具名的字面量只许留在清单里);`unit` 换个不撞的名字;`dsh.workbenchFaces` 补上两个**源码**入口;四个脚本补全;私有不发就保持 `private: true`。**工具只声明在清单里**(`dsh.workbench.tools`)且必须与 `mount()` 里 `registerTools()` 真正注册的**不重不漏**;写了 `design.json` 的话,它的 `capabilities[].name` 与清单的 `tools[].name` 也要不重不漏(**键集合对账**,只对名字 —— 内容各写一处);`gate.deniedTools` 是可选的运行期收窄 |
|
|
9
|
-
| `protocol` | `dsh.workbench` 是外壳读你得懂的那份声明:缺版本、形状不对、**多一个键**、枚举值不认都要在装机前拦下来。多出来的键最阴 —— 外壳会**静默忽略**它(拼错一个键 = 那句话白写) | `没有 apiVersion` / `apiVersion 是 99,这份只认 1` / `声明了 apiVersion 就要把静态半写全 —— 缺 dsh.workbench.tools` / `dsh.workbench 里有协议 v1 不认识的键 "faces"` / `card 里有不认的键 "enabledByDefault"` / `tools[0] 里有协议不认的键 "scopes"` / `tools[0] 里有不认的键 "scope"` | 补 `"apiVersion": 1`;把 `unit` / `card` / `tools` / `host` 四个键写全(没卡片写 `"card": null`、没工具写 `"tools": []`、一个服务都不要写 `"host": { "inject": [] }`);删掉协议外的键(`faces` 住在 `dsh.workbenchFaces`,写回 `dsh.workbench` 里就是生字段);`scope` **不要写**。字段清单见 `dsh-wb-module` 的 README「协议」那一节 |
|
|
10
|
-
| `bundles` | 两个运行时互串:浏览器产物里带 node 内置、宿主产物里带 DOM / react | `浏览器产物里出现了 node 内置 …` / `宿主产物里出现了 react` | 宿主面别 import 浏览器侧的东西;浏览器面别 import `node:*`。两侧只通过服务名相接 |
|
|
11
|
-
| `source` | 非种子词的 `@deepseek-ai/*`、宿主侧碰 dsh 的浏览器包、`@/` 指到工作台源码 | `…在宿主侧可达` / `不是平台种子词` | 宿主侧只用 Node 侧的东西;dsh 原语一律走 `kit`;`@/` 只指自己的 `src/` |
|
|
12
|
-
| `primitives` | 声明了 `external` 却没有产物物证 ⇒ dsh 原语被**内联**成第二份实例 | `…用了却没声明 external` / `宿主产物里出现了` | 在 `dsh.client.external` 里显式声明它;确认产物里是 `require(...)` 而不是被打包进来 |
|
|
13
|
-
| `kit` | 用了 peer 下限那份外壳**还没有**的 kit 成员 ⇒ 卡片首帧 `Element type is invalid`,整块面板被 React 卸掉 | `kit.components.X 在这份外壳里没有` / `peer 下限比 devDependency 还旧` | 只用 peer 范围里真有的成员;把 `peerDependencies` 的下限提到你实际用的那档 |
|
|
14
|
-
| `cards` | 卡片在某一种**外壳侧输入**下崩 / 空白 / 变脏 ⇒ 用户看到"卡片一直不出来 / 点一下白屏 / 屏上冒出 `undefined`"。判据拿七种输入各喂一遍(待机 / 在读 / 在读但保留旧值 / 有值 / 有值但空容器 / 读不到 / 能力闸缺插件),`zh`、`en` 各一遍;取数走**你装的那份真壳**的 `useUnitBoard` | `loading(zh) 卡片抛了` / `loading(zh) 卡片返回空` / `屏上出现了 undefined`(`NaN` / `[object Object]` / `null` 同形) / `t('x') 在 zh 里没有落点` / `卡片没套外壳的卡壳` / `清单的 card.titleKey「x」在模块字典的 en 里没有` | 让卡片在五态下都画得出来(已有的加载 / 空 / 阻断态);外面套 `kit.components.Card`;清单写了 `titleKey`,`registerUnit` 交的 `messages` 里 `zh` / `en` 都得有那一条;**卡片与它用的 kit 组件问过的每个键也都要有落点**(判据自己当 `t`,没命中就报原始键名)。判据**不断言**"画的是哪一态那一屏" —— 那归你自己那份 `plugin.test.ts` |
|
|
15
|
-
| `standalone` | "只有工作台源码在场才编得过" ⇒ 发布出去就散架 | `build 在外壳源码不在场时失败` / `typecheck 在外壳源码不在场时失败` | 别引用工作台源码;`tsconfig` 里 `paths` 的值必须是**相对路径**(`"./src/*"`,不是 `"src/*"`) |
|
|
16
|
-
| `markers` | 内网域名 / 企业内部名泄进要公开发布的包 | `命中内网 npm 源` / `命中内网域名` | 那些值不该写进包里;要配就落到模块自己的配置面,或走用户自己声明的来源 |
|
|
17
|
-
| `alias` | `@/` 伸进工作台源码 ⇒ 装的人手上没有那份源码 | `…指向外壳源码` | `@/` 只许指自己的 `src/`;跨包的东西走包名 |
|
|
18
|
-
| `style` | 模块定义 `.wb-*` 去覆盖外壳的类(全局类名,两份规则按注入顺序打架) | `定义了外壳前缀 wb-* 的 CSS 规则` / `借用了契约里没有的类` | 自己的样式走 `*.module.css`(类名**不带** `wb-` 前缀,构建时按文件路径哈希);外壳给的类只能用契约里列出的那些,要借就写 `:global(.wb-…)`;被 `样式归属` 判红的手写「标签+控件」行或模块自己管的行距 ⇒ 改用 `FormRow(s)`([KIT.md](KIT.md) §5) |
|
|
1
|
+
# 门禁红了怎么办:十三条判据的人话对照表
|
|
19
2
|
|
|
20
|
-
|
|
3
|
+
跑 `dsh-wb-module check`(或单跑一条 `--only <名字>`)。**每条红都在讲一件真出过事的事**;红了照下表修,别抛给用户。
|
|
4
|
+
|
|
5
|
+
| 判据 | 它防什么 | 怎么修 |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `contracts` | dsh 组合期**不校验** `package.json` 声明;清单 ↔ 代码 / 底稿 ↔ 清单漂了没人拦 | 报哪条改哪条:`unit` 撞了换名;`dsh.workbenchFaces` 补两个**源码**入口;四个脚本补全;私有就 `private: true`。`dsh.workbench.tools` 与 `registerTools()` 注册的**不重不漏**;写了 `design.json` 就对**键集合**(只对名字);生成物过期跑 `dsh-wb-module sync` |
|
|
8
|
+
| `protocol` | `dsh.workbench` 缺版本 / 形状不对 / **多一个键** —— 外壳会**静默忽略**生字段 | 补 `"apiVersion": 1`;`unit`/`card`/`tools`/`host` 写全(没卡片 `null`、没工具 `[]`);删协议外的键(`faces` 住 `dsh.workbenchFaces`,`scope` **不要写**);字段清单见 module-tools README「协议」 |
|
|
9
|
+
| `bundles` | 两个运行时互串:浏览器产物带 node 内置 / 宿主产物带 DOM、react | 各自别 import 对方那侧;只通过服务名相接 |
|
|
10
|
+
| `source` | 非种子词的 `@deepseek-ai/*`、宿主侧碰 dsh 浏览器包、`@/` 指到工作台源码 | 宿主侧只用 Node 侧;dsh 原语一律走 `kit`;`@/` 只指自己 `src/` |
|
|
11
|
+
| `primitives` | 声明 `external` 却没产物物证 ⇒ 原语被**内联**成第二份实例 | 在 `dsh.client.external` 显式声明;确认产物里是 `require(...)` |
|
|
12
|
+
| `kit` | 用了 peer 下限那份外壳**还没有**的成员 ⇒ 卡片首帧 `Element type is invalid`,整块面板被卸 | 只用 peer 范围里真有的成员;下限提到你实际用的那档 |
|
|
13
|
+
| `cards` | 卡片在外壳侧输入下崩 / 空白 / 变脏(八态 × `zh`/`en` 各一遍;走**真壳**取数) | 五态都画得出(加载 / 空 / 阻断);外套 `kit.components.Card`;`titleKey` 与卡片问过的每个键 `zh`/`en` 都有落点。判据**不断言**画的是哪一态 —— 那归 `plugin.test.ts` |
|
|
14
|
+
| `standalone` | "只有工作台源码在场才编得过" ⇒ 发出去就散架 | 别引用工作台源码;`tsconfig` 的 `paths` 用**相对路径**(`"./src/*"`) |
|
|
15
|
+
| `markers` | 内网域名 / 企业内部名泄进要公开发布的包 | 别写进包里;要配就落到模块自己的配置面 |
|
|
16
|
+
| `alias` | `@/` 伸进工作台源码 ⇒ 装的人手上没那份源码 | `@/` 只许指自己 `src/`;跨包走包名 |
|
|
17
|
+
| `style` | 模块定义 `.wb-*` 覆盖外壳类;手写「标签+控件」行 / 操作区 | 样式走 `*.module.css`(不带 `wb-` 前缀);借外壳类写 `:global(.wb-…)`;行与操作区用 kit 的 `FormRow(s)` / `FormActions`([KIT.md](KIT.md) §5) |
|
|
18
|
+
| `arch` | 结构退化(巨文件 / 反向依赖 / 跨运行时 / 表外目录 / 模型层没测试) | 见「第十二条 `arch`」 |
|
|
19
|
+
| `lint` | 单文件语法卫生(`as any` / 残留 `console`、`debugger` / 内联颜色、`!important` / react-hooks) | 见「第十三条 `lint`」 |
|
|
20
|
+
|
|
21
|
+
## 第十二条 `arch`:模块自己的分层形状
|
|
22
|
+
|
|
23
|
+
**先分清跑没跑**:只跑带 `arch.config.mjs` 的模块;没有那份文件 = `○ 不适用`(skip),**不是红**。上车就在包根加一行工厂:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import { moduleArch } from '@dsh-workbench/module-tools/arch';
|
|
27
|
+
export default moduleArch();
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**怎么读**:`dsh-wb-module check --only arch`。通过长这样(数字随模块走):
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
✔ 模块架构(分层 / 边界 / 设计系统) 1/1 个模块包带 arch.config.mjs · 7 个源文件 · 58 条规则在跑
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
红了逐条是 `<包名>: <文件>:<行> <说明>(<规则>) —— <hint>`;括号里那个规则号是下表的键;只有 `error` 挡门禁(`warn` 只提示);配置自己跑不起来(import / 语法错)也判红。
|
|
37
|
+
|
|
38
|
+
| 常见红(规则) | 它在说什么 | 怎么修 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `[S01] 文件不在目录契约内` | 文件放到层序表外的目录(如 `src/utils/`) | 挪进 `model/` / `components/`,或用 `layers`/`roles` 明写这一层。`src/messages.ts` / `src/declared-tools.ts` / `src/**/__tests__/**` 已有内置角色 |
|
|
41
|
+
| `[S21] 反向依赖` | 底层文件引了更上层(如组件引 `../host`) | 被引的下沉到 `model/`;只许依赖层号 ≤ 自己的 |
|
|
42
|
+
| `[S23] 跨运行时走了「client」面` | **宿主侧 import 了 `src/components/**`**(或反过来) | 组件整片算 client 面;两侧只通过服务名相接 |
|
|
43
|
+
| `[S16] 文件 N 行,超过上限 200` | 文件太长(默认文件 200 / 函数 80)。**测试文件不吃这条**(`.test.`/`.spec.` 与 `src/**/__tests__/**` 豁免) | 拆;确实该长的传 `fileLines`/`viewLines`/`functionLines`,或给这一个文件开 `exceptions` |
|
|
44
|
+
| `[M08] 没有配对测试` | `src/model/**` 有文件却没测试 | 补 `src/model/X.test.ts` |
|
|
45
|
+
| `[D12] padding: 13px 不在长度的刻度里` | 手搓了不在刻度上的数值 | 引用 `--dsw-*` 令牌或走刻度(`0` 本来就被跳过) |
|
|
46
|
+
| `[D09] … 用了 !important` | `!important` 只许写在 `src/components/vendor/` | 用选择器 / 层级解决 |
|
|
47
|
+
| `[D17] 定义的类没人用` | `.module.css` 里定义了没人用的类 | 删掉 |
|
|
48
|
+
|
|
49
|
+
要放宽**从参数进**(别改判据):`moduleArch({ fileLines, deps, layers, roles, structure, exceptions })`,`exceptions` 每条**必须写 `reason`**(后缀 / 单文件都行,glob 是 picomatch):
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
export default moduleArch({
|
|
53
|
+
layers: { host: 10 },
|
|
54
|
+
roles: [{ id: 'lib:platform', pattern: 'src/platform/**', layer: 1 }],
|
|
55
|
+
exceptions: [{ rule: 'S16', glob: 'src/host/service.ts', reason: '…为什么这一个该长…' }],
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
⚠️ 层序表与两个面(`dsh.workbenchFaces` 指的那两个文件)不是可调项 —— 改了先红 `contracts`/`source`(换面要整体替换 `structure` 并改清单)。内置已有三条豁免,不必重复声明。
|
|
60
|
+
|
|
61
|
+
## 第十三条 `lint`:模块自己的语法卫生
|
|
62
|
+
|
|
63
|
+
`arch` 管**文件之间**的结构,`lint` 管**文件之内**的写法。
|
|
21
64
|
|
|
22
|
-
|
|
65
|
+
**先分清跑没跑**:只跑带 `eslint.config.mjs` 的模块;没有 = 汇总行里 `○ 模块代码卫生(eslint):(不适用)…`(skip),**不是红**。上车就在模块根放一份:
|
|
23
66
|
|
|
24
|
-
|
|
67
|
+
```js
|
|
68
|
+
import { moduleLint } from '@dsh-workbench/module-tools/lint';
|
|
69
|
+
export default moduleLint({ rootDir: import.meta.dirname });
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`rootDir` 把 typescript-eslint 的 `tsconfigRootDir` 钉在这个模块上 —— 本仓根上还有一份配置,多份并存时不传就猜不出来(每个文件报 `No tsconfigRootDir was set…`),所以模板与骨架都带着它。
|
|
73
|
+
|
|
74
|
+
**怎么读**:`dsh-wb-module check --only lint`。通过长这样(部分模块上车时多一句 `其余 N 个没有 eslint.config.mjs(不适用)`):
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
✔ 模块代码卫生(eslint):1/1 个模块包带 eslint.config.mjs · 8 个文件 · 0 error · 0 warn
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
红了逐条是 `名字: 文件:行:列 规则 —— 一句话`(压成单行、截 200 字)。**只有 `error` 算红**,`warn` 只计数(单跑时列在 `warn(不挡门禁):` 下);聚合最多列 40 条,`--only lint` 列全;配置跑不起来也判红(同 `arch`)。
|
|
81
|
+
|
|
82
|
+
| 常见红(规则) | 它在说什么 | 怎么修 |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `@typescript-eslint/no-explicit-any` | `as any` / `: any` 绕开契约 | 写得出真类型就写;要放行从 `moduleLint({ rules })` 开口子,或就地 `// eslint-disable-next-line` |
|
|
85
|
+
| `no-console` | `src/**` 里留了 `console.log`/`info`/`debug`(`warn`/`error` 放行) | 删掉;`scripts/**` 不判这条 |
|
|
86
|
+
| `no-debugger` | 留了 `debugger` | 删掉 |
|
|
87
|
+
| `no-restricted-syntax` | 内联颜色字面量(`#ff0000`/`rgb(`/`hsl(`),或字符串里带 `!important` | 外观走 `--dsw-*` 与 `*.module.css`;`!important` 只许 `src/components/vendor/**` |
|
|
88
|
+
| `react-hooks/*`(`rules-of-hooks` · `refs` · `exhaustive-deps` · `immutability`) | 条件调 hook / 渲染期写 ref / 漏依赖 | hook 提到顶层、副作用放 `useEffect`、依赖补全 |
|
|
89
|
+
| `@typescript-eslint/no-unused-vars` | 死代码(`^_` 开头放行) | 删掉,或改名 `_x` |
|
|
90
|
+
| 其余 `recommended` / `react/*` | 语法级那批(`no-unreachable` / 空 `catch` / `jsx-key`…) | 照 eslint 的原话修 |
|
|
91
|
+
|
|
92
|
+
要放宽**从命名参数进**(`ignores`/`rules`);按目录细调就展开再追一块:
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
export default [
|
|
96
|
+
...moduleLint({ rootDir: import.meta.dirname, rules: { 'no-console': 'off' } }),
|
|
97
|
+
{ files: ['src/legacy/**'], rules: { '@typescript-eslint/no-explicit-any': 'off' } },
|
|
98
|
+
];
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
⚠️ **别改方言**:判据拿 `moduleLint()` 产出的**整份 8 块**(`@eslint/js/recommended` · 三块 `typescript-eslint/*` · `dsh-wb/module-lint` 那四块)与你的配置**逐块比**(名字 / `files` / `rules`;`/overrides`、`/root` 不比),全局 ignores 按**超集**比:多忽略可以,少一条点名缺哪条 glob。少一块、同名两次、删一条规则、改一个规则值都当场红;**只抄一个名字**(`[{ name: 'dsh-wb/module-lint', rules: {} }]`)、`export default []`、`moduleLint().filter(…)` 这类筛法也红。**残余只剩一条**:自己 import 方言再照抄整份分不出来 —— 这是抬高门槛、让改动明着写,不是"防住伪造"。
|
|
102
|
+
|
|
103
|
+
**`--fix`**:`dsh-wb-module lint --fix`(模块里是 `pnpm lint -- --fix`)写回能自动修的、打印「修了 N 条 / 还剩 M 条」。**`check` 永远不动手改文件**。
|
|
104
|
+
|
|
105
|
+
## 什么时候该怀疑门禁本身
|
|
106
|
+
|
|
107
|
+
判据自己也有正反例,正常不误报。但**骨架刚 `init` 出来、一行没改却红了** —— 那是模板 / 工具坏了,不是你的。
|
|
25
108
|
|
|
26
109
|
## 三条"不是判据、但更常踩"的
|
|
27
110
|
|
|
28
|
-
|
|
111
|
+
(细节在 [WIRING.md](WIRING.md) / [BUILD.md](BUILD.md))
|
|
29
112
|
|
|
30
|
-
1. **装上去没反应** → 先问 `workbench_diy_status`(`unit
|
|
31
|
-
2. **改完没热换** →
|
|
113
|
+
1. **装上去没反应** → 先问 `workbench_diy_status`(`unit=…`)。[WIRING.md](WIRING.md) §3
|
|
114
|
+
2. **改完没热换** → 宿主侧秒级热换;客户端刷新页面。[WIRING.md](WIRING.md) §4
|
|
32
115
|
3. **"再装一遍"** → 日常迭代只重建、不重装。[WIRING.md](WIRING.md) §3
|
|
33
116
|
|
|
34
117
|
## 用户说"卡片不对"时:四步取证
|
|
35
118
|
|
|
36
|
-
他说"一片空白 / 一直转圈 / 数字不对"
|
|
37
|
-
|
|
38
|
-
1. **`workbench_diy_panel
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
3. **自己把那条读跑一遍**:卡片画的就是模块自己那条宿主路由的返回值 ——
|
|
44
|
-
`curl '<工作台地址>/workbench/api/<unit>/<route>'`(地址就是你起工作台那个,默认 `http://127.0.0.1:3081`)。
|
|
45
|
-
接口回对了而卡片不对,问题在渲染那一侧;接口就回错,问题在宿主半侧(回头照着 [DATASOURCE.md](DATASOURCE.md) 重探一次)。
|
|
46
|
-
要的是**浏览器里那一次**的原文(带凭据的那种),用 `workbench_diy_probe`(`script` 里 `fetch` 自己那条读)。
|
|
47
|
-
4. **最后才看用户那一页**:要这一页的**控制台**,走三拍 —— `workbench_diy_logs_watch`(装上带钩子的收听)→
|
|
48
|
-
做那件你怀疑会报错的事 → `workbench_diy_logs_read`(最近 50 条,读完 `workbench_diy_logs_stop` 卸掉)。
|
|
49
|
-
钩子有上限(每页 200 条、单条截 400 字、随刷新消失),不会把用户那一页吃胖。要在页面里跑一段自己的代码
|
|
50
|
-
(读接口 / 抄一个 DOM 值),用 `workbench_diy_probe`。**别用 `opencli browser <会话> console`** ——
|
|
51
|
-
那条命令拿到的是个 stub(恒回空数组、还 exit 0),看上去像"没有报错"。
|
|
52
|
-
要看这一页是什么、有哪些可点的,用 `state`;要一张图用 `screenshot <路径>`。
|
|
53
|
-
宿主侧热换 / 装载的报错在**起工作台那个终端**里,不在浏览器里。
|
|
54
|
-
|
|
55
|
-
两条纪律:**别让用户截图或替你转述报错**(第 1 步就是干这个的);**截图与 probe 只用来定位,不用来验收** ——
|
|
56
|
-
验收仍是他对着三张表走「打开哪 → 看什么 → 点什么 → 应该看到什么」。
|
|
57
|
-
|
|
58
|
-
面板运行态是**用户那一页自己报上来的**(页面关了就停在最后一次,带时间与新鲜度),所以第 1 步没有内容时,
|
|
59
|
-
先让他把工作台切到工坊那个工作区、刷新一次页面再看。
|
|
119
|
+
他说"一片空白 / 一直转圈 / 数字不对"时,**先取证再改代码**;前一步有结论就停在那儿修:
|
|
120
|
+
|
|
121
|
+
1. **`workbench_diy_panel`** —— 这个工作区面板现在什么样:哪几张卡、每张最后一次取数是读到了还是读不到(带宿主回的码与原文)、崩过没(原因 + 次数)。有答案就别往下走。
|
|
122
|
+
1.5 **看这一张卡**(`workbench_diy_screenshot` / `workbench_diy_html` / `workbench_diy_find` / `workbench_diy_eval`)—— 打的是**用户当前那一页**上工坊那块板里这张卡(`unit=…`):`screenshot` 给图(要卡那一块先量盒子、填 `clip`)、`html --as json` 给结构树、`find` 给元素与 `ref`、`eval` 算派生读数(对比度 / 可见文本;`in: 'card'` 注入卡根 `root`,脚本自己 `return`)。配合上一步把"看起来不对"说成"哪一处不对"。**这一族(浏览器那 14 把 + `logs_*`)要先 `workbench_diy_debug_start`**(`unit=…`;会请用户点头一次)—— 没有这场约定一律当场拒;不看屏先 `_pause`,收工 `_stop`。(`_panel` / `_status` / `_probe` 不碰他那一屏,不用开。)
|
|
123
|
+
2. **`workbench_diy_status`(`unit=…`)** —— 看"起得来"那一行:模块起不来时原因就在那里(装配抛的错 / 声明被拒的原文)。
|
|
124
|
+
3. **自己把那条读跑一遍**:卡片画的就是模块那条宿主路由的返回值 —— `curl '<工作台地址>/workbench/api/<unit>/<route>'`(默认 `http://127.0.0.1:3081`)。接口对而卡片不对 ⇒ 渲染侧;接口就错 ⇒ 宿主侧(照 [DATASOURCE.md](DATASOURCE.md) 重探)。要**浏览器里那一次**的原文,用 `workbench_diy_probe`(`script` 里 `fetch` 自己那条读)。
|
|
125
|
+
4. **最后才看用户那一页**:要控制台走三拍 —— `workbench_diy_logs_watch` → 做那件你怀疑会报错的事 → `workbench_diy_logs_read`(读完 `workbench_diy_logs_stop` 卸掉;钩子上限:每页 200 条、单条 400 字、随刷新消失)。**别用 `opencli browser <会话> console`** —— 那是个 stub(恒回空数组、还 exit 0)。要在页面里跑自己的代码用 `workbench_diy_probe`(**读会话**那页);看**用户那一页**是什么、有哪些可点的用 `workbench_diy_state`。宿主侧热换 / 装载的报错在**起工作台那个终端**里。
|
|
60
126
|
|
|
127
|
+
两条纪律:**别让用户截图或替你转述报错**;**验收就是你自己拿这一族走** —— 逐条对着三张表走「打开哪 → 看什么 → 点什么 → 应该看到什么」,`screenshot`/`html`/`find`/`eval` 的读数、动作族的返回、`workbench_diy_network` 的收发包、`logs_*` 的报错都算证据;走通再 `design accept`,走不通的如实说没验过(用户不必在场,也不用请他逐条点头)。工具参数与返回体都是 opencli 的原样(见 [TOOLS.md](TOOLS.md) §13)。面板运行态是**用户那一页自己报上来的**,第 1 步没有内容时,先让他把工作台切到工坊那个工作区、刷新一次页面再看。
|