dsh-dbhub-live 5.0.1 → 5.2.0-dev.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.
Files changed (50) hide show
  1. package/AGENTS.md +95 -209
  2. package/README.en.md +251 -169
  3. package/README.md +251 -169
  4. package/doc/ARCHITECTURE.md +108 -0
  5. package/doc/CLIENT.md +124 -0
  6. package/doc/DESENSITIZE.md +56 -0
  7. package/doc/DEVELOPMENT.md +139 -0
  8. package/doc/IMPACT-MATRIX.md +36 -0
  9. package/doc/RELEASE.md +93 -0
  10. package/doc/REQUIREMENTS.md +177 -105
  11. package/doc/images/chat-query.png +0 -0
  12. package/doc/images/row-editor.png +0 -0
  13. package/doc/images/settings-page.png +0 -0
  14. package/lib/adhoc.mjs +412 -34
  15. package/lib/capability.mjs +157 -0
  16. package/lib/client.js +1243 -80
  17. package/lib/config.mjs +370 -21
  18. package/lib/i18n.mjs +119 -4
  19. package/lib/index.mjs +401 -43
  20. package/lib/mcp.mjs +496 -391
  21. package/lib/meta.mjs +267 -0
  22. package/lib/options.mjs +25 -5
  23. package/lib/policy.mjs +92 -0
  24. package/lib/toml.mjs +188 -0
  25. package/lib/tools.mjs +1093 -827
  26. package/lib/update.mjs +244 -0
  27. package/package.json +34 -10
  28. package/scripts/desensitize-check.mjs +10 -3
  29. package/scripts/desensitize.config.mjs +255 -237
  30. package/scripts/docs-check.mjs +214 -0
  31. package/scripts/release-preflight.mjs +180 -0
  32. package/scripts/render-check.mjs +1366 -843
  33. package/test/adhoc-toml.test.mjs +365 -0
  34. package/test/bridge.test.mjs +227 -185
  35. package/test/capability.test.mjs +231 -0
  36. package/test/client-format.test.mjs +515 -352
  37. package/test/configure-options.test.mjs +244 -0
  38. package/test/configure-ssh.test.mjs +232 -0
  39. package/test/copy-from.test.mjs +155 -0
  40. package/test/env-options.test.mjs +456 -0
  41. package/test/meta.test.mjs +234 -0
  42. package/test/options.test.mjs +160 -123
  43. package/test/policy.test.mjs +132 -0
  44. package/test/row-cache.test.mjs +237 -138
  45. package/test/ssh-probe.test.mjs +834 -0
  46. package/test/test-op-fastpath.test.mjs +208 -0
  47. package/test/toml.test.mjs +352 -0
  48. package/test/update.test.mjs +270 -0
  49. package/test/util.test.mjs +17 -0
  50. package/test/zero-knowledge.test.mjs +120 -78
