dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH Tavern 错题库:按人物卡分类记下调试中踩过的坑,先查本卡、再跨卡。 / Per-card defect ledger for DSH Tavern.
DSH Tavern 的错题库:按人物卡分类记下调试中踩过的坑,下次先查本卡、再跨卡。
调一张卡踩过的坑,隔一周换一张卡会再踩一遍 —— 因为是同一类问题:同名的状态字段、同一段正则、同一个脚本钩子。这个插件把每个坑记在出问题的那张卡名下,并在调试时给出一个固定顺序:先查出问题那张卡自己的错题库,再跨卡查。
MVU版本 互相认得,能把一条结论一键复制过去。四个入口,指向同一个页面:
(前两个是卡片工作台里的入口;后两个来自 Tavern 的插件接口,装不装 Tavern 都不影响别的功能。侧栏面板用的是接口第 2 版的 registerPanel,装在旧版 Tavern 上只会少这一块。)
页面里六个页签:
lib/,这里用来核对补丁还在不在、一键重打。顶部筛选会同时作用于左侧列表:选了「未解决」或某个范围之后,左侧只留下还有命中条目的分类。没选筛选时不过滤(否则一进页面就像库读坏了)。
错题页顶部还有一个**「回流到 Skill」**按钮:把调出来的结论写进某个 skill 的参考资料,下次做同类事情时 Agent 会读到。见「回流到 Skill」。
从「记进错题库」抓下来的底稿,出处不只是写进 refs 里那句话,另外存了一份结构化的
{ gameId, turn, textVersion }。理由是来源会失效:回退、重新生成、编辑正文之后,
那一轮的正文已经不是当初抓下来的那段,refs 里那句「第 N 轮」就成了指向空气的指针。
Tavern 的插件接口第 2 版会通知剧情线变化(onTimelineChanged),错题库据此给受影响的
底稿盖一枚标记:
| 变动 | 对底稿的处置 |
|---|---|
| 回退到来源轮次或更早 | 标脏(那一轮及之后都读不到了) |
| 撤销回退 | 自动清掉因回退盖的标记 |
| 重新生成 / 编辑来源轮次 | 标脏(轮次还在,正文换了) |
| 分叉 | 原局的底稿不动 |
| 没带轮次的通知 | 退化成按整局标,不去编一个轮次号 |
被标脏的条目在面板上多一个「来源已失效」角标,点开详情写明是哪一轮、怎么变的,并说明 底稿本身一个字没改。确认问题仍然存在就点「确认仍有效」把标记清掉。
条目自己存的 source 是可选的:早期条目(以及手工新增的条目)没有它,永远不会被标脏 —
那时候只有一句人读的 refs,没有可比对的东西。
设置页右上角有一个 「● 卡片更新器 · 已连接」 按钮,用来显示另一个插件在不在。
它有三种状态:
任何时候点它都会打开卡片更新器的仓库页(https://github.com/XGUIMAX/dsh-card-updater):未连接时那是补装入口,已连接时那是查更新和看文档的地方。鼠标悬停会写明当前状态。
探测方式是往对方插件自己的路由发一次 GET /dsh-card-updater/state,每 15 秒复查一次。这用的是对方的公开接口,不需要两边事先约定协议,也不要求两个插件同时升级 —— 对方没装、版本老、或者路由还没就绪,这里只会显示未连接,不会有报错弹出来。
面板右上角有一个对端状态按钮:显示卡片更新器的连接状态(绿点「已连接」/ 未连接), 并在它挂着「专属预设」时把预设名带过来。两个插件互不读写对方的数据文件,只走 HTTP。
卡片更新器认得出作者在帖子里写的「另附专用预设」,错题库把这一条读过来显示:人物卡列表里,带专属预设的卡会多一个虚线边框的「专属预设」小标签,鼠标悬停能看到来源链接。
判据是卡片更新器状态里的 config.cards[].primary.gates 含 preset 项 —— 也就是它的面板上那个「另附专用预设」条件。能不能对上号取决于这张卡有没有配原卡路径;对不上就安静地不显示,不会报错、也不会卡住。
这里只做展示,不往错题库里写任何东西。 专属预设是卡片的客观属性,不是「踩过的坑」;自动写成条目会把病历本变成资料库,查故障时反而更吵。需要记录时「回流到 Skill」那条路更适合。
错题库是病历,skill 是诊疗手册。这一栏搭的就是两者的桥:错题页顶部点**「回流到 Skill」**,把选中的条目写进某个 skill 的 references/ 下。
只写你自己的 skill —— 这条写在面板的提示行里,不用点进来才知道。内置 skill(card-to-mvu、debug-card 这些)在程序目录里,Tavern 更新会整份覆盖它们,写了也留不住,所以弹窗里只列能写的;要往那个语境里沉淀,先新建一个自己的。
弹窗里填三件事:目标 skill、范围(全部 / 当前分类 / 未解决的)、目标文件(默认 references/错题库回流.md)。选中某个 skill 时会显示它的简介、参考资料和条目数 —— 建重了、或者建完一直没用(条目数 0)一眼就能看出来。旁边的「删除这个 skill」删之前会把整个目录复制到 <数据根>/skill-backups/,备份不成功就不删。
每条按现象 / 根因 / 修法 / 证据铺开,带上分类、范围和状态。同一个目标可以反复回流,不会写出重复的东西。
条目会跟着错题库更新,但手工改过的段落归你。 每段标题下面有一行 `` 标记,记着它来自哪条错题、写的时候什么状态、以及正文的指纹。同步时按它分三种:
所以想给某条加上自己的注解,直接改那一段就行;从改的那一刻起,那一段就不再被同步碰。
老文件里那些没有标记的段落会被认领一次:结构对得上(有元信息行 + 至少一个小标题)就补上标记、纳入管理;对不上的当手工段落护起来。认领之后照上面的规则走。
新建 skill 时把简介写实。 description 是 Agent 判断「要不要自动加载这个 skill」的唯一依据 —— 光有名字加一句"这是个错题库",它不知道自己该在什么时候想起来。三个要件:是什么 + 什么时候用 + 症状词。
不知道怎么下笔就点「示例」:它按当前范围和错题库里的实际条目算一句真的出来,不是留个空模板。全部范围会生成类似这样一句,可以直接用或改:
已解决故障库:DSH Tavern 调试中踩过的 40 个坑(工具链、MVU、前端UI、正则、世界书),涉及 Tavern 本体、龙娘回廊!5.3 MVU版本、卡片更新器、卡内故障。用户排查卡片异常、转 MVU、或调试插件与宿主时,先翻一遍看有没有同一类 —— 每条含现象、根因、修法与实测证据。
两条硬边界:目标文件不许跑出 skill 目录(.. 会被拦),skill 名也不许带路径。
手动点回流的结果通常是:记完错题就忘了点,skill 停在几天前,下次调试读到的还是旧的那份。所以在弹窗里勾上**「以后新条目自动同步到这里」**之后,每次记错题、改错题、批量导入都会顺手跟一次。
回流只改「我们自己写的、且没人动过」的段落,所以自动跑是安全的 —— 没变化时是 0 条写入、文件一个字节不动。实测:打开自动同步时 unchanged 48、文件大小不变;之后改一条错题的状态,只有那一段被重写。
是否真的在跑要能看见,所以:
自动同步已开 → ziyongskill(上次 2026-09-25 17:43,写入 0 条);弹窗里同样能看到这套状态,复选框就在目标 skill 下面。
DSH Tavern v2.3 起内置了自己的改卡记忆(data/card-memory/,配 tavern_memory_search / _preference / _experience 三个工具,底层是 memon 记忆引擎)。它和错题库记的是同一类东西 —— 改卡时踩过的坑 —— 但两者的长处不同,所以并存、按长处分工,而不是合并成一个。
宿主改卡记忆 → 跨卡、可复用的经验。
它的 scope: shared 语义明确:一条经验要么属于当前这张卡、要么是全库通用,没有含糊地带。而且带强制约束 —— status 只要不是 unverified(即 static-validated / runtime-verified / user-confirmed),就必须填 evidence,不填直接报错。这让「声称这条已验证」这件事留得下凭据,比错题库那套纯靠自觉的 status 有力。
错题库 → 按卡归因的具体故障。
per-card 分桶让它天然回答「这张卡出过什么问题」;面板能一览全部条目、按状态过滤、原版卡与 MVU版本 互认;而且回流出的 skill 能在别的会话里被搜到 —— 这是目前唯一一条能跨会话把结论带走的链路。
检索时两处都查。 两个系统互不读取:只查错题库会漏掉跨卡经验和存档的用户偏好,只查宿主记忆会漏掉按卡归因的存量。任何一处查不到,都不代表「没踩过这个坑」。
一处有意的例外。 v2.3 的说明里写着「将错误经验写入改卡记忆,不再默认生成 Skill」,而错题库保留自动回流 —— 这不是没跟上更新。回流是错题库的核心机制,它的产物能被别的会话检索到,这条链路目前没有替代品。所以这条分歧是刻意留的。
作者经常随卡附带自定义脚本 —— 悬浮状态栏、地理数据加载、变量更新器、自动化流程 —— 它们不在任何通用清单里,却决定了卡能不能正常跑。DSH 又没有全局脚本槽:脚本随卡加载,换一张卡就换一套。所以排查任何一张卡之前,先看清它带了什么。
「卡脚本」页签把这件事摊开。这一页是懒加载的,切过去才扫,不拖慢打开面板:
import 指向的外链、脚本自己的 data 键。常见的几个(外置状态栏、小手机脚本、创意工坊、格式修复、辅助计算脚本)算通用,压暗显示,不跟特化脚本混在一起;update / json_patch 的条目;data/tools/ 下的脚本 —— 那些不属于任何一张卡,是从卡里导出来的、或者为卡自制待命的那批,卡出问题时就靠它们适配。脚本的判据是从 data/tools/card-scripts/scan.mjs 搬进来的:读 data.extensions.tavern_helper.scripts,并且要先剥掉卡文件外层可能套着的那层 raw —— 少这一层,脚本数会永远是 0。
一份跨卡共用的脚本库。这些脚本不挑卡,装到哪张卡就在哪张卡用;库目录是
data/tools/wrongbook/common-scripts/,不属于任何一张卡。
典型用途:让副 API 在后台生成资料、把状态栏渲染交给脚本、跑回归测试、做结果回填。
面板只列「可装脚本的卡」,判据是三条任一:
… MVU版本;dsh_card_marker.kind = hand-tuned-mvu(自带 MVU,见「卡型标记」);只作参照的原版卡不在此列,也不提供装卸入口。
库里每个脚本有三个动作:导入脚本(在页面右上角,进新脚本)、更新(换掉这一份,并自动推到已经装了它的卡上)、删除(只删库文件,不动已装到卡上的)。
dsh_meta.category(没有就归「未分类」),标题行可以按分类筛。助手agent_v0.15 只看装了它的卡。脚本本身就是分类单位,不再另外给卡设一套分类。库里每个脚本右侧的按钮,按"精准度"分三层:
| 按钮 | 做什么 | 快慢 |
|---|---|---|
| 检测适合的卡 | 即时关键词匹配:从脚本内容认出它调用了哪些宿主能力,与每张卡的特征对着看,对得上就给一条理由 | 秒出 |
| 生成建议 | 生成一段文本(脚本路径 + 全部候选卡的 @ 引用 + 要求逐张读卡、点名具体功能),复制到剪贴板,对话页开着就顺便填进输入框 | 秒出 |
| 后台分析 | 逐张读卡的静态分析,带进度条 | 慢,但关界面也继续 |
为什么「生成建议」不直接发出去:它会让工作台那边的模型逐张读卡、说清"这张卡的哪个功能能被脚本接管"(比如「龙娘回廊的橱窗」)。 这一步要消耗一次完整的全卡分析,所以做成"生成 + 给你看 + 你决定发不发"。
关于"精准"的边界:宿主没有给插件暴露模型调用接口,所以插件自己做不到"模型读完整卡再判断"。 「检测适合的卡」和「后台分析」给的是技术层面的匹配(脚本用到的能力 × 卡具备的特征), 理由全部引用卡里实际存在的东西(正则名、脚本名、世界书条数),可以照着核对 —— 但它们判断不了题材是否合适,那需要真的读过卡。
逐张读卡的深度分析:读 description、正则名、脚本名、世界书标题、MVU 机制,然后和脚本能力做匹配。
刻意不读全文(有卡 11MB)。
关掉面板也会继续跑,靠四点:
data/tools/wrongbook/fit-<脚本名>.json),所以中途重启也能看到进度到哪了;setTimeout(r, 0)),不然会占满宿主进程、界面卡死。前端只负责启动和轮询,并用 localStorage 记住上次看的脚本名,重开面板接着显示进度。
装/卸只改卡文件里的 extensions.tavern_helper.scripts,其余字段逐字节不变(做过逐字段实测)。
注意:脚本本身可能用 indexedDB / localStorage、挂 setInterval、往 window 上挂东西、
用 eventOn 订阅事件 —— 卸下只是停止注入,运行期已经写下的缓存不会自动清掉。
库里的脚本用「更新」换新版。它和「导入」是两个动作,不是同一个:
| 导入 | 更新 | |
|---|---|---|
| 用于 | 新脚本进库 | 换掉库里已有的那份 |
| 同名时 | 静默覆盖 | 明确提示"库里没有就请用导入" |
dsh_meta | 按新文件重算(没带就生成草稿) | 保留库里的 —— 新文件显式带了才用它的 |
| 备份 | 无 | 更新前把旧版拷进 common-script-backups/<时间戳>/ |
| 已装的卡 | 不动 | 自动同步到新版(同一批卡,走同一个安装流程) |
为什么 dsh_meta 要保住:分类 / 用途 / 建议可能是你在界面上改过、或者插件攒下来的,
不能因为新版文件里没带这一段就被一份自动草稿盖掉。
内容与库里那份完全相同时直接返回「没有改动」,不白写一次盘。
更新库里的脚本时,已经装了它的卡会一起更新 —— 这一步是自动的,不需要再点一次。
理由是「库里是新的、卡上还是旧的」属于最容易漏掉的一种不一致: 它不会报错,只会让某张卡在某个时刻行为与预期不符,而人很难想到去核对"卡上那份是什么时候装的"。
同步走的是同一个安装流程(installCommon),所以备份、写盘、只动 scripts 数组这几件事都一致;
用的内容就是刚落盘的新版。更新完面板会列出同步了哪几张卡。
同步失败不影响库的更新:库已经换好了(那是主操作),卡这边没跟上属于部分完成 —— 面板会把失败原因写出来,让人知道哪一步没成。
有的脚本自己会写 localStorage / indexedDB / 世界书条目 / MVU 变量。
换掉库里的文件不会清掉那些数据 —— 新版可能读到一份旧版留下的状态。
所以更新时会先认一遍这个脚本会写什么,命中就在面板上列出来:
注意:这个脚本自己会写 localStorage · indexedDB · 世界书 ——
换掉库里的文件不会清掉那些数据,新版可能读到旧版留下的状态。
顺带会对比新旧两份的版本常量(脚本自己写了 VERSION 之类的话)并显示 旧 → 新,
降级时能一眼看出来。
这一条不阻止更新,只是提示 —— 判断该不该清状态需要知道脚本具体怎么用那些数据,那是读过它才说得准的事。
导入脚本时,后端会扫一遍内容,生成一份 dsh_meta 草稿(分类 / 用途 / 建议)写进库,
免得回头再补。已经带了 purpose 的不会被覆盖 —— 作者的说明优先。
它给的是能力清单,不是内容判断:
分类 助手 / 后台(调 generateRaw / worldbook_profile)
变量 / MVU · 聊天 / 记录 · 界面 / 渲染 · 未分类
用途 「这个脚本会用到的宿主能力:读/写 MVU 变量、世界书读写、监听消息事件…」
刻意不写"建议装到哪些卡":脚本能不能装跟卡无关,硬编一个卡名单只会误导。
有一条例外值得单独说。「来当小男友爆管人的米吧!」是作者原制、自带 MVU的卡, 不是 DSH 转换出来的 —— 转 MVU 会丢失大量内容(21 条正则含 583K 字正文美化、7 个脚本、170 条世界书), 代价大于收益。
所以它不走文件名约定,而是在卡里写一个显式标记:
dsh_card_marker: {
kind: "hand-tuned-mvu",
label: "已手改 MVU",
conversionBlocked: true,
...
}
面板读这个标记,把它的每张卡都当 MVU 卡对待(可装通用脚本、进统计)。
标记是手动打的,只用于这类例外 —— 正常情况下文件名 … MVU版本 就够了。
这是这个插件存在的理由,所以单独说。
① 本卡错题库。 同一张卡上的问题是复发概率最高的一类:还是那个状态字段、还是那段正则。先看它,是为了避免把已经调出来的结论重新调一遍。
② 其它错题。 本卡没有答案时先来这一段:归档位置可能有偏差 —— 卡的问题被记进了「其它错题」、或者插件上的坑其实是某张卡的坑 —— 而归类偏了不该让检索漏掉它。它排在跨卡之前,因为插件与工具链上的坑影响的往往不止一张卡。
③ 跨卡查询。 最后才是别的卡片。这时候「这也是个新坑」本身就是信息,而别处的相似记录开始有参考价值。
三段的范围是固定的:① 当前分类,② 「其它错题」那一组(当前就在这一组时是组内其余分类),③ 其余卡片。从哪一头进来顺序都一样。
三段共用同一套打分:标题 > 标签 > 现象 > 根因/修法 > 引用,越靠前权重越高。没给关键词时,工具输出里的 ② ③ 只印前 8 条并标出总数 —— 全库几十条铺开会把 ① 淹掉;带上关键词就是有目的地在找,那时整段列出来。
工具在插件加载时注册到宿主,同时会往 system prompt 注入一段说明。只有前者不够:工具列表里多几个名字,模型不会因此去用它们 —— 真发生过一次,让 Agent 归档时它把错题写进了 materials/错题库.md,因为它只知道那儿。
注入的说明要求 Agent:
wrongbook_lookup,按三段顺序看;wrongbook_record 归档,现象、根因、证据、处理、防坑都写进去;wrongbook_update 补根因或标记已修复;| 工具 | 用途 |
|---|---|
wrongbook_lookup | 按固定顺序返回三段:「① 本分类」→「② 其它错题」→「③ 跨卡查询」。参数 card(卡名、cards/xxx.json,或其它错题里的主题名)、query、status、scope、limit |
wrongbook_record | 把新发现的问题记进对应分类;card 填一个还不存在的名字会当场建出这个分类。落库到 Tavern 数据根下 |
wrongbook_update | 标记已修复、补写根因或修法 |
card 的解析是分层的:完整路径 → 文件基名 → 分类显示名完全相等 → 名字包含。模糊命中多个分类时不猜,落到「通用 / 未归类」并把候选带回来。
这条对原版卡和它的 MVU版本 同样成立 —— 它们是两个不同的分类,坑往往只出在其中一张上(调试对象通常是 MVU 版)。所以 card: "道渊" 会同时命中两张,此时:
wrongbook_lookup 照常返回三段,但在开头警告「对上了 2 个分类:…」,让你补全名字;wrongbook_record 直接拒绝,要求写全名字。替你在两者之间挑一个,就是把结论记到不该记的地方,比让你多写几个字贵得多。面板上这两类也各有标记:原版卡标「原版」,MVU 版标「MVU」。
先装 DSH Tavern。 这个插件是它的伴生工具:人物卡、卡片工作台对话都由 Tavern 提供,插件只负责把这些坑记下来、并在调试时按顺序翻出来。
Tavern 地址:https://github.com/flizzywine/dsh-tavern
此外需要已经在用的 DSH,且有一个 profile(本说明中默认叫 tavern,Tavern 项目用的就是它)。插件是 DSH 插件,不是独立程序。
dsh plugin 是 DSH 自带的插件管理命令:把参数转给 profile 目录下的 pnpm,装完之后再按已安装状态核对 dsh.profile.bundles —— 声明了 dsh.bundle 的依赖自动进入层栈。所以不必手工改 profile 的 package.json。
从 GitHub 装,不必先 clone:
dsh plugin --profile tavern add github:XGUIMAX/dsh-wrongbook
在项目目录里装本地副本(改源码立刻生效):
cd ~/.dsh/plugins/dsh-wrongbook
dsh plugin --profile tavern add .
装完重启 DSH,设置 → 错题库 出现即装好。
在 DSH Desktop 的命令行工具里,默认 profile 就是
tavern,写dsh plugin add .就够了,--profile可以省略。
dsh plugin --profile tavern remove dsh-wrongbook
重启 DSH 后入口消失。数据留在 ~/.dsh/profile-data/tavern/data/tools/wrongbook,要一并清掉就手动删该目录。
GitHub 装的:
dsh plugin --profile tavern update dsh-wrongbook
本地目录装的:在项目目录里 git pull,重启 DSH。
单文件 JSON:~/.dsh/profile-data/tavern/data/tools/wrongbook/data.json,结构是 { version, cards, entries, config }。
每次写盘前把旧文件复制到备份目录,文件名形如 <毫秒>__<起因>__data.json,一眼能看出这份备份是哪个动作之前留下的。自动清理保留最近 60 份;面板上可以按份数手动清理,也可以删掉指定的某一份。还原之前会先把当前内容备份一次,所以还原本身也是可回退的。
默认在 tools/wrongbook/backups,想换盘或丢进同步盘就在备份页上点「选择文件夹」。
选择目录走的是自己的浏览弹窗,不依赖宿主的目录选择器 —— 那个能力在不在、能不能用都说不定,而「点一下什么也没发生」是最难查的一类故障。做法参照卡片更新器:host 出一个接口列一层目录,面板渲染一层。空路径列出所有驱动器(逐个探 A:\ 到 Z:\,Windows 上 Node 没有列盘的 API),所以换到别的盘也走得到;路径框也接得住粘贴进来的东西(两边带引号、正反斜杠、D: 只写盘符、相对名都认)。
保存时会先建目录、再写一个探针文件试一下,路径有问题在按下去的那一刻就报出来 —— 而不是等某次备份静默失败、回头看才发现这几天的都没落地。
切换只影响之后写入的备份:旧目录里已有的文件不搬动也不删除,所以换回去随时还能看到。选空路径则恢复默认。
自定义目录很可能是你挑的一个通用备份文件夹,里面躺着别人的 JSON。保留策略是会删东西的,所以列表、清理、删除都先按文件名认人:只有 <毫秒>__<起因>__data.json 这种形状才算本插件的备份,别的一律不列、不动、也删不掉。
面板右上角的「检测更新」比对两件事:
package.json、lib/index.js、client.js 三个文件的 sha256,跟上次检查的记录比。手改过源码就会被标成「本地有改动」。config.remoteUrl 指向一个返回 { "version": "..." } 的地址,有配置时会去比版本号,没配置就只报本地指纹结论。结果落在 tools/wrongbook/selfcheck.json,保留最近 20 次。
~/.dsh/plugins/dsh-wrongbook,不在程序安装目录里。<Tavern dataRoot>/tools/wrongbook。dependencies 里一条 link:(或 GitHub 装的版本号),dsh.profile.bundles 里一条名字。这两处为什么动不到,不是猜的。Tavern 更新 profile 清单走 bin/profile-configuration.mjs 的 mergeProfileManifest,是读-改-写加精确排除:
const userBundles = currentProfile.bundles.filter((n) => !previousManagedBundleSet.has(n))
const bundles = uniqueStrings(sourceBundles.concat(userBundles))
const dependencies = { ...currentDependencies }
for (const n of [...previousManagedDependencies, ...excludedMobileBundles]) delete dependencies[n]
for (const n of managedDependencies) dependencies[n] = sourceDependencies[n]
它只删自己上次托管的、只覆盖自己这次托管的,用户自己加的条目原样留在里面。这个插件既不在这份 managedBundles / managedDependencies 里,也不在 previousManaged* 里,所以一个分支都碰不到它。
面板上有一行实时自检(备份与还原页底部),查三件事:dependencies 里那条还在不在、dsh.profile.bundles 里那条还在不在、node_modules 的链接是否指回插件目录。全绿即正常;缺了会直接给出补回命令(dsh plugin add <插件目录>,或手工补那两处),不用去翻这份 README 猜。
数据量上去以后,最常走的几步都该是一次线性扫描。verify/perf.mjs 用 300 张卡 + 8000 条记录实测(同一台机器上的数字,量级参考):
| 动作 | 耗时 |
|---|---|
打开面板(GET /state) | ~31 ms |
| 带关键词检索 | ~24 ms |
| 不带关键词检索 | ~16 ms |
| 靠名字模糊匹配检索 | ~12 ms |
| 记一条新错题 | ~56 ms |
| 导入 500 条 | ~67 ms |
| 一次手动备份 | ~43 ms |
有几处曾经会随规模恶化,都改成了单遍:
unshift,是 O(n²);现在一次拼好再赋值。stat);现在两个目录各列一次建索引,之后全是查表。statSync 取尺寸和时间 —— 那两样没人用,只是顺手拿的。写入路径的开销主要在备份:每次写盘前把旧文件整份复制走,这是拿磁盘换「随时能回退」。8000 条时一次约 43 ms,换来的是任何一次误操作都能找回上一份。
「选择文件夹」点下去什么也没发生? 不会。它走的是插件自己的浏览弹窗,只依赖插件自己的 HTTP 路由,跟宿主提供什么能力无关。
「打开数据目录 / 打开备份目录」没反应? 两级依次尝试,每一级的失败原因都留着,全失败时连同 PATH 一起报回面板:宿主解析器 / SystemRoot 拼出的绝对路径 → cmd /c start。成功时消息末尾会带上走通的层级(比如 · C:\WINDOWS\explorer.exe),所以这台机器上到底是哪种方式有效不用猜。
面板里为什么没有系统弹窗? window.prompt 在 Electron 的渲染进程里没有实现,调用后直接返回 null 而且不报错 —— 用它做的「导入 JSON」「改分类名」会表现成按钮点了没反应。window.confirm 的行为在各版本之间也不一致。所以导入用面板内的文本框、改名用内联输入框、删除和还原用二次确认按钮。验收脚本里有一条静态检查,防止这两个 API 再被引进来。
卡片改名或被移走了,记在上面的错题会丢吗? 不会。已经不在卡片目录里的分类只标成「文件已不在卡片目录」,记录保留。删分类也一样,记录会先迁到「通用 / 未归类」。
为什么有两组分类? 分类带 group 字段:card 来自卡片目录扫描,other 是手建的。两组共用同一套列表与条目区渲染,只是换数据源。
lib/index.js Host 半边:数据、卡片扫描、检索、HTTP 路由、Agent 工具
client.js 浏览器半边:设置页的三个页签与目录浏览弹窗
verify/ 两套验收脚本
node verify/host.mjs # 259 项:扫描、分组、归类、卡名解析、三段检索、卡内脚本、安装自检、skill 回流与删除、路由、工具、备份
node verify/client.mjs # 110 项:注册接线、字典一致性、四个页签与两个弹窗、三段渲染、禁用 API 静态检查
node verify/perf.mjs # 9 项预算:300 张卡 + 8000 条记录下量一遍最常走的几条路
node verify/live.mjs # 只读核对**真实数据**:错题库 ↔ 回流过的 skill 还对不对得上
verify/host.mjs 在系统临时目录里现造一个沙盒数据根,不碰真实数据;verify/client.mjs 用最小 React 运行时渲染面板,不发网络请求。
套件不打开任何外部程序。 曾经有一条实跑 openBackupDir 的用例,已经删掉:它会真的弹出一个资源管理器窗口,而窗口指向的目录一旦在事后被删(沙盒会),资源管理器就弹「位置不可用」——比不测还烦。「打开目录」是人工验证项:重启后在面板上点一下,看窗口落到哪儿、via 是哪一级回退。
改 package.json 的 version,打同名 tag:
git tag v1.0.1 && git push origin main --tags
面板右上角的版本号读的就是 package.json。
MIT