dsh-test-framework
面向测试工程师的 DSH 插件:把一个空文件夹变成你自己的「测试工作框架」,并在 AI 会话里
自动带上框架约束、把「需求文档 → 子模块拆分 → 版本 / 跨文档关联 / 测试环境账密」这条链
做成绕不过去的闸门。
- 宿主是 cordis 插件,零第三方依赖(只用 Node 内置模块),无前端构建步骤
- 插件版本:
0.2.9 | 适配:DSH(dsh.engines.dsh = "*")| Node ≥ 20
〇、前置条件(装之前先确认)
| 外部 MCP | 必需? | 没有会怎样 |
|---|
playwright MCP(mcp__playwright__browser_*) | ✅ 必需,且没有替代路径 | 「看着页面操作流程」这类验证做不了。框架铁律 10 要求「必须真实浏览器 + 真实点击/输入」,明确不许用只读 DOM / 只抓报文 / 离线脚本 / 旧截图冒充 —— 所以缺它不是"降级运行",而是"这条要求根本执行不了"。 插件会在会话开头的「测试框架状态」段落和 test_framework_status 输出里报「未检测到 playwright MCP(必需)」;看到就先配好再开工 |
keyword_mcp(接口报文落盘 / toast 历史) | 建议 | 少一条取证通道(与 playwright 互补不替代) |
cr69_mcp(Chromium 69) | 可选 | 老内核 / 小视口兼容性验证做不了 |
配在哪:DSH profile 的 cordis.patch.yml —— 加一个 mcp-playwright 服务(serverName: playwright,
命令用 @playwright/mcp),并且必须有头(窗口可见),否则"看着"是假的。
⚠️ 改完必须重启 DSH(profile 层不热加载)。
自查:
# npm 安装的用户
npx dsh-test-framework doctor
# 在测试框架里(插件源码在 _plugin\ 下)
node _plugin\dsh-test-framework\lib\cli.js doctor
doctor 会把这三项逐个列出来("已检测到" / "未检测到" / "无法判定")。
⚠️ 口径说明:插件是靠读 profile 的 cordis.patch.yml 来判断的,"未检测到"≠"一定不存在"
(你也可能用别的方式起这个 MCP)—— 真查不到时它会说"无法判定",不会硬下结论。
一、它解决什么问题
用 DSH 做需求测试时,反复出问题的是这几件事 —— 它们不是能力问题,是纪律问题:
| 痛点 | 本插件的处置 |
|---|
| 新建一个文件夹就要手工搭一遍框架骨架(AGENTS.md / skills / _templates / 产品级规范 / 目录联接) | test_framework_init 一条命令生成,已存在即跳过、不覆盖 |
在 <产品>\<模块>\ 这种子目录里开会话,框架根 AGENTS.md 静默不加载(DSH 只认 .git 当项目根) | 初始化时建 .git 标记;并且插件另注入一段实时状态,没加载成也会明说 |
| 120 页的需求文档含 3 个子模块,AI 自己就拆了或干脆不拆 | 探针给页数/章节/菜单命中 → 命中判据就必须用 ask_user_question 让用户确认,不许自作主张 |
| 某个业务逻辑其实写在另一份需求文档里,没人问过 | 闸门 crossDocChecked:必须问过(答"没有"要落 relatedDocs: [],缺 key = 没问过) |
| 需求文档路径不区分版本,测错版本全白干 | 闸门 versionsConfirmed,并在流程里写死"不许用文件时间判版本" |
| 每次都要重新问一遍环境地址、账号密码、数据库地址 | test_framework_record 落产品级规范(人读的 md + 机器读的 json),下次直接命中 |
| 密码散落到脚本和报告里 | 明文只落 _框架配置\环境与账密.md,框架登记.json 里只记指针(有测试守着) |
报告越堆越多,模块 report\ 里几个月前的报告还混在最新一批里 | test_framework_report_gc:满 24h 的报告移进 _archive\报告归档\<模块>\<日期>\(归档不是删除,台账记着原名与日期);agent 入口默认只预演 |
| 报告写成"密密麻麻一大长篇",看不出有几条、最严重的是哪条、差在需求哪一句 | AGENTS.md 铁律 4 + 测试报告模板硬性要求:结论先行 + 缺陷速览表在最前 + 一条缺陷一小节(≤20 行)+ 复现步骤可照做 + 每条写「需求原文依据」与「与需求不符点」 |
一个模块测了好几版,几版的口径/造数逻辑/坑全揉在一份 SKILL.md 里,用的时候不知道哪句对哪版 | 模块 SKILL.md 降为索引,每版一份 skill\<版本>\SKILL.md,一版一份、多版并存、互不覆盖;test_framework_module_add 传 version 就自动建好并回填索引 §8 版本索引 |
别的项目 / 别的工具的规矩渗进本框架 —— 它的"落盘必须在自己的工作区内"变成了本框架的规矩,于是模块里被建出一批永远装不进东西的 temps\ 空壳、模块 SKILL.md 里留下"照着做必然失败"的经验 | lib\isolation.js:① 会话开头先说清当前会话的项目根是谁、会加载哪份 AGENTS.md(嵌套仓库截断 / 找不到 .git 退回 cwd / 项目根跑到框架外,三种都认);② 模块里出现外部工具的工作目录就报,并写明正确做法(落它自己的工作区 → 证据拷回模块 data\);③ doctor 一并列出周边外部权威来源(别处的 、 规范库) |
二、装插件
路线 A:从 npm 装(别人用这条)
# DSH 插件就是 npm 包 —— `dsh plugin ...` 本身就是转发给 pnpm
dsh plugin --profile desktop add dsh-test-framework
# 重启 DSH(profile 的 bundles 层与 cordis.patch.yml **不支持热加载**,不重启不生效)
装完看会话里有没有 test_framework_status 工具,或跑:
npx dsh-test-framework doctor --profile desktop
路线 B:从源码装(改代码时用这条)
cd <插件目录>
# ① 看计划(不写盘)
node lib\cli.js install --profile desktop
# ② 真装(链接安装:`link:` + 目录联接,不跑 pnpm)
node lib\cli.js install --profile desktop --apply
# ③ 重启 DSH
自检:
node lib\cli.js doctor --profile desktop
doctor 会逐项报:插件清单 / 入口 / cordis.patch.yml / Node 版本 / 找不找得到 dsh 与 pnpm /
这个 profile 里依赖与 bundles 两处是不是都有(只有一处 = 半装 = 不会加载)。
可选配置
# profile 的 cordis.patch.yml —— 把需求文档总根目录固定下来
- id: test-framework
config:
reqDocRoot: '<你的需求文档总根目录>'
不配也可以:省略时按「插件配置 → 环境变量 DSH_REQ_DOC_ROOT → <用户主目录>\需求业务文档」解析。
插件里没有写死任何绝对路径(有测试守着这一条)。
默认为什么不跑 dsh plugin add
默认走的是「链接安装」:备份 package.json → 给 dependencies 和 dsh.profile.bundles
各加一条 → 在 node_modules 里建一个目录联接。link: 依赖的本质就是这个联接,
所以这条路和 pnpm 的产出等价,但不碰整棵 node_modules。
dsh plugin add 会在 profile 目录里真跑一次 pnpm install。实测本机 desktop profile 的
node_modules\@deepseek-ai 处于两个 dsh 版本混链的状态(profiles\node_modules 下
0.1.1-rc.2 与 0.1.5-rc.3 混着指),一次 pnpm install 很可能把这棵树"规范化"掉、
连带影响正在运行的 DSH。装一个小插件不值得冒这个险。
要用官方命令(或补跑 pnpm):
# 官方命令:会真跑 pnpm install
node ...\cli.js install --profile desktop --apply --official
# 链接安装 + 之后再补跑一次 pnpm install
node ...\cli.js install --profile desktop --apply --pnpm
关于本机有多个 dsh
本机 npx 缓存里有 4 个 dsh(0.2.0-rc.2 / 0.1.7-rc.1 / 0.1.5-rc.3 / 0.1.1-rc.2),
而 profile 的 node_modules 是混链的。所以 doctor / install 挑 dsh 的顺序是
PATH → 这个 profile 自己链的那个 → npm 全局 → npx 缓存(版本降序),并把候选全列出来;
要指定就 --dsh <dsh.cmd 路径>。
(找 profile 自己链的那个时要查两层:<profile>\node_modules 与
<dshHome>\profiles\node_modules —— 本机 @deepseek-ai\dsh 挂在上一层,只查自己那层会挑错版本。)
怎么确认真装上了:重启后新开一个会话,看 skill 列表里有没有 test-framework-onboard、
工具列表里有没有 test_framework_status;或直接说一句"看下测试框架状态"。
(本次装到 desktop profile 后无需重启就已生效:会话的 skill 目录里已出现 4 个
test-framework* skill,dsh --profile desktop --dump-config 里能看到 - id: test-framework。)
卸载
node lib\cli.js uninstall --profile desktop
只摘掉 profile 的依赖与 dsh.profile.bundles 条目(改前一定先备份成 package.json.bak-testfw-<日期>),
并摘掉手动建的那个目录联接(不然会留下一个指向已卸载插件的死链接)。
不动框架工作区里的任何文件 —— 卸载插件 ≠ 删框架。
三、装完能得到什么
3.1 七个工具
| 工具 | 干什么 |
|---|
test_framework_status | 开工前先调它。返回框架根、产品与模块、登记到哪一步、还缺哪些项。ready: false 时不许直接开测 |
test_framework_init | 把(空的)文件夹初始化成框架骨架。dryRun: true 只看计划 |
test_framework_module_add | 新增一个模块(= 系统里的一个叶子菜单;父菜单不建目录、只作菜单路径):建 requirement/final/report/data/skill + 渲染模块索引 SKILL.md + 传 version 时再建 skill\<版本>\SKILL.md(一版一份,互不覆盖)并回填索引 §8 版本索引,最后同时回填框架根 AGENTS.md §三 与产品级 模块路由.md |
test_framework_link | 跨端关联的只读体检(不改任何文件):查 关联边两端模块是否存在 / 单向边(只有 A→B 没有 B→A)/ 链路断点 / 端前缀命名(默认端不带前缀、非默认端必须带 <端>-)/ 非默认端没写逻辑业务对象 / 路由漂移 / 模块登记落点缺失。返回结论行 + 错误清单 + 摘要(模块数 / 关联边数 / 链路数 / 各端模块数);命令行等价入口 node lib\cli.js link |
test_framework_doc_probe | 零依赖探一份 docx:页数(优先 Word 自己记的 Pages)、章节树、表格、图片数、带删除线的段落数、疑似子模块、命中的顶级菜单,以及拆分判定 |
test_framework_report_gc | 报告回收:把满 N 小时(默认 24)的报告从模块 report\ 移进 _archive\报告归档\<模块>\<日期>\。默认 dryRun: true 只预演,确认后传 dryRun: false 才真搬。它不重写归档逻辑 —— 只是去找并调用框架里那份 archive_reports.py,所以不存在"两套规则打架" |
3.2 四个 runtime skill(自动加载)
| skill | 什么时候命中 |
|---|
test-framework | 测试框架总契约:三层结构、目录约定、12 条铁律摘要、闸门表 |
test-framework-init | 「初始化测试框架」「把空文件夹变成测试框架」 |
test-framework-onboard | 「测这份需求文档」「新版需求」「要不要拆」「测试环境地址/账号密码」 —— 这就是那条强制引导流程 |
test-framework-module | 「新增模块」「这个功能算哪个模块」 |
⚠️ runtime skill 的 rank 是 250;项目级 skill(<框架根>\skills\<同名>\SKILL.md,rank 100)会覆盖它。
也就是说:你在自己框架里写的同名 skill 永远优先,插件不跟你抢。
3.3 一段系统提示词 + 两个斜杠命令
- 系统提示词段落:每次组装提示词时求值,把「框架根有没有
.git」「哪个产品还缺哪几项登记」
实时摆到模型面前。空的时候返回空字符串,不占上下文。
这是"闸门拦得住人"的关键:产品级/模块级规范不会被自动加载,光靠 AGENTS.md 拦不住。
- 同一段里还会摆出"契约边界"(
lib\isolation.js):当前会话的项目根是谁、会加载哪份 AGENTS.md、
模块里有没有被塞进外部工具的工作目录。只报要拦的(嵌套仓库把项目根截断 / 向上没 .git 退回 cwd /
项目根跑到框架外 / 模块里出现 temps\ 这类目录),一切正常就一个字都不说。
起因是真事故:别的项目的规矩渗进来,模块里被建出一批永远装不进东西的空壳。
完整画面(含"周边有哪些外部权威来源")在 test_framework_status 与 doctor 里。
/tf-status、/tf-gate:等价于查状态,不消耗模型回轮(结果不进模型历史)。
四、引导流程长什么样(test-framework-onboard)
需求文档动辄 100+ 页,模型不可能整篇读进来。流程刻意设计成「先出结论 → 人确认 → 再动手」:
① test_framework_status 先认账,看缺什么
定位需求文档(不许猜路径)
② test_framework_doc_probe 页数 / 章节 / 菜单命中 / 删除线段落 → 拆分判定(命中任一判据就停下来问)
A 页数 > 100
B 一/二级标题里有 ≥2 个独立业务功能名
C 正文里有 ≥2 个不同的顶级菜单名 ← 最硬的判据,基本不误报
D 用户口述里有并列词("包含/以及/里面还有")
→ ask_user_question 摆出「建议拆分方案 + 依据(章节号/页数区间)」等用户确认
③ 跨文档关联:问"本地还有哪些需求文档跟它有关联"(答没有要落 [])
④ 版本确认:问"本次测的是哪一版"(不许用文件时间判版本)
⑤ 环境与账密:页面地址 + 账号密码 + 数据库地址/账号密码 + 数据地址
⑥ test_framework_module_add × N(每个子模块一次)
⑦ test_framework_record(文档 + 环境)
⑧ test_framework_status 返回 ready: true 才允许开测
范例(用户自己举的例子):需求文档写的是「药品联动保障」,正文里同时有 缺药登记 +
短缺评估 + 余缺调拨 → 必须拆成三个模块,各一个 SKILL.md、各一套 requirement/final/report/data。
需求原文只存一份,各模块 SKILL.md §9 用相对路径引用它。
闸门口径(这是判"问没问过"的唯一依据):
| 字段 | 缺 key / 空 | 有值 |
|---|
split | [] 或没有 = 拆分未确认 | 模块名数组 + splitConfirmed: true |
relatedDocs | 缺 key = 跨文档关联还没问过 | [] = 问过了、答"没有";有值 = 关联文档的本地绝对路径 |
version | 空 = 版本未确认 | 如 2606、2606beta3 |
environments[] | 空 = 环境未登记 | 至少一个环境 + 对应库账密 |
五、文件说明
_plugin\dsh-test-framework\
├── package.json 清单:main / bin / exports / dsh.bundle.patch(成为 profile layer 的唯一凭据)
├── cordis.patch.yml bundle patch:insert: [{ id: test-framework, name: 'dsh-test-framework' }]
├── README.md 本文
├── lib\
│ ├── index.js cordis 宿主入口:注册 4 skill + 7 工具 + 1 提示词段落 + 2 命令
│ ├── skills.js 4 个 runtime skill 的**正文**(跟着插件走,任何工作区都能用)
│ ├── tools.js 7 个工具定义(**手写 ToolDefinition**,不 import defineTool)
│ ├── framework.js 框架根识别 / 状态与闸门 / 框架登记读写 / 脚手架 / 模块登记(含**按版本建规范 + 回填 §8 版本索引**)/ 表格 upsert
│ ├── report-gc.js **报告回收**:定位框架里那份 `archive_reports.py` 并调用它(**不重写归档逻辑**)
│ ├── docx.js **零依赖** docx 探针:zip 自己解、XML 自己解
│ ├── templates.js {{KEY}} 渲染(未替换的 key 保留 + 告警,不静默吞掉)
│ ├── install.js 安装 / 卸载 / 体检(默认链接安装,官方 `dsh plugin add` 可选,**先备份**)
│ └── cli.js 命令行入口(init、status、probe、report-gc、install、uninstall、doctor)
├── templates\ 生成到工作区的模板(AGENTS.md / README / 模块骨架 + **版本骨架** / 测试报告 / 框架级 skill / 产品级规范 / **报告回收脚本**)
└── test\
├── framework.test.mjs 32 条框架测试
└── install.test.mjs 8 条装机测试(全部在临时假 DSH_HOME 上跑,绝不碰真 profile)
产出的框架工作区长这样
<框架根>\
├── AGENTS.md 框架契约(DSH 自动读取,前提是根上有 .git)
├── README.md
├── skills\ 框架级 skill(DSH 自动发现)
├── .dsh\skills → ..\skills **目录联接**,DSH 的 skill 发现入口
├── .git\ **项目根标记**(DSH 的 findProjectRoot 只认 .git)
├── .dsh-test-framework.json 插件认框架根的标记
├── <产品>\
│ ├── _框架配置\ 产品级规范:环境与账密 / 需求文档台账 / 模块路由 / 框架登记.json
│ ├── _冒泡测试\ 冒泡测试规范 + archive_reports.py(报告回收脚本)
│ ├── _库结构\
│ └── <模块>\ SKILL.md(索引)+ skill\<版本>\SKILL.md(该版规范)+ requirement / final / report / data
├── _templates\
└── _archive\ scripts / backups / outputs / **报告归档\<模块>\<日期>\**
六、边界(照实说,不含糊)
做不到的 / 故意不做的:
| 项 | 说明 |
|---|
| 解析加密 docx | 本机加密件是 OLE 容器,解密要 msoffcrypto(Python)。探针会认出加密并给出处(走框架级 requirement-docs skill),不会假装读到了内容 |
解析二进制 .doc | 不是 zip,读不了;如实上报,建议另存为 .docx |
| 拆分方案自动定案 | 不自动。命中判据只做一件事:把 AI 摁住,让它去问用户 |
| 多步表单 UI | DSH 斜杠命令只收裸文本,且结果不进模型历史;真 UI 要 dsh.client + 槽位,而官方客户端构建工具链未随包发布。所以引导靠 skill 正文 + ask_user_question |
| 热加载 | 装完/改完 cordis.patch.yml 必须重启 DSH(dsh-web-app 把 hmr 在 host plane 关掉了) |
| 覆盖已有内容 | 一律不覆盖。模块 SKILL.md 是累积型文档(框架铁律 9),要补内容请追加 |
已知的实现取舍:
lib/tools.js 不 import defineTool —— 插件常以 link: 安装(真实路径在 profile 之外),
Node 解析不到 profile 里的 node_modules\@deepseek-ai\*。所以手写等价形状;
ToolRuntime.register() 只强校验 output.schema + output.render,原始注册由工具自己校验参数。
- 同理,宿主入口全部零静态
@deepseek-ai/* import,服务一律用 ctx.get(name) 可选取 +
方法名探测,每段注册外面包 try/catch —— 插件绝不能把 DSH 启动搞挂。
- 注入的提示词正文会把
{{ 打散成 { {:renderPrompt 是严格模式,遇到未知 {{变量}} 会抛错。
- 登记函数(
recordDocuments / recordEnvironments)位置参数与对象参数两种都收 ——
同模块里 frameworkStatus / addModule 用的是对象参数,两种形状并存太容易写错(开发中实际踩了两次)。
七、开发者自检
cd <插件目录>
npm test # 72 条测试(64 框架 + 8 装机)
node lib\cli.js doctor --profile desktop # 环境体检
node lib\cli.js init --product "演示产品" --root .\tmp --dry-run # 只看脚手架计划
node lib\cli.js probe "<任意 .docx 绝对路径>" # 探一份真实需求文档
node lib\cli.js report-gc --dry-run # 先看会归档什么(不加 --dry-run 才真搬)
框架测试 48 条覆盖:脚手架完整性(含版本骨架与报告回收脚本逐字节一致)+
生成物里没有没被替换的占位符 + 登记文件是合法 JSON(Windows 反斜杠转义)
框架登记.json 被正确报成「本次新建」而不是「跳过」 + 不覆盖已有内容 + dryRun 不落盘 + 框架根识别 +
模块登记幂等且不覆盖累积型 SKILL.md + 闸门逐项拦住与放行(含「relatedDocs 缺 key = 没问过」
「[] = 问过答没有」)+ 密码只落 md 不进 json + 登记幂等 + 登记函数两种调用方式都行 +
表格 upsert 三种动作 + 渲染告警 + 全部模板只用已知占位符 + docx 探针(非 Office / 不存在 / OLE 加密)
- zip 与 XML 基础件 + skill/工具定义形状合法 + skill 里提到的工具名都真的注册了
(反向也查:每个工具都至少被某个 skill 提到)。
(0.2.0 新增)版本化模块规范 4 条:
① addModule 带 version → 索引与 skill\<版本>\SKILL.md 两份都落,且索引 §8 版本索引自动补行、§1「当前在测版本」跟着走;
② 两版并存:2607 新开一份,2606 逐字节不变;同一版重复登记不堆重复行;
③ 不给 version → 不建版本规范且必须显式告警(不许静默少文件);
④ 模板自带「按版本分开」三件套(版本骨架含 §V1~§V6、模块骨架指向 skill\<版本>\、脚手架不漏搬子目录)。
(0.2.0 新增)报告回收 5 条:productsWithScript 只认真落地了脚本的产品 +
planReportGc 默认 dryRun、指到落地脚本、--hours/--all 透传、没脚本/没注入 listProducts 都明确失败 +
renderReportGc 人类可读且原样带出脚本输出 + runReportGc 真跑一次 dry-run(本机没 python 就明确 skip,
不许假装通过)+ report_gc 工具描述与行为一致(不传 dryRun 时规划出来必须带 --dry-run)。
(0.2.0 新增)模板契约 2 条:AGENTS.md / README.md 模板不许再出现旧归档根 _archive\冒泡测试\,
且必须写明版本化 skill 两层、报告硬要求(缺陷速览 / 需求原文依据 / 与需求不符点 / 复现步骤)。
(0.2.8 新增)防干扰 8 条:干净框架一个字都不报(空字符串、不占 token)+
嵌套仓库截断契约要报、且不许误诊成"框架根没有 .git" + 项目根跑到框架外面时要点名实际会加载哪份 AGENTS.md +
skills\ 是辅助目录不是产品(回归:曾把 vision-read\engine 误报成"外来目录")+ 模块里的 temps\ 算污染、
而散落文件只算"没归位"且不进闸门(本框架自己就有个有意的生成产物)+ .git 缺失要报 +
外部权威只报告、不进闸门(兄弟目录的 AGENTS.md / .trae 扫得到)+ 有发现时闸门不许因为"登记没问题"就整段跳过。
"向上没 .git → DSH 退回 cwd"那条没固化成用例:它要求被测目录上游真的没有 .git,
而本机 C:\.git 是存在的(临时夹具都在 C: 上)—— 这条在 D: 上实测通过,但不写进单测免得变成环境依赖。
装机测试 8 条(全部在临时假 DSH_HOME 上跑,绝不碰真 profile):
listProfiles 只认有 package.json 的目录 + 找不到 profile / 没声明 dsh.bundle.patch 要报错 +
link 模式先备份、写依赖与 bundles、建出的联接真能解析回插件目录 + 不动别人的依赖 +
幂等不堆重复项 + 认出「半装」状态 + 卸载摘干净(依赖 / bundles / 死链接)但不动别人的依赖、不删备份 +
装机计划里必须写明「要重启」与「默认不跑 pnpm」。
npm test 刻意用 node test/*.mjs 直跑,不用 node --test test/ ——
后者会给每个文件起子进程,在受限沙箱里会撞上 spawn EPERM。
八、变更记录
| 日期 | 版本 | 变更 |
|---|
| 2026-10-15 | 0.3.0 | 模块的定义改成「叶子菜单」,并给框架加上「跨端业务关联」的登记与校验。起因:同一件事常常横跨多个端(中心端 / 成员端 / 患者端 …),模块却只按顶级菜单建 —— 于是「这个功能在另一端有没有对应实现、在哪个环节交接、共享哪张表」没人说得清,报告与需求原文之间也对不上号。 ① 新口径:模块 = 系统里的一个叶子菜单(父菜单不建目录、只作菜单路径);每个模块带两个新属性 —— end(端)与 businessObjects(逻辑业务对象,跨端对齐的唯一抓手);模块名 = <端>-<逻辑业务对象>,默认端不带端前缀、非默认端必须带(默认端由 框架登记.json 的 ends[].default === true 决定)。 ② 登记结构:框架登记.json 新增顶层 ends[] / relations[] / flows[],modules[] 新增 end / businessObjects;addModule 新增 end / businessObjects / links 三个可选入参,按 (from,to,businessObject,flow) 幂等 upsert 关联边(同键就地更新,不堆重复行),并自动重算两端的 registeredOnBothSides(只写一边 = 单向,两边都登记后自动变双向)。 ③ 模块 SKILL.md 新增「§关联模块」段:只追加(表在就段内 upsert 一行、表不在就补段、新段优先插在 ## 归档索引 之前),固定八列 关联模块 / 方向 / 关联类型 / 业务对象 / 交接点 / 共享表 / 对端路由 / 说明,反复登记不堆行。 ④ 新工具 test_framework_link(只读) + CLI link 子命令:查 E_MODULE_MISSING / E_ONE_WAY / E_FLOW_BROKEN / E_END_PREFIX / W_NO_BUSINESS_OBJECT / W_ROUTE_DRIFT / W_REGISTERED_IN / W_DUPLICATE_MODULE(外加登记读不到时的 E_REGISTRY_MISSING);退出码 0 = 无错误(warning 不拦人)。 读登记时对"人手写的那套口径"做了兼容(真机上撞出来的):端名认 name 或 key、链路节点认 order 或 nodes、路由认 routes{from,to} 或扁平的 fromRoute/toRoute(后者只在像路由时才比对,因为那两列常装菜单路径/入口描述)、from/to 允许写端名(有的端整端不拆模块,如 H5 患者端)—— 不兼容就会出现"每个模块都报端前缀错、整条链路报模块缺失"的误报洪水。 ⑤ 闸门新增 linksChecked:纯登记就能判的三类问题(单向边 / 链路断点 / 端前缀)为零即通过;刻意不含需要读磁盘的 E_MODULE_MISSING(它在 missingItems 里单独提问); —— 没有任何模块声明非默认端、且没有任何关联边时。: 加「跨端业务关联」一节 + 叶子菜单口径 + 闸门表补 + 列 ; §2 加端前缀命名规则、文末加 §6 指向;新增产品级模板 (§1 端清单 / §2 业务对象↔各端模块 / §3 关联边表〔固定 12 列〕/ §4 链路图 / §5 旧名↔新名 / §6 维护规则);模块骨架 加「§关联模块(跨端 / 上下游)」段。:出去的是 (字符串,多个 分隔)/ / / , / / ++ 作兼容别名(两套都写、读哪套都行,产品侧脚本与插件写出来的是同一套);规范名(不留空壳字段)。的时候两套都吃, 入参也两套都收( / / / 可平铺传)。手写口径一并写进模板:端 = 、链路节点 = 。:① —— 有的端(如 H5 患者端)整端不拆模块、目录名直接用端名,原判据会拿 前缀去报 ;现在时放行(同时留一条 提示,要求写进 §1 交代清楚),同一端下模块照旧必须带前缀。② —— 两边常写成 与 ,整串比必然"对不上",但;现在只比,路径真不同才报(查询参数、散文前缀、 措辞差异一律不算漂移)。:同一条边重复登记时, —— 先前是"无条件覆盖",于是"只补一句 "会把对端手写的 、散文形态的 、共享表覆盖掉,还会把 (与模块侧那个真机缺陷同一类,属)。现在 12 个字段逐个走"给了才改、没给沿用";(不是只进不出);本次不给 时还会放宽键去命中已有的那条边,不再堆重复边。: 在单一事实源里是,插件拿 拼一个塞进去,会被后续会话误当成"已确认口径"。现在 ;由模块 「§关联模块」的说明列在拼一个带 前缀的兜底串()—— 前缀就是它的身份声明,且(有测试直接 grep 登记文件里不许出现 )。: (64 框架 + 8 装机);另在(,)真机造一个 3 模块 / 2 条单向边的产品, 如实报出 (退出码 1);补上反向边后重跑 → 、(退出码 0);再插一条断链 → 报 (临时目录用完即删)。:① —— 幂等重入时无条件用本次入参覆盖 / / / / ,而"在对方模块补一条 "这种调用根本不会重复填这些字段,于是路由被悄悄清空(真机上就是这么踩到的)。现在改为。② —— 的"页面路由"列回填的是 时, 会拿它去比对,凭空报一堆"路由漂移"()。现在该列按"填没填"判,占位符等于没填、不参与比对。③ (最严重的一类):插件自己写的是 / / ,而人手写的产品登记用的是 / / / 扁平的 /,且 / 里(有的端整端不拆模块)—— 按插件自己的字段名去读,会把(于是 22 个模块全被判"非默认端、缺端前缀")、把端名判成"模块缺失"(整条链路报错洪水)。现在这些字段名,端名视为合法链路节点,扁平路由只在时才比对(那两列常装菜单路径/入口描述)。顺手加了 ( 同名重复登记 —— 真机上确实有)。:22 模块 / 42 边 / 5 链路 / 3 端 → (全是 :那 10 个模块的条目里没写 ),工具一个字都没改那份文件。(用户口径:"先修残留,再一起提交并发布")。0.3.0 改了契约与主要模板,但 里还留着 6 处旧定义:① (§0 标题「一个模块 = 一个顶级菜单」、模块↔菜单定义、「本次测的模块(= 顶级菜单)」);② 判据 A/C(「100 页以上的事」「(本框架的定义)」);③ 两处;④ 目录树一处。:统一改成「一个模块 = 一个叶子菜单」,并把一并规范化 —— → (遍历清单 JSON、预检结论表、缺陷记录表、复现步骤共 6 处); 作为的字段名( 的列定义、、 的 示例都是合法用法,不是残留)。 另补说清父子关系( = 父菜单、 = 完整路径、本模块是该路径上的叶子), 改以 打头。:全包(排除 / )搜「一个模块 = 一个顶级菜单 / 模块 = 顶级菜单」→ 。⚠️ : 里 4 个 skill 的描述( 逐字写着「」, 正文还专门写「模块 = 叶子菜单()」)。我是在 里看到旧文案才一度当成源码残留 —— ,特此更正、不上工单。 |