package/doc/CLIENT.md ADDED
@@ -0,0 +1,124 @@
1
+ # 客户端半(lib/client.js)与布局契约
2
+
3
+ > AGENTS.md 的配套文档:改 `lib/client.js`、改配置页布局/交互/i18n 时读。
4
+ > 架构红线(注入、零知识、状态通道)见 [AGENTS.md](../AGENTS.md);**这些布局取舍的依据**(为什么只用图标、为什么“来源”不做徽章、为什么库名不截断、为什么是 700/720 双阈值)见 [ARCHITECTURE.md](ARCHITECTURE.md#布局取舍)。
5
+
6
+ ## bundle 契约(手写 lazy-CJS,无构建步骤)
7
+
8
+ `lib/client.js` 由 DSH client 模块系统按 `dsh.client` 声明 + `exports["./client"]` 直接服务,**没有构建步骤**。改完跑 `node test/client-format.test.mjs` 守护:
9
+
10
+ - 必须以 `window.__ModuleLoader__.load({ id: "dsh-dbhub-live", factory: (require) => { … } })` 注册;
11
+ - `id` 必须等于 Loader entry 名(`dsh-dbhub-live`,见 `cordis.patch.yml` 的 `name`),**不是行 id**;
12
+ - factory 只允许 `require("react")` 等 baseline 模块;不 import 其他插件的值(bundle-purity);
13
+ - 导出 `name` / `inject` / `apply`,结尾 `return module.exports`;
14
+ - 若本包脱离仓库发布 npm,`dsh.client` 与 `exports["./client"]` 必须保留,否则 client-modules 扫描会启动报错。
15
+
16
+ ## 三个入口(都要在,且不得自锁)
17
+
18
+ | slot | 说明 |
19
+ | --- | --- |
20
+ | `settings.section`(id `dbhub`,order 40) | **主入口,始终注册**。设置面板是核心 shell,任何部署都在,用户也在那里找连接管理 |
21
+ | `sidebar.panellist`(id `dbhub`)+ `main`(key `dbhub`) | 侧边栏快捷入口,**仅 `enabled && showSidebarEntry` 同时成立时显示** |
22
+ | `plugins.row.config`(key `dsh-dbhub-live#dbhub-live`) | 组件同时服务 `props.view === 'summary'`(一行摘要)与 `'page'`(完整表单,自画标题/图标/面包屑);仅在官方插件页浏览器半区活跃时存在,**不能当唯一入口** |
23
+
24
+ - **「隐藏入口」≠「禁用插件」**:关开关只是不显示侧边栏入口,工具照常可用;禁用则一律不贡献入口。
25
+ - 两种状态下 `settings.section` 与 `plugins.row.config` 都**始终注册**,否则用户再也回不来。
26
+ - 侧边栏由 `syncSidebar()` 挂 `face.subscribe` 按 view 即时 register/dispose;判定 `sidebarWanted()` = `view.enabled !== false && view.showSidebarEntry !== false`。
27
+ - 注册不存在的 slot 会被静默忽略(「配置页消失」就是这么来的),别改回已删除的 slot。
28
+
29
+ ## host face:探测与写回(别退回 inject)
30
+
31
+ `export const inject` **只能有 `["slots"]`**。`legacyFace(scope)` 与 `httpFace()` 实现同一接口(`getSnapshot` / `subscribe` / `setEnabled` / `saveConfig` / `configOp`,快照 `{ status, value }`),`ctx.get('settingsScope')` 有则优先 legacy。
32
+
33
+ - HTTP face 四个入口:`GET /state`、`POST /enabled`、`POST /options`、`POST /op`;写完用响应里的 view 直接刷新快照(不等轮询);`configOp` 的 `{op: …}` 直接 POST。
34
+ - **选项写入优先走官方 settings 表单**:Host 发布 `entryId`,客户端有 `ctx.configForms` 且 entryId 非空时 `configForms.get(entryId).set(field, value)`,失败/解析不到才回退 `POST /options`(0.1.x 与无 settings 客户端走这条)。
35
+ - **HTTP 通道轮询是「按订阅」的**:只有渲染中页面用 `props.subscribe(fn, { live: true })` 起 1.5s 轮询(`document.hidden` 跳过);插件作用域订阅(`face.subscribe(syncSidebar)`)只做一次性拉取。**空闲 GUI 不得持续轮询**,残留 interval 会让 `check:ui` 挂住不退出。
36
+ - 组件不得接触 `ctx`:数据/回调一律通过注册时的 `inject: () => face` 传入 props。
37
+
38
+ ## apply() 必须能被重复执行
39
+
40
+ 客户端 HMR 会重跑整个 bundle。`localeSvc.register(NS, …)` **不幂等**(同 namespace+locale 再注册抛 `locale namespace … already has locale zh`,整个浏览器半区同步失败)。所以:
41
+
42
+ 1. 捕获 disposer 用 `ctx.effect(() => dispose, …)` 挂在插件作用域;
43
+ 2. 注册失败**降级到 `localTranslator(localeSvc)`**(读本 bundle 字典 + `localeSvc.getLocale().active`),绝不打断整个插件半;
44
+ 3. 新增/改动 `apply()` 注册都要问一句:**reload 第二次还会成功吗?**
45
+
46
+ 样式在 `apply()` 里由 `ensureStyle()` 注入一份作用域样式表(`#dsh-dbhub-live-style`,类名前缀 `dbh-`)——悬浮/焦点是伪类,内联样式表达不了;**不要把样式退回成一堆内联对象**。
47
+
48
+ ## 主题适配(红线)
49
+
50
+ - 样式表**只允许 `--dsw-alias-*` token**,兜底值必须中性且半透明(`rgba(127,127,127,.3)`、`transparent`);
51
+ - **禁止任何不透明色字面量**(`#ffffff` / `#f6f8fa` / `#1f2328` / 品牌色…)。用户会装主题/皮肤插件(本机就有 skin-center,背景可能是半透明的),写死白底或固定浅灰字会直接看不清;`test/client-format.test.mjs` 有 "tokens only" 断言门禁;
52
+ - 只在动态取值时用内联样式;对比度:正文及以上用 `label-primary` / `label-secondary`,`label-dimmed` 只留给极次要说明。
53
+
54
+ ## 布局契约
55
+
56
+ ### 整体结构
57
+
58
+ - **按 `path` 分组**(`groupRows`;标题不是身份——同名工作区是不同目标,同名组头用路径消歧)。
59
+ - `layoutModeOf` **无阈值**判定:所有工作区都只有 1 个环境 → **平铺网格**(`dbh-tiles`,`minmax(280px,1fr)` + 内容列 `max-width:960px`,自然封顶 3 列);只要有任一工作区是多环境 → **分组折叠**(`dbh-group` + 可点击组头 `aria-expanded`)。判定必须是纯函数,改判定先改 `scripts/render-check.mjs` 的断言。
60
+ - **层级**:工作区分组是 level-1(边框卡片 + 极淡阴影),环境行是 level-2(缩进 + 左侧引导竖线 `dbh-groupbody{border-left}`);两层绝不能长得像同一层。
61
+ - **默认展开**:单个工作区自动展开;多工作区时展开「最近活跃工作区」(由 `useWorkspaces` 的 `WorkspaceSnapshot.updatedAt` 推出;设置面板是全局的,拿不到当前会话选中态,**别猜**);取不到快照全折叠。另有「全部展开/折叠」总控。展开态 / 添加面板 / 设置条存模块级 `uiMemory`(同页面会话内跨重挂载保留,**不持久化**——视图状态不是配置)。
62
+
63
+ ### 每个连接只占一行
64
+
65
+ ```
66
+ 环境 chip(窄、淡) · 连接元数据 · source chip · [▶][✏][🗑]
67
+ ```
68
+
69
+ 三条硬规则:
70
+
71
+ 1. 行内操作**只允许图标**(`primitives` 的 `IconPlayOutline16` / `IconEditOutline16` / `IconCloseOutline16` / `IconTrashOutline16`,无边框 24px,label 走 `title`/`aria-label`)——文字按钮一放上来就吃掉半行。
72
+ 2. `source` 做成**可复制 chip**(`sourceChip`):虚线边框 + 复制图标 + **短句柄**(`shortHandle`:≤14 字全显,否则保留"工作区哈希 + 环境段"两段),让人一眼看出"这是 id、点了复制",而不是"复制整行连接串";全文与用途进 tooltip(`ws.srcHint`),复制走 `primitives.writeClipboard`(自带 execCommand 兜底)。
73
+ 3. 连接元数据用 `connLabelView` 拆成 `前缀(type://host:port/) + 库名`,**前缀可截断、库名绝不截断**(`dbh-conn-pre` 省略号 / `dbh-conn-db` 不收缩)——"窄行里看不到库名"是最常见的抱怨。
74
+
75
+ - 行内只允许出现 `w.conn`(元数据);`srcId` 只是句柄,**绝不回显 DSN**。
76
+ - 🔒 前缀:**现代面不加**(会被误读成"需要密码",且与只读 chip 的锁冲突);legacy 面保留原标记。
77
+ - 「来源」(手填 / 扫描 / 复制 / 自动发现)**不得做成行内徽章**——它几乎每行都一样却最抢眼;改为环境名 chip 的 tooltip(`originTitle`),只有"自动发现且未持久化"这一例外在 tooltip 里显式点名。
78
+ - 改名走 `configOp({op:'rename'})`;改名 + 改连接走**一条** `{op:'add', renameFrom}`(`configOp` 在 legacy 通道是单字段,连续两次写会互相覆盖)。
79
+
80
+ ### 顶部与添加
81
+
82
+ - **legacy 面(0.1.x)顶部只留一行**:状态徽章 + 工具数 + 设置条摘要 + `[⚙ 设置]` + `[启用/禁用]`;自动更新间隔与「在侧边栏显示入口」复选框一起收进默认收起的设置条。
83
+ - **现代面(≥0.2.1)按「三面布局」**:标题 + 版本 + 升级链接;状态行只留状态与连接数;设置用 DSH 行样式;长说明进折叠区。
84
+ - **添加连接默认收起**:「+ 添加连接」(全局;工作区用 `<datalist id="dbh-ws-list">` 给已有工作区)/ 分组头「+ 环境」(工作区自动带入,少一个字段)。保存仍走原有「试连 → 失败才确认」门禁。
85
+
86
+ ## 环境选项的落点(只读 / SSH)
87
+
88
+ 同一组选项在**两个面**上呈现不同,按 `view.dshModern` 分叉——**这是 legacy 与现代面的分工,不是新旧两套规则**:
89
+
90
+ | 元素 | 现代面(`dshModern === true`) | legacy 面(`dshModern === false`) |
91
+ | --- | --- | --- |
92
+ | 只读标记 | 行内 `.dbh-rochip`,弹窗/宽屏/手机三种布局都出现;点击发 `{op:'options', readOnly}`(无 DSN);必须 **开锁+「可写」/ 闭锁+「只读」**(同一 16px 线性图标风格),否则两个灰色「只读」会被误读成都是只读 | 环境 chip 内 `span.dbh-ro`,说明并入 chip `title` |
93
+ | 设置控件 | 开关必须用 `primitives.Switch`(缺失时才回退 `.dbh-fbswitch`) | `.dbh-checkbox` 复选框 |
94
+ | 连接串 🔒 | 不加 | 保留 |
95
+
96
+ - SSH 只影响路由,**不做徽章**,只进环境 chip 的 tooltip(`ro.sshTitle`)。
97
+ - 编辑表单里的只读勾选保留(两面都有);`.dbh-checkbox` **不用 `.dbh-input`、不包进 `.dbh-field`**,否则 `render-check` 的 `dbh-field === 3` 与 `typeWithin('dbh-addpanel',0..2)` 索引会变。
98
+ - SSH 高级区 `.dbh-adv` 折叠面板**必须排在 DSN 输入之后**,折叠态放 `uiMemory.advOpen`;区内「测试该配置」旁是**「测试 SSH 隧道」**(独立按钮/回执,只验 SSH 层)。
99
+ - 秘密输入 `type="password"`、占位符写明「留空 = 保持原值」,**编辑态绝不回显已存密码/口令/密钥路径**(镜像行只给 `authKind` / `hasPassword` / `hasPassphrase` / `keyReady` 布尔)。
100
+ - 写入载荷统一走 `connDraftOf(form)` / `withConnDraft(op, form)`:只读开关与隧道随 `add` / `test` / `options` 三种 op 一起发出,**绝不单独再发一条**。表单对象在产生 op 时会带 `_draft` 指回原始草稿,别绕过这两个函数手拼 `readOnly` / `ssh`。
101
+
102
+ ## 三面布局契约(5.2,`dshModern` 门)
103
+
104
+ 同一个组件的**三个面由 slot + 实测宽度决定**:`settings.section`(弹窗,窄列)、主面板与 `plugins.row.config` 的 `page`(宽屏整页)、`< 700px` 手机小屏(堆叠紧凑、触摸尺寸;`isWide` 阈值为 720)。
105
+
106
+ - **新界面必须挂在 `view.dshModern` 下**;`dshModern === false`(DSH 0.1.x)必须渲染**与旧版逐字兼容的 DOM**(含旧页头 `4 个工具(固定)`、⚙ 设置条、`dbh-checkbox`),否则 `scripts/render-check.mjs` 既有断言会红。旧断言不动,新断言由 harness 另行扩展。
107
+ - 现代面硬约定:
108
+ 1. 页头 = 标题 + 灰色版本号 + 一行「↑ 可升级到 x」文字链接(点击才展开中性说明面板;**禁止红底/红框/error 色**)+ 图标式 ↻;
109
+ 2. 状态行只显示「运行中/已停用 · N 个连接」;工具数 / 一次性模式 / dbhub 版本进页面底部「ⓘ 说明与技术详情」折叠区(默认收起);
110
+ 3. 设置 = **所有非弹窗、非手机页面(宽屏页 / 插件页 / 窄桌面列)一律一行紧凑设置条**(窄列自动加 `.dbh-barnarrow` 隐藏标题与状态词、收窄间距,**不得退回竖排设置行**;弹窗与手机才用竖排)。宽屏条 `.dbh-settingsbar`:标签 + 状态词 + `primitives.Switch` + 更新输入/保存同排,开关 `scale(.85)`;弹窗/手机为紧凑设置行(13px 标题、8px padding、开关 `scale(.8–.9)`)——**字号与控件刻意弱化,连接列表才是主体**。状态词统一为 `启用/停用`、`显示/隐藏`、`开启/关闭`;更新间隔输入 + 天 + 保存必须在同一行不换行;
111
+ 4. 行内只读 chip 在三种布局都出现(见上表);
112
+ 5. 弹窗面的设置收进状态行「设置」折叠,宽屏面直接展开。
113
+
114
+ ## 交互细节(每条都对应一段回归门)
115
+
116
+ - **复制必须给出干净字符串**:连接串拆成两个 flex item 后,浏览器复制序列化会在块级子元素间插分隔符(粘进 DSN 就成 `…3306/ app`)。因此 `connLabelView` 的元素自己接管 `onCopy`(`e.clipboardData.setData('text/plain', w.conn)` + `preventDefault()`)——挂在**连接串元素**而非整行,复制环境名不受影响。`w.conn` 是元数据,不涉凭据。
117
+ - **测试中显示已耗时**:`startTest` 记 `startedAt`,`testing` 态渲染 `test.elapsed`(`测试中… Xs`);1s 计时器**只在有行处于 testing 时存在**(effect 依赖 `anyTesting`,cleanup 清掉)。看门狗按是否隧道分档(30s / 60s),必须高于宿主探测预算(25s / 45s)。
118
+ - **等待态不给二次提交**:`pendingPretest` 期间保存/添加按钮 `disabled`,避免重复派发同一测试。
119
+ - 探测要几秒(spawn dbhub),但**写入结果必须立刻可见**——靠宿主 `publishFast` 即时回推,不要改回「等整轮扫描」。
120
+
121
+ ## 验收
122
+
123
+ - 布局/交互改动的验收 = `npm run check:ui`(离屏渲染 + 点击交互;分段 `[1]`…`[30]`,涵盖模式判定、默认展开、全部展开/折叠、添加面板、复制、i18n 覆盖率、热重载幂等、覆盖门、HTTP 桥、只读/SSH 载荷、现代三面、版本门)全绿 + `node test/client-format.test.mjs`。
124
+ - 已知易错门:`[14]` 重复 locale 注册、`[17]` 复制字符串、`[18]` 无 settingsScope 走桥接 + `configForms` 优先与回退 + 轮询可回收、`[19]` 启用∧开关 / 禁用一律隐藏 / 设置页始终在、`[20]`/`[21]`/`[23]` 只读与 SSH 载荷、`[22]` 残留 interval、`[28]` 手机布局、`[29]` 0.1.x 渲染旧页、`[30]` 窄桌面窗口保持横向设置条。
@@ -0,0 +1,56 @@
1
+ # 脱敏门禁(`npm run check:secrets`)
2
+
3
+ > AGENTS.md 的配套文档:新增示例/默认值/文案/夹具、或门禁报错时读。
4
+ > 一句话红线见 [AGENTS.md](../AGENTS.md#约定)。
5
+
6
+ ## 原则
7
+
8
+ 真实主机 / 账号 / 密码 / 库名 / 项目名只允许存在于两处:
9
+
10
+ 1. `$DSH_HOME/storages/dsh-dbhub-live/credentials.json`(仓库外);
11
+ 2. 本地 `.env` 的 `DSH_DBHUB_DESENSITIZE_RE`(`.gitignore` 排除、`package.json` 的 `files` 不含)。
12
+
13
+ **仓库内一切内容都视为会公开分发**(含 `test/`——它被打进 npm tarball——以及工具描述文案)。这些值一旦公开即视为泄露、无法追回,所以门禁是硬门而不是提示。
14
+
15
+ ## 三层规则
16
+
17
+ 门禁由 `scripts/desensitize-check.mjs` 执行,规则与槽位来自 `scripts/desensitize.config.mjs`。
18
+
19
+ 1. **槽位规则(主力,高精度)**:槽位表登记"真实值可能出现的出口"(宿主文案、浏览器文案、连接构造代码、扫描器、测试夹具、文档示例、包元数据、本提示词、维护脚本),每个槽位声明它需要的规则(`password` / `dsn` / `host` / `name` / `localpath`)。**槽位 glob 必须匹配到文件,否则门禁报 `slot-stale`**——槽位表不会随目录移动悄悄失效。
20
+ - `localpath` 只挂在**文档与提示词类槽位**(`docs` / `package-meta` / `agent-prompt`):真实机器路径(Windows 盘符绝对路径、用户 home 目录下的路径)不得出现在会被公开分发的文案里;测试夹具允许用合成盘符路径,故 `tests` 槽位不启用它。
21
+ 2. **通用兜底(覆盖未登记的新出口)**:
22
+ - `ip`:只允许回环、`0.0.0.0` 与 RFC 5737 文档段 `192.0.2.x` / `198.51.100.x` / `203.0.113.x`;
23
+ - `secret`:GitHub / npm / OpenAI / Slack / AWS / JWT / PEM 等真凭据格式;
24
+ - `entropy`:大小写 + 数字混排的高熵串;
25
+ - `host`:`.local` / `.internal` / `.corp` 等内网后缀;
26
+ - `name`:本地 denylist。
27
+ 3. **本地 denylist(补充)**:pattern 抓不到的**裸名字**(项目 / 公司 / 库名)放在 `.env` 的 `DSH_DBHUB_DESENSITIZE_RE`(`|` 分隔、正则转义、多条取并集)。CI 里没有 `.env`,该层自动降级为提示,不影响其余两层。
28
+
29
+ `SKIP_DIRS` 跳过 `.git`、`node_modules`、`.dsh-test`、`.release-test`、`dist`、`coverage`;两个本地开发文档(`doc/PLAN-*.md`、`doc/implementation-notes.md`)不进 git 也不进包,因此不扫。
30
+
31
+ ## 写法约定(新增内容一律照此)
32
+
33
+ | 场景 | 写法 |
34
+ | --- | --- |
35
+ | 示例主机 | `127.0.0.1` / `localhost` / RFC 5737 段 |
36
+ | 示例密码 | `CHANGE_ME` |
37
+ | 测试夹具 | 短占位可以(`u:p`、`root:secret`),但主机仍须回环或保留段 |
38
+ | 误报 | 行内标记 `desensitize:allow`(仅限确认无害的行) |
39
+ | 本地路径 | 用 `<repo>` / `<dsh 安装目录>` 占位,**不写任何人的绝对路径** |
40
+
41
+ ## 义务(硬规则)
42
+
43
+ - 新增功能若引入示例、默认值、文案串或夹具 → **同一次改动里更新 `scripts/desensitize.config.mjs` 槽位表**;
44
+ - 提交前跑 `npm run check:secrets`,命中就改占位符或换规范值,不要用 `desensitize:allow` 掩盖真实值;
45
+ - 发现新的真实值 → **只追加到本地 `.env` 的 `DSH_DBHUB_DESENSITIZE_RE`**,绝不写进任何受版本控制的文件。
46
+
47
+ 执行点:本地手动 + `prepublishOnly` + CI(`.github/workflows/quality.yml`)。
48
+
49
+ ## 零知识自检(模型可见面不得含密码)
50
+
51
+ ```bash
52
+ node test/zero-knowledge.test.mjs # 全绿
53
+ npm run check:secrets # 零匹配
54
+ ```
55
+
56
+ 两者都过才算干净。
@@ -0,0 +1,139 @@
1
+ # 开发与调试(按需加载)
2
+
3
+ > AGENTS.md 的配套文档:怎么在不弄坏正在运行的 Harness 的前提下验证改动。
4
+ > 本文件只讲**操作**;红线与架构约束见 [AGENTS.md](../AGENTS.md)。
5
+
6
+ DSH 启动是 fail-loud:任一插件激活失败整树拒绝启动、GUI 打不开。所以**永远不要在配置或源码上直接动你正在使用的主实例**,按下面的阶梯来。
7
+
8
+ ## 1. 静态校验(30 秒,零风险)
9
+
10
+ ```bash
11
+ npm run check # node --check 全部 lib
12
+ ```
13
+
14
+ 单测按**文件逐个跑**——沙箱里 `node --test` 的 runner 子进程会被 EPERM 挡住:
15
+
16
+ ```bash
17
+ node test/<file>.test.mjs
18
+ ```
19
+
20
+ 单测自己会建临时 `DSH_HOME`,不会碰真实 store。
21
+
22
+ ### 只读 / 隧道路径的静态旁路验证(不需要 Harness)
23
+
24
+ ```bash
25
+ node -e "…" # 调用 buildDbhubToml({dsn, ro:true, ssh:{…}}) 打印文本
26
+ ```
27
+
28
+ 确认三件事:**两条 `[[tools]]` 都在**、文本里没有密码、`env` 覆盖了文本里引用的每个 `${VAR}`。`test/toml.test.mjs` 已自动断言,手工复核只用于排查现场。
29
+
30
+ ### 真 dbhub 冒烟(不经插件)
31
+
32
+ 把生成的 TOML 落到临时目录,直接跑:
33
+
34
+ ```
35
+ dbhub --transport stdio --config <file>
36
+ ```
37
+
38
+ 喂 `initialize` / `tools/list` / `tools/call`。命令见本地文档 `doc/PLAN-5.1-readonly-and-ssh-tunnel.md` 附录 A(该文件 gitignore 排除、不进包)。预期:
39
+
40
+ - 只读环境 `INSERT` / `CREATE` 回 `READONLY_VIOLATION`;
41
+ - `SELECT 1` 与 `search_objects` 成功(**只写一条 `[[tools]]` 会让 `search_objects` 消失**);
42
+ - `--config` 与 `--dsn` 同时给必 fatal。
43
+
44
+ ## 2. 会话内跑通逻辑(不重启)
45
+
46
+ 用动态插件工具 `cordis_define` / `cordis_run` / `cordis_stop` / `cordis_undefine`:把纯逻辑(configure 流程、`scrubSecrets` 等)以**无 import** 的 Host 代码贴进动态包跑,`cordis_stop` 即清场;只活在内存 + 当前会话,不影响主进程。
47
+
48
+ 限制:动态 Host 代码不能用 `import`,带 `@deepseek-ai/schemastery` 的 `lib/index.mjs` 不能整文件贴。只做纯逻辑验证。
49
+
50
+ ## 3. 冷启动验证(隔离实例)
51
+
52
+ 用独立 `DSH_HOME` 起测试实例。约定用**仓库外的独立目录**(例如 workspace 根下的 `.dsh-a` / `.dsh-b`)作为隔离 home;唯一硬要求是**绝不指向真实 home**。
53
+
54
+ ### 路径 A:源码实例(走 tsx)
55
+
56
+ ```bash
57
+ cd <dsh 源码 checkout>
58
+ mise r dsh web --port 3082 --no-open
59
+ ```
60
+
61
+ - `pnpm dsh` 会先跑依赖校验;node_modules 不同步时它想重装并因无 TTY 中止——直接 `node --import tsx/esm apps/cli/src/bin.ts …` 跳过。
62
+ - esbuild spawn 在沙箱下 EPERM,属于沙箱边界,别绕。
63
+
64
+ ### 路径 B:已安装实例(推荐,无 esbuild / pnpm)
65
+
66
+ ```powershell
67
+ $env:DSH_HOME = "<repo>/.dsh-a" # 隔离 home
68
+ Remove-Item Env:DSH_PROFILE, Env:DSH_PROFILE_DIR -ErrorAction SilentlyContinue # 继承主 profile 会污染/参数冲突
69
+ node <dsh 安装目录>/lib/bin.js web --port 3082 --no-open
70
+ ```
71
+
72
+ `dsh web` 自带 `web` profile,**不要再传 `--profile web`**(会报「select a profile only once」)。
73
+
74
+ ### 装包进隔离实例
75
+
76
+ ```powershell
77
+ # pnpm 需要可写 store(默认 store 在 workspace 外会被沙箱拒绝)
78
+ $env:npm_config_store_dir = "<repo>/.pnpm-store"
79
+ pnpm pack
80
+ node <dsh>/lib/bin.js plugin --profile web add <打包目录>/dsh-dbhub-live-<ver>.tgz
81
+ ```
82
+
83
+ 沙箱内 pnpm 可能因为事先被别处 store 链接过而报 `UNEXPECTED_STORE`;此时可手工替换 `.dsh-a/profiles/web/node_modules/dsh-dbhub-live/` 的目录内容(`file:` 依赖就是普通目录,不写 store、不动 lockfile)。**仅用于验证,不要当成发布流程。**
84
+
85
+ ### 验证清单(0.2.x)
86
+
87
+ 日志应出现:
88
+
89
+ ```
90
+ [dsh-dbhub-live] host apply
91
+ dbhub 插件已加载(4 个工具)
92
+ 模型侧访问策略已注册(system prompt 段 …)
93
+ 浏览器桥接已注册: /api/dsh-dbhub-live/*
94
+ ```
95
+
96
+ 接口(从启动日志取 `?token=…` → `GET /?token=…` 拿会话 cookie):
97
+
98
+ | 请求 | 期望 |
99
+ | --- | --- |
100
+ | `GET /api/dsh-dbhub-live/state` | 200 + `{ok,value}`;**不带 cookie 必须 401** |
101
+ | `POST /api/dsh-dbhub-live/options {"updateIntervalDays":3}` | 200,且 `$DSH_HOME/storages/dsh-dbhub-live/prefs.json` 落盘 |
102
+ | `POST /api/dsh-dbhub-live/op {"op":{"op":"nope"}}` | 200 + `patches: []` |
103
+
104
+ volatile Config 通道(0.1.7+ / 0.2.x):在 profile 的 `cordis.patch.yml` 里钉 `- id: <行 id>` + `config: {updateIntervalDays: 5}` → 重启后视图与 `prefs.json` 都变 5(采纳),日志出现「配置表单策略已注册: auto=false」;再 `POST /options {"updateIntervalDays":9}` → **profile patch 文件本身**应被改写成 9,视图里的 `entryId` 等于该行 id(不是包名)。
105
+
106
+ 客户端包 URL 必须带 `rev`:
107
+
108
+ ```
109
+ /plugins/??dsh-dbhub-live/client.js&rev=<rev> # 200(单斜杠路径一律 404)
110
+ ```
111
+
112
+ **取 bundle URL 的正确方式**:index HTML 里的模块清单 JSON(`{"id":"dsh-dbhub-live","url":"/plugins/??…&rev=<rev>"}`),`rev` 带 `-<n>` 后缀(如 `d4aeeae7c6c2a859-55`,每次渲染会变)。用正则截 `rev` 会截断后缀而 404——必须整段取出该 `url` 再请求。
113
+
114
+ 浏览器:`http://127.0.0.1:3082` 侧边栏出现「DBHub 数据库工具」、设置里出现同名一级页面,行内显示连接元数据。
115
+
116
+ ### 0.1.x 线(双栈的另一半)
117
+
118
+ 另起一个 scratch home 装 0.1.6 的 CLI 即可,`dsh web` 会自己初始化 web 模板:
119
+
120
+ ```powershell
121
+ # 用 0.1.6 的 CLI 初始化 DSH_HOME=<repo>/.dsh-b,把包目录拷进 profiles/web/node_modules/
122
+ # 并在 package.json 的 dsh.profile.bundles 里加上 dsh-dbhub-live(bundle 的 patch 会自行插入插件行)
123
+ ```
124
+
125
+ 期望日志:`host apply` + `dbhub 插件已加载(4 个工具)` + `状态命名空间已注册: dsh-dbhub-live` + `浏览器桥接已注册: …`(0.1.6 没有 `settings.configure`,故没有「配置表单策略」那行)。期望接口:`/api/dsh-dbhub-live/state` 带 cookie 200、不带 401;view 里 `entryId` = 行 id;bundle 200。
126
+
127
+ `mise r dsh plugin … ls` 只用于核对安装版本。
128
+
129
+ ### 桌面端
130
+
131
+ **不做本地自动验证**——由用户经可视化「插件」页安装/更新/卸载后人工确认(见 README「更新」)。
132
+
133
+ ## 4. 在位热更新(有失败保护,谨慎)
134
+
135
+ 主实例的 `cordis.patch.yml` 支持 HMR,读取/解析失败时保留最后一个可用树,GUI 不会挂。但 **HMR 不监听插件源码**,只适合挂载/卸载验证;代码迭代用上面的 1 / 2 / 3。
136
+
137
+ ## 5. 安全网
138
+
139
+ `$DSH_SNAPSHOT=replay` 可从 `cordis.snapshot.yml` 启动回放点。改动主 profile 前先备份 `cordis.patch.yml`。
@@ -0,0 +1,36 @@
1
+ # 变更影响矩阵(按需加载)
2
+
3
+ > AGENTS.md 里只留一句提醒:**改任何东西前,来这里核查一遍联动项**。原因与判断依据在 [ARCHITECTURE.md](ARCHITECTURE.md),操作步骤在 [DEVELOPMENT.md](DEVELOPMENT.md) / [CLIENT.md](CLIENT.md) / [RELEASE.md](RELEASE.md) / [DESENSITIZE.md](DESENSITIZE.md)。
4
+
5
+ ## 怎么用
6
+
7
+ 1. 按「改动」列找到你正在动的那一类,再看「同次必须更新」——**同一提交里补齐**,不要留到下次;
8
+ 2. 跑「自动门禁」列的命令;没有门禁的项靠人工复核;
9
+ 3. 出现了新的联动关系(第 11 类)→ **本表同次加行**。这是一张受管文档,不是备忘。
10
+
11
+ ## 矩阵
12
+
13
+ | 改动 | 同次必须更新 | 自动门禁 |
14
+ | --- | --- | --- |
15
+ | lib 红线语义(零知识 / TOML / source 只读 / 状态通道 / 连接管理) | AGENTS.md 对应条 + `doc/ARCHITECTURE.md` 对应节 + 相关 `test/*.test.mjs` + `doc/REQUIREMENTS.md`(涉需求时) | `npm run check` + 单测 |
16
+ | 模型可见文案 / 工具描述 / i18n | `test/zero-knowledge.test.mjs` + `scripts/desensitize.config.mjs` 槽位表 + `README.md` 与 `README.en.md`(用户可见时) | `check:secrets` + 零知识单测 |
17
+ | 配置页布局 / 交互 / 文案 | `doc/CLIENT.md` + `scripts/render-check.mjs` 断言 + AGENTS.md「客户端半」 | `check:ui` + `test/client-format.test.mjs` |
18
+ | 调试方式 / 隔离实例 / 验证清单 | `doc/DEVELOPMENT.md` | 无(人工) |
19
+ | 发布流程 / 版本号 / 支持矩阵 | `doc/RELEASE.md` + `release-test/matrix.json` → `npm run support:matrix` 重生成 `doc/SUPPORT-MATRIX.md` + `package.json` 的 `dsh.compatibility.dshReleases` | `support:check` + `release:preflight` |
20
+ | 脱敏规则 / 槽位 / 示例值 / 测试夹具 | `scripts/desensitize.config.mjs` + `doc/DESENSITIZE.md` | `check:secrets` |
21
+ | npm 脚本 / 包内容(`files`、`exports`、`dsh.client`) | `package.json` + AGENTS.md「质量门」+ `doc/RELEASE.md`「包内容」 | `check:docs` |
22
+ | 文档新增 / 改名 / 调整章节(含 `doc/` 与 `AGENTS.md`) | AGENTS.md 里的指针 + **本表** + `package.json` 的 `files`(需随包分发时) | `check:docs` |
23
+ | 需求条目(R 编号) | `doc/REQUIREMENTS.md` + AGENTS.md(有行为约束时) | 人工 |
24
+ | 用户可见行为 / 界面文案 | `README.md`(唯一真源)→ 同次派生 `README.en.md` + `doc/REQUIREMENTS.md` | `check:secrets` |
25
+ | 新增/调整联动关系(即本表) | 本表加行 + AGENTS.md 的提醒指向不变 | `check:docs` |
26
+ | 部署开关(`lib/options.mjs`) | 配置页开关 + `STATUS_DEFAULTS` / `STATUS_SCHEMA` / `Config` volatile + `test/options.test.mjs` | 单测 + `check:ui` |
27
+
28
+ ## 门禁只保证什么
29
+
30
+ `npm run check:docs` 机械校验的是**提醒本身不烂**:
31
+
32
+ - AGENTS.md 与各文档的链接、锚点真实存在;
33
+ - 本表引用的每个仓库路径真实存在;
34
+ - AGENTS.md 指向 `doc/` 的文档都随 npm 包分发(`files`)。
35
+
36
+ 它**不能**判断内容是否真的同步了——那仍靠评审时按本表逐项核对。
package/doc/RELEASE.md ADDED
@@ -0,0 +1,93 @@
1
+ # 发布与版本(按需加载)
2
+
3
+ > AGENTS.md 的配套文档:打 tag、发 dev/正式版、改版本号、跑版本矩阵时读。
4
+ > 质量门命令与脱敏红线见 [AGENTS.md](../AGENTS.md);脱敏细则见 [DESENSITIZE.md](DESENSITIZE.md)。
5
+
6
+ ## 发布测试门(`release-test/`,每个声明版本都实测 web 端)
7
+
8
+ **支持哪些 DSH 版本不靠记忆**:`release-test/matrix.json` 是唯一声明,`doc/SUPPORT-MATRIX.md` 是由证据生成的表,`npm run release:preflight` 是发版闸门。
9
+
10
+ ```powershell
11
+ mise -C release-test install # 一次性预装矩阵里的全部 DSH 版本(不激活;没 mise 可跳过,测试会自动回退 npm)
12
+ npm run test:release # declared 矩阵:钉版 CLI + 隔离 DSH_HOME + 真实 dsh web + HTTP/日志探针
13
+ npm run test:release:all # 连 advisory 观察项一起跑
14
+ node release-test/run.mjs --only 0.2.1-rc.2 # 只跑一个版本(调试用,不能当发版依据)
15
+ npm run support:matrix # 重新生成 doc/SUPPORT-MATRIX.md(矩阵或证据变了就重生成)
16
+ npm run support:check # 校验文档与 matrix/最近证据一致(CI 用)
17
+ npm run release:preflight # 闸门:报告新鲜 + tree 一致 + 矩阵未变 + 声明无偏差 + AI 判定通过 + 工作区干净
18
+ ```
19
+
20
+ - 产物:`.release-test/reports/<run>/` —— `report.md`(人看)、`report.json`(机器)、`judge-prompt.md`(给 AI)、`evidence/<版本>/`(安装/启动日志与探针回执)。**`.release-test/` 不进 git**(`.gitignore` 已排除,脱敏门禁 `SKIP_DIRS` 已跳过)。
21
+ - **AI 判定是流程的一步,不是可选项**:按 `judge-prompt.md` 复核证据 → `node release-test/run.mjs verdict --template --run <dir>` 生成骨架 → 逐版本写结论、逐条回答 open question → `npm run release:verdict`(即 `verdict --check`)校验(硬失败不能判成 pass,报告哈希必须对得上)。
22
+ - 结果解读:`fail` = 硬失败,禁止发版;`warn`/`skip` = 进 open questions 由 AI 判定(例如沙箱里被迫手工装配);`claim drift` = 声明与 manifest 矛盾(典型:peerDependencies 范围排除预发布版)——**报告照跑,但闸门会拦住发版**。
23
+ - 增删支持版本:只改 `release-test/matrix.json`,再 `npm run support:matrix` 重生成文档;`package.json` 的 `dsh.compatibility.dshReleases` 必须与矩阵一致(`node release-test/run.mjs matrix` 会列出偏差)。
24
+ - 隔离纪律:每个版本都在 `.release-test/homes/dsh-<版本>/` 里跑,**永不碰用户真实 profile**(主实例 fail-loud)。CLI 一律钉版。
25
+ - **CLI 来源**:默认 `--cli-source auto`——mise 里预装了该版本就用 `mise x npm:@deepseek-ai/dsh@<版本> -- dsh …`(不激活),否则回退 `.release-test/cache/tools/` 的 npm 一次性安装;报告里记录每个 target 实际用了哪种。预装清单 `release-test/mise.toml` 由矩阵生成,勿手改。确认 mise 装齐后可 `node release-test/run.mjs clean --tools` 回收 npm 缓存(每版本约 470 MB)。
26
+
27
+ ## 分支职责
28
+
29
+ - `dev`:**集成/测试分支**。所有改动先落这里(直接提交,或功能分支 PR 进 dev);允许 force push;CI(`gate`)在这里跑。
30
+ - `main`:**只用于发布**。已开分支保护:必需检查 `gate`、禁止 force push、禁止删除。改动只能从 `dev` 经 PR 合入,main 上不再有其它直接提交。
31
+ - 每次发布后 `dev` 与 `main` 收敛:`git checkout dev && git merge --ff-only main`。
32
+
33
+ ## 触发词与硬约束(最重要,别越界)
34
+
35
+ - 用户说「**可以了,发布**」(或类似)= **发 dev 测试版**。此时**绝不**动 `main`、**绝不**执行默认(`latest`)的 `npm publish`。
36
+ - 只有用户明确说「**合并主分支发布正式版**」(或等价明确指令)时才走正式版流程;并且**正式版的 npm 发布命令只输出给用户手动执行**——代理不代发(用户自己登录拿凭据)。仓库里没有 `NPM_TOKEN` secret,CI 也不会替你发。
37
+ - **已发布的 tag 与 npm 版本绝不移动 / 删除 / unpublish**;发现缺陷就发下一个补丁版本(发布出去的东西收不回来)。
38
+ - 测试版**必须** `--tag dev`(若发 npm)+ GitHub `--prerelease`;只有正式版才能用 `--latest` 与默认 dist-tag,否则 `latest` 会被指到测试版,普通用户直接中招。
39
+ - 只有 CI(`gate`)绿的提交才能进 `main`;`pnpm pack` 的产物名必须与 `package.json` 版本一致。
40
+
41
+ ## 阶段一:测试版(在 dev 上)
42
+
43
+ ```powershell
44
+ git checkout dev && git pull
45
+ # 改代码;package.json 版本 = <目标正式版>-dev.<N>(N 不复用,如 5.3.0-dev.1)
46
+ npm run check; npm run check:secrets; npm run check:ui # 再按文件逐个跑单测
47
+ $env:npm_config_store_dir = "<repo>/.pnpm-store"; pnpm pack # dsh-dbhub-live-<ver>.tgz
48
+ git add -A; git commit -m "..."; git push origin dev # CI 在 dev 上跑 gate
49
+ # ⚠ 发版闸门(必跑):在干净提交上跑矩阵 + AI 判定,preflight 不过不许打 tag
50
+ npm run test:release; npm run release:preflight
51
+ git tag -a v<ver> -m "..."; git push origin v<ver>
52
+ gh release create v<ver> --prerelease --title "v<ver>" --notes-file <notes> dsh-dbhub-live-<ver>.tgz
53
+ # 可选(只在需要验证 npm 安装路径时):npm publish --registry=https://registry.npmjs.org/ --tag dev
54
+ # 安装验证(不碰 npm 也行):dsh plugin --profile web add <tarball>(或 dsh-dbhub-live@dev)
55
+ ```
56
+
57
+ 预发布不会抢 Release 页的 "Latest" 徽章,普通用户 `dsh plugin add dsh-dbhub-live` 仍拿正式版。
58
+
59
+ ## 阶段二:正式版(仅在用户明确要求时)
60
+
61
+ ```powershell
62
+ # 1) 在 dev 上把版本号转正(去掉 -dev.N),提交 "vX.Y.Z (release): ...",push dev
63
+ # 2) 开 PR 合入 main,等 CI 绿后合并
64
+ gh pr create --base main --head dev --title "vX.Y.Z (release): ..." --body "..."
65
+ gh pr merge --merge
66
+ # 3) main 的合并提交也要过闸门(tree 相同则复用 dev 的报告,否则重跑),然后打 tag
67
+ npm run release:preflight
68
+ git checkout main && git pull; git tag -a vX.Y.Z -m "..."; git push origin vX.Y.Z
69
+ # 4) GitHub 正式发布(--latest,附 tarball)
70
+ gh release create vX.Y.Z --latest --title "vX.Y.Z" --notes-file <notes> dsh-dbhub-live-X.Y.Z.tgz
71
+ # 5) npm:把命令交给用户手动执行(代理不代发)
72
+ # npm publish --registry=https://registry.npmjs.org/ # 不带 --tag → latest 指向它
73
+ ```
74
+
75
+ **给代理的默认行为**:本仓库里代理只往 `dev` 提交/推送;`main` 只通过 PR 合入。需要动 `main` 或发 npm 时先停下来向用户确认。
76
+
77
+ ## 版本号策略(测试版本 → 发布版本)
78
+
79
+ - **基线 = 代码库当前版本**(`package.json` 版本,即最近一次发布)。测试/调试迭代一律在基线上挂 `-dev.<序号>`,序号从 1 递增、不复用,`pnpm pack` 产物随之换名。
80
+ - 为什么要升版本:pnpm 对 `dsh plugin add <同名同版本 tarball>` 会判「Already up to date」跳过、**不换包**,只有换版本号才能保证调试包真正部署。
81
+ - `dsh plugin add` 后务必用 `dsh plugin ls` 或核对 `node_modules/<pkg>/package.json` 的版本号确认已替换。
82
+ - **测试完成要发版时,在基线上按「跨度和力度」决定升级幅度**(不以 dev 序号原号发布):
83
+ - 小 bug 修复 / 小功能改动 → 升**补丁号**(`5.2.0` → `5.2.1`);
84
+ - 大功能新增 / 影响面较广 → 升**次版本号**(`5.2.0` → `5.3.0`);
85
+ - 破坏性升级 / 阶段性架构变更 → 升**主版本号**(`5.2.0` → `6.0.0`)。
86
+ - 跨档并存时按最高档计;归属拿不准时**先向用户确认再发版**,禁止未确认跨度直接升主版本。
87
+ - **发布版本成为新基线**,下一轮测试从 `<新基线>-dev.1` 重新起序。
88
+
89
+ ## 包内容
90
+
91
+ `package.json` 的 `files` 决定 npm 包内容:`lib`、`doc/REQUIREMENTS.md`、`doc/ARCHITECTURE.md`、`doc/DEVELOPMENT.md`、`doc/CLIENT.md`、`doc/RELEASE.md`、`doc/DESENSITIZE.md`、`doc/IMPACT-MATRIX.md`(七份文档供包内 AGENTS.md 链接)、`doc/images`、`test`、`scripts`、`cordis.patch.yml`、`README.md`、`README.en.md`、`AGENTS.md`、`LICENSE`。本地文档(`doc/PLAN-*.md`、`doc/implementation-notes.md`、`doc/RELEASE-NOTES-*.md`)被 `.gitignore` 排除,**永不进 git / npm 包**;`doc/SUPPORT-MATRIX.md` 是生成物,不随包分发。
92
+
93
+ `prepublishOnly` = `npm run check:docs && npm run check:secrets && npm run release:preflight`——发布前三道门自动跑。