dsh-molbio-tools 0.0.0-stage → 0.20.0
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/CHANGELOG.md +2055 -0
- package/LICENSE +21 -0
- package/README.md +735 -2
- package/align-post.mjs +270 -0
- package/align.mjs +205 -0
- package/batch.mjs +185 -0
- package/benchmark/README.md +573 -0
- package/benchmark/_freeze-trace.mjs +51 -0
- package/benchmark/_probe-all.mjs +209 -0
- package/benchmark/_probe.mjs +262 -0
- package/benchmark/changed.mjs +349 -0
- package/benchmark/fixtures.mjs +245 -0
- package/benchmark/harness.mjs +160 -0
- package/benchmark/profile.mjs +305 -0
- package/benchmark/run.mjs +793 -0
- package/benchmark/score.mjs +397 -0
- package/benchmark/sequences.mjs +246 -0
- package/benchmark/tasks/01-core.json +272 -0
- package/benchmark/tasks/02-primers.json +214 -0
- package/benchmark/tasks/03-restriction.json +181 -0
- package/benchmark/tasks/04-cloning.json +240 -0
- package/benchmark/tasks/05-plasmids.json +234 -0
- package/benchmark/tasks/06-analysis.json +535 -0
- package/benchmark/tasks/07-alignment.json +152 -0
- package/benchmark/tasks/08-records.json +87 -0
- package/benchmark/tasks/09-formats.json +171 -0
- package/benchmark/tools.mjs +231 -0
- package/benchmark/verifications.mjs +406 -0
- package/build/browser-api.mjs +56 -0
- package/build/cdp-drive.mjs +125 -0
- package/build/cdp.mjs +85 -0
- package/build/client-bundle-core.mjs +307 -0
- package/build/client-bundle.mjs +120 -0
- package/build/client-entry.mjs +577 -0
- package/build/panel-core.mjs +392 -0
- package/build/preset-patch.mjs +148 -0
- package/build/rebase-audit.json +585 -0
- package/build/rebase-audit.mjs +145 -0
- package/build/release-notes.mjs +106 -0
- package/build/release-preflight.mjs +748 -0
- package/build/render-samples.mjs +231 -0
- package/charts.mjs +1100 -0
- package/cloning.mjs +820 -0
- package/codon.mjs +449 -0
- package/composition.mjs +493 -0
- package/cordis.patch.yml +34 -0
- package/crispr.mjs +892 -0
- package/design.mjs +962 -0
- package/docs/capability-gap-survey.md +300 -0
- package/docs/client-panel.md +244 -0
- package/docs/client-pipeline-exploration.md +176 -0
- package/docs/history.md +363 -0
- package/docs/maintainer.md +103 -0
- package/docs/roadmap.md +273 -0
- package/docs/route-b.md +153 -0
- package/docs/rules.md +216 -0
- package/docs/v19-plan.md +354 -0
- package/docs/v20-plan.md +197 -0
- package/docs/workflow.md +662 -0
- package/fastq-qc.mjs +735 -0
- package/font-metrics.mjs +175 -0
- package/genbank.mjs +198 -0
- package/host.mjs +56 -0
- package/index.mjs +7603 -0
- package/lib/client.js +9113 -0
- package/lib.mjs +1520 -0
- package/logo.mjs +241 -0
- package/methylation.mjs +246 -0
- package/msa.mjs +523 -0
- package/multiplex.mjs +469 -0
- package/package.json +132 -4
- package/packages/molbio-panel/README.md +63 -0
- package/packages/molbio-panel/cordis.patch.yml +7 -0
- package/packages/molbio-panel/index.mjs +33 -0
- package/packages/molbio-panel/lib/client.js +9113 -0
- package/packages/molbio-panel/package.json +57 -0
- package/papers.mjs +168 -0
- package/pcr.mjs +267 -0
- package/phylo.mjs +1111 -0
- package/plasmid.mjs +330 -0
- package/plot.mjs +311 -0
- package/preset/install.mjs +185 -0
- package/preset/molbio-lab/agent.cordis.yml +354 -0
- package/preset/molbio-lab/preset.patch.yml +300 -0
- package/preset/molbio-lab/preset.yml +2 -0
- package/protein-structure.mjs +346 -0
- package/protein.mjs +378 -0
- package/records.mjs +112 -0
- package/sanger.mjs +269 -0
- package/seqio.mjs +126 -0
- package/silent-site.mjs +286 -0
- package/snapgene.mjs +335 -0
- package/svgio.mjs +494 -0
- package/svgpng.mjs +1170 -0
- package/taqman.mjs +621 -0
- package/test/benchmark-changed.mjs +194 -0
- package/test/benchmark-coverage.mjs +228 -0
- package/test/benchmark-profile.mjs +168 -0
- package/test/benchmark-score.mjs +185 -0
- package/test/charts.mjs +777 -0
- package/test/client-mount.mjs +381 -0
- package/test/client.mjs +427 -0
- package/test/contract.mjs +662 -0
- package/test/drift-probe.mjs +238 -0
- package/test/fixtures/benchmark-traces.json +557 -0
- package/test/fixtures/pUC118.dna +0 -0
- package/test/fixtures/pUC118.gb +67 -0
- package/test/map-card.mjs +347 -0
- package/test/panel-render.mjs +619 -0
- package/test/preset-health.mjs +532 -0
- package/test/preset-rows.mjs +69 -0
- package/test/release.mjs +514 -0
- package/test/slots-stub.mjs +95 -0
- package/test/smoke.mjs +4040 -0
- package/test/svgio.mjs +216 -0
- package/test/svgpng.mjs +834 -0
- package/view.mjs +149 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,2055 @@
|
|
|
1
|
+
# 变更日志(Changelog)
|
|
2
|
+
|
|
3
|
+
本仓库有两套版本号,请在提 issue 或对照本文档时区分:
|
|
4
|
+
|
|
5
|
+
- **包版本(`package.json` / npm / git tag)**:遵循 semver,如 `0.5.1`。
|
|
6
|
+
- **preset 版本目录(`dsh-molbio-tools-vN`)**:**已于 0.13.0 废弃并从仓库删除**(见该条)。
|
|
7
|
+
历史上 DSH 的 ESM 模块缓存按**文件 URL** 缓存,而 preset 里的插件行写成相对路径
|
|
8
|
+
`./plugins/dsh-molbio-tools-vN/index.mjs`,因此每次插件代码变更**必须新建目录**。
|
|
9
|
+
0.1.7-alpha.1 上那条相对路径**不再被 import**(preset 能挂载,却一个工具都加载不了);
|
|
10
|
+
改为按**包名**引用后模块缓存问题随之消失——没有拷贝,也就没有需要保持同步的版本目录。
|
|
11
|
+
下文历史条目里出现的 `vN` 目录保持原样,作为当时的记录。
|
|
12
|
+
|
|
13
|
+
## [0.20.0] — 2026-10-17(Cas 家族几何、qPCR 订购/配平、批量分析与静默位点突变;并入 0.19.1 硬化)
|
|
14
|
+
|
|
15
|
+
**工具数 58 → 60,benchmark 56 → 61 题(60/60 覆盖)。** 这一版把 0.19.1 计划的四项硬化直接并入,
|
|
16
|
+
并落地 roadmap 2.3 / 2.5 / 2.7 / 2.8 与 survey #8。Cas12a/Cas13 不是"再加一个 PAM 字符串":
|
|
17
|
+
原来的 `findProtospacers` 把 3' PAM、20 nt 和 3' 种子写死在几何里,对 Cas12a/Cas13 会给出**生物学上错误**的
|
|
18
|
+
坐标与评分,因此这一版把家族几何做成一张表,并补上了反向链脱靶定位的静默错误。
|
|
19
|
+
|
|
20
|
+
### 1. 并入的 0.19.1 硬化(原计划单发 patch,现随 0.20.0 一起)
|
|
21
|
+
|
|
22
|
+
- **Smith-Waterman 的 DP 矩阵上限**:`molbio_align` 在分配前拒绝超过 50,000,000 cells 的输入对,
|
|
23
|
+
而不是让两个几百 kb 的序列把宿主进程的内存吃光;工具描述同步说明"对齐窗口或改用
|
|
24
|
+
`molbio_verify_sanger`"。`test/smoke.mjs` 用 10 kb × 10 kb 的输入证明拒绝发生在分配之前。
|
|
25
|
+
- **SnapGene/PubMed XML 数值实体**:`�` 曾让 `String.fromCodePoint` 抛出 RangeError
|
|
26
|
+
并逃出整个解析器;现在越界码点与代理项按字面文本保留,合法实体(含星形平面)照常解码。
|
|
27
|
+
- **`molbio_sequence_logo` 的并发声明**:它写共享默认路径 `logo.svg`,之前却默认 `safe`;
|
|
28
|
+
现与其他画图工具一致声明 `safe: false`。
|
|
29
|
+
- **npm `files` 白名单**:不再整目录带 `benchmark`,改为 `benchmark/*.mjs`、`benchmark/tasks`、
|
|
30
|
+
`benchmark/README.md`,因此 `.gitignore` 里的本机 `benchmark/reports/` 不会再被打进包
|
|
31
|
+
(此前约 1.4 MB)。`test/contract.mjs` 直接禁止裸 `benchmark` 条目。
|
|
32
|
+
|
|
33
|
+
### 2. roadmap 2.5:Cas12a/Cas13 家族几何(以及反向脱靶定位缺陷)
|
|
34
|
+
|
|
35
|
+
- `crispr.mjs` 新增 `CAS_FAMILIES` 表:SpCas9(3' NGG / 20 nt / 3' 种子)、SaCas9
|
|
36
|
+
(3' NNGRRT / 21 nt)、Cas12a(**5' TTTV / 23 nt / 5' 种子**)、Cas13a(RNA / 3' H PFS / 28 nt)、
|
|
37
|
+
Cas13b(RNA / 5' D + 3' NAN / 30 nt)、Cas13d(RNA / 无 PFS / 22 nt)。PAM 校验现在按
|
|
38
|
+
**PAM 的哪一侧贴近 protospacer** 检查简并位,而不是一律要求首位为 N——`TTTV` 因此不再被误拒。
|
|
39
|
+
- **Cas13 不是 PAM**:它有 PFS **偏好**,工具报告 PFS、按缺失扣分并明确说明"不是硬性 PAM";
|
|
40
|
+
只有 `require_pfs=true` 才过滤。Cas13 的 `tm_celsius` 返回 `null`,因为对 RNA guide 报 DNA
|
|
41
|
+
NN Tm 是另一个分子;工具描述与渲染都写明这一点。
|
|
42
|
+
- **反向链脱靶定位是真 bug**:索引把每个 k-mer 记在 top-strand 起点,旧 `finalizeSites` 却对
|
|
43
|
+
反向命中减去 `guideLength-1`,使它们整体偏移 19 nt 并大多越界丢弃——正向 fixture 永远看不见。
|
|
44
|
+
现在两种链都从 `entry.position` 起算,PAM 按 5'/3' 侧与链向取 top-strand 窗口;新增的
|
|
45
|
+
smoke fixture 在 forward guide 与 reverse perfect copy 之间互指,断言反向脱靶 `start/end/pam`
|
|
46
|
+
与 0 mismatch。
|
|
47
|
+
- 新 benchmark:`crispr-cas12a`(5' TTTV、23 nt、坐标 9-31)与 `crispr-cas13`(RNA、PFS A、28 nt)。
|
|
48
|
+
`test/smoke.mjs` 另钉住 Cas12a/Cas13a 的几何、PFS 过滤与错误路径。
|
|
49
|
+
|
|
50
|
+
### 3. roadmap 2.8:TaqMan MGB 与订购 CSV
|
|
51
|
+
|
|
52
|
+
- `probe_chemistry: 'mgb'` 使用 13-20 nt、未修饰 NN Tm 48-60 °C 的窗口;**MGB 修饰的 Tm 不被
|
|
53
|
+
NN 模型预测**,因此报告的是未修饰 Tm,并以 `tm_model` 与 notes 明确要求向供应商复核。
|
|
54
|
+
MGB 不施加"探针 Tm 高于引物"的未修饰差值罚分(那会把每个 MGB 候选都罚掉)。
|
|
55
|
+
- 新增 `taqmanOrderRows` / `taqmanOrderCsv`:每条 assay 三行(F/R/P),列含 5'/3' 修饰、规模、
|
|
56
|
+
纯化与说明;MGB 默认 `NFQ-MGB`,5' 默认 FAM。`save_path` 写 CSV;不传则只返回结构化 order。
|
|
57
|
+
- 标准 chemistry 的默认值、排序与已发布数值保持不变(smoke 的 9 assays 与 pinned probe 仍绿)。
|
|
58
|
+
|
|
59
|
+
### 4. roadmap 2.7:多重 PCR 的退火温度与浓度配平建议
|
|
60
|
+
|
|
61
|
+
- `checkMultiplex` 新增 `annealing`:最低引物 Tm − `annealing_offset_c`(默认 3 °C),
|
|
62
|
+
给出 ±`annealing_window_c` 范围、最低 Tm 引物、Tm 离散度与是否超过警告阈值。
|
|
63
|
+
- 新增 `primer_concentrations` 与 `concentration_note`:默认 200 nM;当扩增子大小比 ≥ 2 时,
|
|
64
|
+
最短的降到 150 nM、最长的升到 300 nM,并明确这是**起始点**而非实测最优。
|
|
65
|
+
- 这些是清晰标注的规则性建议,notes 写明"不是热循环优化,需跑梯度"。
|
|
66
|
+
|
|
67
|
+
### 5. roadmap 2.3:批量分析 + CSV/TSV
|
|
68
|
+
|
|
69
|
+
- 新模块 `batch.mjs` 与新工具 `molbio_batch_analyze`:扫描目录(可递归)或显式 paths,复用
|
|
70
|
+
同一批 FASTA/FASTQ/GenBank/SnapGene 解析器,输出一张 CSV/TSV。
|
|
71
|
+
- 每行:格式、拓扑、条目数、总长、GC%、N%、特征数、平均长度;`analysis: orf` 加 ORF 数/最长
|
|
72
|
+
ORF,`analysis: enzymes` 加指定酶的位点数,`all` 两者都加。单个坏文件变成一行 `error`,
|
|
73
|
+
其余文件继续;文件/字节上限与 `truncated` 都显式报告。
|
|
74
|
+
- 拓扑按 GenBank/SnapGene 声明优先(环状质粒的位点计数不会被当成线性);无声明时才用
|
|
75
|
+
`circular` 参数。新 benchmark `batch-analysis` 同时读 `pUC118.gb` 与 `pUC118.dna`。
|
|
76
|
+
|
|
77
|
+
### 6. survey #8:静默酶切位点突变
|
|
78
|
+
|
|
79
|
+
- 新模块 `silent-site.mjs` 与新工具 `molbio_silent_site_mutagenesis`:只在同义密码子内枚举,
|
|
80
|
+
因此**蛋白序列按构造不变**;输出逐碱基变化、密码子前后与氨基酸、可直接喂给
|
|
81
|
+
`molbio_mutagenesis_primers` 的 mutation texts、以及突变后序列。
|
|
82
|
+
- 支持 remove/add、frame 1-3、可选 `host`(按 e_coli/yeast/human 密码子偏好排序)与区域限制;
|
|
83
|
+
只接受识别序列无简并碱基的酶(`GGTNACC` 这类直接拒绝而不是猜)。
|
|
84
|
+
- 新 benchmark `silent-site` 钉住 EcoRI 的 `C12T`(TTC→TTT,Phe)与突变后序列。
|
|
85
|
+
|
|
86
|
+
### 7. 版本、基线与文档
|
|
87
|
+
|
|
88
|
+
- 根包 `0.19.0 → 0.20.0`,面板 `0.1.2 → 0.1.3`(客户端产物因 crispr/taqman/multiplex 改动而变,
|
|
89
|
+
两个 npm 条目需分别发布)。
|
|
90
|
+
- **DSH 基线 `0.1.7-rc.1 → 0.2.0-rc.2`**:`.github/workflows/test.yml` 的 pin、roadmap/maintainer/
|
|
91
|
+
workflow 与 preset 头注释同步;`preset-health` 在 0.2.0-rc.2 上仍为 30 行全过(行清单无需改动)。
|
|
92
|
+
- README/preset 描述/工具表/benchmark 计数同步到 **60 tools / 61 tasks**;新增模块进入 npm
|
|
93
|
+
`files` 白名单与目录树。
|
|
94
|
+
- **未做**:`benchmark-score` 里 2 条 0.16.0 之前的冻结响应(`methylation-block`、`qpcr-primers`)
|
|
95
|
+
仍是 suspended;本次没有用真实模型重录,测试输出会继续点名这两条,而不是假装它们已通过。
|
|
96
|
+
|
|
97
|
+
### 8. 发布前完整 benchmark(2026-10-17,真实模型)
|
|
98
|
+
|
|
99
|
+
- **61 题首跑 55/61**:tool selection **61/61**,arguments **59/61**,final answer **57/61**;
|
|
100
|
+
12.7M tokens / 1747 s。
|
|
101
|
+
- **新功能任务全部通过**:`taqman-mgb`、`crispr-cas12a`、`crispr-cas13`、`silent-site`、
|
|
102
|
+
`batch-analysis`。`multiplex-panel` 在把"退火温度/浓度"明确写进指令后复跑通过。
|
|
103
|
+
- **4 个失败是断言写死格式,不是产品缺陷**:`plasmid-digest`(模型写 `2,840`)、
|
|
104
|
+
`protein-props`(`3,935`)、`codon-optimize`(`EcoRI-free`)、`chart-volcano`
|
|
105
|
+
(任务要求 `de.svg`,断言却写 `volcano.svg`)。修正后 `--replay` 四题全部转为 PASS。
|
|
106
|
+
- **1 个真实可用性缺陷**:`molbio_extract_region` 的 schema 广告 `sequence`,但
|
|
107
|
+
`resolveVector` 只读 `vector`,于是文档里写的参数被静默忽略、调用报
|
|
108
|
+
"provide either vector or vector_path"。现在 `vector`/`sequence`/`reference` 三个已声明别名
|
|
109
|
+
都接受,smoke 增加回归断言。该任务的真实模型复跑因 DSH 沙箱对 headless 子进程的审批限制
|
|
110
|
+
未完成,因此发布时的实测口径是 **60/61**(这一题以 smoke 回归 + 修复代码为准,而不是假装已跑)。
|
|
111
|
+
- benchmark 工具本身未改;全量首跑使用了一次性更宽沙箱来启动 headless DSH 子进程。
|
|
112
|
+
|
|
113
|
+
## [0.19.0] — 2026-10-11(环形树的旋转多行标签;比对后处理套件;持续交付)
|
|
114
|
+
|
|
115
|
+
**工具数 57 → 58。** 这一版是 [roadmap](docs/roadmap.md) 2.4 与 2.2 两条候选的落地:
|
|
116
|
+
前者修一个**画面正确性缺陷**,后者补一个**比对之后该问的问题**;另外把发布本身做成了 CD。
|
|
117
|
+
|
|
118
|
+
### 1. 环形/扇形布局的长叶名曾整行画出画布(roadmap 2.4)
|
|
119
|
+
|
|
120
|
+
**症状**:`molbio_phylogenetic_tree` 的 `rectangular` 布局早在 v20 就把长叶名折成
|
|
121
|
+
`<tspan>` 多行,而 `circular` / `fan` 一直是**整行**旋转着画出去的。位置靠
|
|
122
|
+
`labelLength * leafFontSize * 0.62` 这个**猜出来的**余量决定,名字比它长就直接跑到画布
|
|
123
|
+
外面——没有任何裁剪、也没有任何提示,只是墨迹落在框外。
|
|
124
|
+
|
|
125
|
+
**修法(三层)**:
|
|
126
|
+
|
|
127
|
+
- `font-metrics.mjs` 新增 `rotatedDepth(width, angle, height)`:一段文字绕基点转 θ 后
|
|
128
|
+
在**原竖直方向**上占的深度是 `W·|cos θ| + H·|sin θ|`。这个数是"标签还剩多少地方"
|
|
129
|
+
的度量,也是**折行预算与旋转之间唯一正确的换算**。
|
|
130
|
+
- `svgio.mjs`:`textSpanLines` 增加 `rotate`——transform 加在**父 `<text>`** 上,而不是
|
|
131
|
+
`<tspan>` 上。这不是选择:光栅化器把 `<text>` 的 transform 应用到它排出的每一行,而
|
|
132
|
+
对 `<tspan>` 自带的 transform 是**计入 unsupported 并上报**(不猜位置)。`wrapTextLines`
|
|
133
|
+
增加 `depth` / `angle`,把深度预算按 `1/cos θ` 换算回基线宽度。
|
|
134
|
+
- `phylo.mjs` 的径向分支:折行 → 以**锚点自身**为旋转中心旋转整块 → 用**四个角是否都
|
|
135
|
+
落在绘图矩形内**做精确判定 → 不满足就逐级缩小字号。半径不再是面板的固定比例,而是
|
|
136
|
+
**由实测的标签深度反推**(`halfExtent - deepestLabelDepth`)。
|
|
137
|
+
|
|
138
|
+
**实现时才暴露的四个偏差**(计划里没有的,每一个都是量出来的、不是想出来的):
|
|
139
|
+
|
|
140
|
+
1. **余量公式的坐标系搞错了**。"标签沿半径还有多少地方"是**旋转后局部 x 轴上的参数**,
|
|
141
|
+
不是页面上的像素距离;把两者混着算,就会在接近竖直的方向算出 1.4 px 而真实答案是
|
|
142
|
+
149 px——于是每个折行预算都被夹到下限,长名字变成 6 px 的窄列。
|
|
143
|
+
2. **`axisDistanceToEdge` 的分子写反了**:`dy < 0`(朝上)时用了**下边界**的距离。这条
|
|
144
|
+
与上面那条同源,症状也一样。
|
|
145
|
+
3. **折行宽度不能随角度变**。试过三种角度相关规则,全部实测失败并被记录在代码注释里:
|
|
146
|
+
按深度预算折(有的角度窄列、有的角度长行)、按射线余量折(竖直方向射线极长,50 字符
|
|
147
|
+
名字折成九行柱)、以及最小化 `W·|cos θ| + H·|sin θ|`(数学上的极值,也是窄列)。
|
|
148
|
+
眼睛要的是**每个角度都差不多长的几行**,所以宽度是固定的,随角度变的只是**行数**,
|
|
149
|
+
由角点判定与缩字循环处理。
|
|
150
|
+
4. **旋转文字的墨迹比几何宽度宽约 1 px**。光栅化器用 `max(0.7, size·0.11)` 宽的圆角描边
|
|
151
|
+
画字形,所以预算必须再留 `strokeMargin + LABEL_INSET`——这是**逐标签量像素**发现的,
|
|
152
|
+
靠推公式发现不了。
|
|
153
|
+
|
|
154
|
+
**回归守卫**(`test/svgpng.mjs`,22 → 23 项):12 个叶、50 字符名字的树,断言每个标签都是
|
|
155
|
+
**多行** `<tspan>` 块、每行共享同一个基线 x、非水平方向的标签真的带 rotate 与正确的
|
|
156
|
+
anchor,并且**没有任何标签墨迹进入面板边框带**——后者与"只有面板装饰"的同结构文档比对,
|
|
157
|
+
所以标题和边框不会被误当标签。**这条守卫能证明自己会失败**:把 anchor 全部强制成
|
|
158
|
+
`start`(v19 的症状)即红。同一批改动里还试过"关掉缩字循环"与"去掉余量内缩"两种变异,
|
|
159
|
+
前者不红——因为 12 个叶的 fixture 在 11 px 本来就放得下——于是把 fixture 从 8 个叶改成
|
|
160
|
+
12 个,并改用请求 15 px 的对照来钉住"能放下就不缩"这一侧。
|
|
161
|
+
|
|
162
|
+
### 2. 新增 `molbio_alignment_postprocess`:比对之后该问的四件事(roadmap 2.2)
|
|
163
|
+
|
|
164
|
+
新模块 `align-post.mjs`(纯计算、零依赖,IUPAC 一律从 `lib.mjs` 取,**不新增第八份字母表**):
|
|
165
|
+
|
|
166
|
+
- **共识**,三种口径由 `consensus_mode` 选:`plurality`(默认,得票达
|
|
167
|
+
`consensus_threshold` 就给该残基,否则给观察到的碱基的并集码)、`union`(一律并集码)、
|
|
168
|
+
`strict`(达阈值给残基,否则给 `N`——混合位点绝不能伪装成一个碱基)。**平票取字母序
|
|
169
|
+
首个**,与 `conservationAnalysis` 一致,是确定性约定而**不是**并集码。
|
|
170
|
+
- **按缺口比例修剪**:`max_gap_fraction`(默认 0.5)以上的列被报出,列号是**修剪前的
|
|
171
|
+
原始 1-based 坐标**,所以与输入仍然对得上;只报"哪些列会走、还剩多少列",
|
|
172
|
+
**不返回修剪后的比对**。
|
|
173
|
+
- **两两同一性矩阵**:分母是"两行中至少一行有残基"的列,**双缺口列被排除**。
|
|
174
|
+
- **逐列覆盖度**:非缺口比例、均值/最小值、完全覆盖列数、低于 `coverage_threshold`
|
|
175
|
+
(默认 0.8)的列清单。
|
|
176
|
+
|
|
177
|
+
**为什么另起一个工具而不是扩 `molbio_conservation`**:其中两件事需要 `msa.mjs` 刻意
|
|
178
|
+
**没有**的语义,改动它就会悄悄挪动已发布版本报出的数字。文档与渲染文本都点名了这一点:
|
|
179
|
+
|
|
180
|
+
| 统计量 | 本工具 | `molbio_conservation` |
|
|
181
|
+
| --- | --- | --- |
|
|
182
|
+
| 同一性分母 | 非双缺口列 | **全部列** |
|
|
183
|
+
| 同一 fixture 的均值 | **37.17%** | **32%** |
|
|
184
|
+
| 逐列 identity 均值 | —— | 70.34%(第三个不同的统计量) |
|
|
185
|
+
|
|
186
|
+
`test/smoke.mjs` 把这三个数**并排钉住**,附逐对手算表(1v2 80、1v3 60、1v4 40、2v3 75、
|
|
187
|
+
3v4 66.67…),因此"两套口径合并"会红。超 50 条序列时矩阵**省略而非截断**,摘要仍覆盖每一对。
|
|
188
|
+
|
|
189
|
+
**benchmark**:新增 `alignment-post` 任务(56 题,58/58 覆盖)。它刻意钉住**两个阈值互相
|
|
190
|
+
独立**:fixture 里 103 列低于 0.8 覆盖度,但缺口比例只有 0.25,**默认阈值下一列都不修剪**
|
|
191
|
+
——把两者混为一谈的模型会报出一个被修剪的比对。期望值全部由 `benchmark/_probe.mjs` 实测,
|
|
192
|
+
两条离线门(`--offline` 与 `benchmark-coverage`)全绿。
|
|
193
|
+
|
|
194
|
+
### 4. 持续交付(CD):推一个 tag 就发完
|
|
195
|
+
|
|
196
|
+
> **首发必须手动做一次,这是 npm 可信发布的固有限制,不是配置错误。** 推 tag 跑 CD 会以
|
|
197
|
+
> `npm error code ENEEDAUTH` / `need auth` 失败——Trusted Publisher 是**逐包**在 npmjs.com 上
|
|
198
|
+
> 配置的,包名还不存在时 npm 没有可认证的对象。**0.19.0 的第一次真实运行就是这样失败的。**
|
|
199
|
+
> 更值得记的是**当时的 preflight 已经检测出"包不存在",却只打印一条 note 就放行**:一个检测到
|
|
200
|
+
> 条件却不据此行动的守卫不是守卫,只是一行日志。现在 `publishPlan()` 把每条注册表状态落成
|
|
201
|
+
> `publish` / `skip` / `refuse` 三个动作,首发与"npm 已有更新版本"都是 `refuse`,workflow 的发布
|
|
202
|
+
> 步骤读 `steps.preflight.outputs.publish_*`,于是**在碰注册表之前**停下并打印准确的首发步骤。
|
|
203
|
+
> 完整顺序(含必要时如何安全地强制移动 tag)见 [workflow.md](docs/workflow.md) 第 4 节。
|
|
204
|
+
|
|
205
|
+
`.github/workflows/release.yml` 把「发布流程」第 4 节那套手工步骤变成流水线。**触发条件是推
|
|
206
|
+
`v*` tag**,不是推 main——版本号就是"这是一次发布"的决定,而 tag 是唯一不可能被顺手推出去的东西。
|
|
207
|
+
|
|
208
|
+
流水线在**被打 tag 的那个提交上**重跑全套 `npm test`(tag 可以指向任何提交,而 `test.yml` 是被
|
|
209
|
+
并行触发的,所以"测试过了"不是这个 job 能假设的前提),再过发布前置检查,然后**分两步**发布
|
|
210
|
+
两个 npm 条目(在仓库根跑两次 `npm publish` 会把根包发两遍——这是 v20 已经记录过的坑),
|
|
211
|
+
最后把 CHANGELOG 里**该版本那一段**抽出来建成 GitHub Release。
|
|
212
|
+
|
|
213
|
+
**凭据是没有凭据**:用 npm 的 **Trusted Publishing**——job 出示 GitHub OIDC token
|
|
214
|
+
(`id-token: write`),npm 换成一张**限于本仓库 + `release.yml`** 的短期凭证。仓库里不存在任何
|
|
215
|
+
长期密钥。代价是 npm 必须先知道这个包,所以**两个包的首次发布必须由人手动做一次**,然后在
|
|
216
|
+
npmjs.com 上为每个包配置 Trusted Publisher。三条坑都写进了守卫或文档:
|
|
217
|
+
|
|
218
|
+
- **设了 `NODE_AUTH_TOKEN` 反而会关掉可信发布**(npm 见到 token 就不走 OIDC),所以 workflow
|
|
219
|
+
**故意不设**它,`test/release.mjs` 直接断言文件里没有对它的赋值、也没有任何 `secrets.` 引用;
|
|
220
|
+
- **首发时 `npm publish` 的报错是 404**,看不出真正原因——所以 `release-preflight.mjs` 先问注册表
|
|
221
|
+
"这个包存在吗",包不存在就明确说"这是首发,必须手动做一次";
|
|
222
|
+
- **面板包的版本线是独立的**(v20 计划:`0.1.1 → 0.1.2`),但两者共用**同一份**客户端源码,所以
|
|
223
|
+
浏览器半一改面板产物就变,版本不动就会被 npm 以"同版本不可重复发布"拒绝。preflight 因此把
|
|
224
|
+
**已发布版本的 `lib/client.js`** 通过注册表 tarball 抓下来,在进程内解 tar,与工作区逐字节比对。
|
|
225
|
+
|
|
226
|
+
新增 `build/release-preflight.mjs`(只检查**只有发布时才会问**的问题;产物新鲜度与 `files`
|
|
227
|
+
白名单已经在 `test/contract.mjs` 里,不重复)、`build/release-notes.mjs`(发布说明从 CHANGELOG
|
|
228
|
+
派生,不存在第二份描述)与 `test/release.mjs`(把每个决定函数**用变异输入驱动**:tag 写错、
|
|
229
|
+
版本漂移、CHANGELOG 缺段、面板版本没 bump、tar 读错文件、根变成 workspace、workflow 里出现
|
|
230
|
+
token——见「守卫必须被证明会失败」)。
|
|
231
|
+
|
|
232
|
+
**刻意不做**:自动版本号(版本是人的决定)、自动回滚(npm 的 `unpublish` 有 72 小时窗口且会
|
|
233
|
+
破坏已装用户;正确做法是发更高版本,必要时 `npm deprecate` 标记坏版本)。
|
|
234
|
+
|
|
235
|
+
### 3. 文档漂移与平台(0.18.4 的同类,本次一并结清)
|
|
236
|
+
|
|
237
|
+
- **`font-metrics.mjs` 被两处现行规则文档写成"宿主侧专用、不进客户端产物"——它明明在产物里**。
|
|
238
|
+
`build/client-bundle.mjs --check` 列出它在 19 个模块之中,`test/contract.mjs` 断言它
|
|
239
|
+
**必须**在模块图里,而它自己的头注释就写着"This module IS part of the client bundle"。
|
|
240
|
+
已改正 `docs/rules.md` 第 5 节(并写明照错的声明去"修好"它就会删掉一个面板必需的模块)、
|
|
241
|
+
`docs/maintainer.md` 与 `README.md` 的目录树。历史文档(`CHANGELOG`、`v19-plan`、
|
|
242
|
+
`v20-plan`)保持原样。
|
|
243
|
+
- `docs/maintainer.md` 的包版本 `0.18.3` → `0.19.0`。
|
|
244
|
+
- **CI 的 node 矩阵、`engines.node` 与该处注释三处一致**,都是 26(本机开发版本),
|
|
245
|
+
不再是"矩阵测下限、注释说下限、engines 写下限"的三方各说各话。
|
|
246
|
+
- 工具数变化同步的四处(README 表与计数、`preset.yml` 描述、`index.mjs` 顶部注释、
|
|
247
|
+
`agent.cordis.yml` 的 `tool-molbio` 注释)与 benchmark 任务数(55 → 56)一并更新。
|
|
248
|
+
|
|
249
|
+
## [0.18.4] — 2026-10-10(清掉余下的债:文档漂移、benchmark 层的死角、套件与平台)
|
|
250
|
+
|
|
251
|
+
**工具数不变(57)。** 这一版收尾:没有新的工具缺陷,全是"记录与事实不符"和"守卫够不着"。
|
|
252
|
+
|
|
253
|
+
### 文档漂移(每一处都曾把错的路径或错的数字递给读者)
|
|
254
|
+
|
|
255
|
+
- README 里那段"改注释的措辞规则"**删掉**:它描述的 `test/svgpng.mjs` 子串搜索早已不存在
|
|
256
|
+
(0.18.0 改成结构性断言),roadmap 3.5 也早就写着"该规则已从 README 移除"——只有 README 自己
|
|
257
|
+
还留着它,而且与它上面三段的说法相反。
|
|
258
|
+
- `benchmark/tasks.json` 自 0.15.x 起就是 `benchmark/tasks/*.json`:**七处**引用(benchmark/README、
|
|
259
|
+
workflow、maintainer、README、`.gitignore`、`run.mjs`、两个 `_probe`)已改正。
|
|
260
|
+
- 任务数 `52 条` → `55`(roadmap 3.2);`52 个工具` → `57`(client-panel 两处、`client-bundle.mjs`
|
|
261
|
+
的注释);maintainer 的"rules.md 第 6 节" → 第 7 节(v0.14.0 拆文档时的错位)。
|
|
262
|
+
- `docs/workflow.md` 的测试清单补上 `test/charts.mjs`(它从 0.17.0 起就在 `npm test` 里)。
|
|
263
|
+
- 历史文档(CHANGELOG、v20-plan、route-b)**保持原样**:它们记录的是当时的事实,改掉就是伪造记录。
|
|
264
|
+
|
|
265
|
+
### benchmark / 测试层的死角
|
|
266
|
+
|
|
267
|
+
- **`bench:profile:check` 曾被并入 `npm test`,CI 两个平台随即都红在 `Run the suite`——已回退。**
|
|
268
|
+
它比较的是**本机生成**的 profile(`$DSH_HOME/profiles/molbio-bench`),而 CI 从未生成过它,仓库也
|
|
269
|
+
载不了它(里面是绝对路径与指向本 checkout 的 `file:` 依赖)。`test/benchmark-profile.mjs` 的头注释
|
|
270
|
+
早就写着"**刻意不检查磁盘上的 profile 目录**:那是一条由另一个命令生成的、用户自己的路径"——
|
|
271
|
+
我没先读它就动手。顺带把这个失败的两种状态拆开:以前"没生成过"和"生成了但过期"都打印
|
|
272
|
+
`profile STALE`,现在前者打印
|
|
273
|
+
`profile ABSENT (this compares the profile THIS machine generated — not part of npm test)`,
|
|
274
|
+
并把原因写进 `benchmark/README.md`,免得下一个人再试一遍。
|
|
275
|
+
- **两个孤儿 builder 删掉**(`plasmid-map`、`restriction-map`:任务改名后留下的,永远不会被调用,
|
|
276
|
+
也就永远不再验证任何东西;其中一个还带 `type: 'FIXTURES.CDS'` 这个字面量笔误)。新增**反向检查**:
|
|
277
|
+
从源码读出 builder id,断言每一个都对应真实任务。
|
|
278
|
+
- **夹具前提有了守卫**:`genbank-parse` 让模型比较 `pUC118.gb` 与 `pUC118.dna`,而这两个文件是各自
|
|
279
|
+
手写的(旧注释声称由 `benchmark/_make-genbank.mjs` 生成——**该文件根本不存在**)。现在断言两者的
|
|
280
|
+
序列逐碱基相同(实测 3162/3162),前提被破坏时会红。
|
|
281
|
+
- **`--offline` 现在真的听 `--tools` / `--changed` / `--tier`**:以前这些标志被完全忽略——看起来
|
|
282
|
+
缩小了检查范围,实际整套照跑。默认仍是**全量**(`--offline` 的语义是"校验全套期望值"),
|
|
283
|
+
显式 `--tier core` 才是 14 题。
|
|
284
|
+
|
|
285
|
+
### 代码
|
|
286
|
+
|
|
287
|
+
- `protein.mjs` 的避让酶表是 `lib.mjs` 酶表的**第二份副本**(81 条识别位点,逐条比对全部一致)
|
|
288
|
+
——现在只留**酶名清单**,位点从 `lib.mjs` 推导。与 IUPAC 那次同理:能收敛是因为先证明了等价。
|
|
289
|
+
- `plot.mjs` 四处对用户输入的校验抛的是**裸 `Error`**(rules.md 第 7 节要求 `MolbioInputError`),
|
|
290
|
+
已统一。
|
|
291
|
+
- 删掉五处**死导出**(`BUFFER_TABLE_ENZYMES`、`METHYLATION_MARKS`、`DEFAULT_PROTOCOLS_FILE`、
|
|
292
|
+
`DEFAULT_EXPERIMENTS_FILE`、`isUnambiguous`):定义之后全仓无任何引用,连自身文件都不用。
|
|
293
|
+
- `test/smoke.mjs` 的 MSA 不变式("每一行去掉 gap 等于输入")此前只断言**一行一个夹具**,
|
|
294
|
+
现在对三组夹具的**每一行**断言,并补了长度差更大的输入。
|
|
295
|
+
|
|
296
|
+
### 套件与平台
|
|
297
|
+
|
|
298
|
+
- **套件不再依赖宿主分隔符**:`test/smoke.mjs` 184 处、`test/map-card.mjs` 3 处宿主路径字面量收敛为
|
|
299
|
+
两个助手(`at()` 造工具参数、`shown()` 造工具 `join` 之后的形态),根由 `process.platform` 决定。
|
|
300
|
+
验证方式(本机不是 Linux):把根临时强制为 `/tmp` 后两个套件**仍然全绿**——断言现在与分隔符无关,
|
|
301
|
+
而不是"在 Windows 上碰巧对"。
|
|
302
|
+
- **CI 同时跑 `ubuntu-latest` 与 `windows-latest`**(此前只有 Windows,原因就是上面那条)。
|
|
303
|
+
`DSH_HARNESS_ROOT` 的推导改由 **Node 做路径运算**(Windows 是 `<prefix>/node_modules`,POSIX 是
|
|
304
|
+
`<prefix>/lib/node_modules`),所以同一个步骤在两个平台都成立。
|
|
305
|
+
- `lib.mjs` / `crispr.mjs` 的工作区副本本来就是 CRLF(仓库行尾混杂),已统一为 LF,与
|
|
306
|
+
`.gitattributes` 的 `eol=lf` 一致。
|
|
307
|
+
|
|
308
|
+
## [0.18.3] — 2026-10-10(余下的静默错误;IUPAC 字母表收敛为一处)
|
|
309
|
+
|
|
310
|
+
**工具数不变(57)。** 三条解析路径不再"数字看着对、含义对不上",一个坐标约定被写清,
|
|
311
|
+
一张手抄了七遍的参考表收敛为一处。
|
|
312
|
+
|
|
313
|
+
### 1. `molbio_align` 的跨度:文档说 1-based,字段是 0-based
|
|
314
|
+
|
|
315
|
+
`a_start`/`b_start` 是 **0-based 偏移**,`a_end`/`b_end` 是 **0-based 排他**结尾,而工具描述写
|
|
316
|
+
"aligned spans (1-based)",schema 里四个字段**没有任何说明**。渲染文本用 `+1` 补偿,所以读文本
|
|
317
|
+
的人不会踩到,读字段的人会。现在描述与四个字段说明都写明约定,两侧都有断言(字段 `0/4`、
|
|
318
|
+
文本 `sequence2 1-4`)。顺带删掉 `pos1` 一个**只会产生 NaN** 的回退:它引用的正是那个"为空"的
|
|
319
|
+
值;三种差异类型都必然带 `trace_pos`,所以那段代码既不可达又有害。
|
|
320
|
+
|
|
321
|
+
### 2. `molbio_golden_gate` 推荐自己会拒绝的酶
|
|
322
|
+
|
|
323
|
+
参数描述把 **SapI / BspQI / LguI** 列为可用,而模拟器对 3 bp overhang 一律拒绝(它只搭标准
|
|
324
|
+
4 bp 接头);连**报错信息本身**也在推荐 SapI/BspQI。模型照描述选 SapI 就是死路。两处都已修正,
|
|
325
|
+
并新增一条**推导式守卫**:从描述里读出它宣称的酶名,逐个断言"在酶表里、且 overhang 为 4"
|
|
326
|
+
——把 SapI 加回描述即失败。
|
|
327
|
+
|
|
328
|
+
### 3. SnapGene:序列包截断曾静默变短
|
|
329
|
+
|
|
330
|
+
DNA 包自带长度,但代码只检查"文件还没结束"。文件短于声明长度时 `slice` **静默截断**,读出一个
|
|
331
|
+
更短的序列——序列读取器最不该悄悄做的事。现在直接报 "the DNA packet runs past the end of the
|
|
332
|
+
file"。尾部可选 XML 包的宽松处理保留并注明理由(写入器会留尾部填充),并说明它**只能**丢掉
|
|
333
|
+
可选注解、丢不到序列。
|
|
334
|
+
|
|
335
|
+
### 4. Sanger:质量值可能与碱基错位
|
|
336
|
+
|
|
337
|
+
`readBases` 会对碱基串 `.trim()`,`readQualities` 却原样返回数组——PBAS2 前部若有填充,其后
|
|
338
|
+
**每个质量值都错一位**,而低质量过滤与 `verdict` 正是用这些数字算的。现在两者**一趟读出**:
|
|
339
|
+
填充连同它的质量一起跳过;只有"每个碱基都有对应质量"时才返回质量(错位的质量比没有更糟)。
|
|
340
|
+
回归测试用带前导填充的合成 ABIF 钉住(旧实现读出 39.9 而不是 40)。另外 `.fasta` trace 里有多条
|
|
341
|
+
记录时不再把它们拼成一条,而是明确拒绝。
|
|
342
|
+
|
|
343
|
+
### 5. GenBank:被丢弃的特征与自相矛盾的长度
|
|
344
|
+
|
|
345
|
+
位置读不出的特征一直**静默消失**(现计入 `features_skipped`);`LOCUS` 声明长度与 `ORIGIN`
|
|
346
|
+
不符时工具静默采信序列长度(现回 `length_declared`,并在渲染文本里说明用的是哪一个)。
|
|
347
|
+
|
|
348
|
+
### 6. 客户端:远端地图标记解析失败不再是一片空白
|
|
349
|
+
|
|
350
|
+
`mapCardView` 的边界检查只看首尾标签,标记真解析不了时卡片**保留着 "N feature(s)" 的标题、
|
|
351
|
+
下面空着**,没有任何说明。现在显示原因(`test/map-card.mjs` 用既有的 DOMParser 桩钉住)。
|
|
352
|
+
|
|
353
|
+
### 7. IUPAC 字母表:七份编码收敛为一处
|
|
354
|
+
|
|
355
|
+
`lib.mjs`、`pcr.mjs`、`crispr.mjs`、`logo.mjs`、`msa.mjs`(三张表)各自维护同一张字母表——
|
|
356
|
+
数组、字符串、Set、位掩码、索引映射五种形态。取值当时完全一致,但这正是 0.16.0 甲基化表
|
|
357
|
+
(15/29 条与来源不符)的同型结构。现在只有 `lib.mjs` 定义它,并导出派生形态
|
|
358
|
+
(`IUPAC_EXPAND` / `IUPAC_MASKS` / `MASK_SYMBOLS` / `BASE_INDEX` / `BASE_ORDER`),其余模块
|
|
359
|
+
一律 import。测试钉住位序(A=1 / C=2 / G=4 / T=8)以及"每个符号都能经掩码往返",防止一次编辑
|
|
360
|
+
同时改变五个模块对同一符号的理解。
|
|
361
|
+
|
|
362
|
+
**刻意未做**:`plot.mjs` 与 `svgio.mjs` 的 `escapeXml` / `niceStep` / `FIGURE_FONT` 重复**仍然
|
|
363
|
+
保留**——它没有 IUPAC 那样的等价性证明(旧输出被像素断言与 benchmark 期望值钉着),
|
|
364
|
+
roadmap 3.4 早就写明"等下一次不得不动 `plot.mjs` 时一并做"。同一条理由也写在新的 roadmap 3.6
|
|
365
|
+
里,解释为什么套件里 187 处宿主路径字面量**不**在没有 Linux runner 可验证时盲改。
|
|
366
|
+
|
|
367
|
+
## [0.18.2] — 2026-10-10(面板把渲染器的拒绝显示出来;补齐失效的守卫与缺失的截断标志)
|
|
368
|
+
|
|
369
|
+
**工具数不变(57),参数不变。** 这一版来自对**守卫层自身**的复核:规则第 9 条说"没人见过
|
|
370
|
+
失败的守卫不算守卫",而其中几条**永远不可能失败**,另有两条**声称**有突变实验却没有任何
|
|
371
|
+
探针能驱动它们。
|
|
372
|
+
|
|
373
|
+
### 1. 面板:渲染器拒绝的东西以前是一片空白,现在是消息
|
|
374
|
+
|
|
375
|
+
**症状**:选一个标注超过 200 个特征的文件,面板区域**什么都不显示**。`renderPlasmidMap`
|
|
376
|
+
对超过 200 个特征抛 `too many features (limit 200)`,而异常从 `useEffect` 里逃了出去——
|
|
377
|
+
既不进面板的错误态,也没有任何解释。
|
|
378
|
+
|
|
379
|
+
**修法**:`build/client-entry.mjs` 的 SVG 副作用把渲染器调用包进 try/catch,异常文本交给
|
|
380
|
+
面板错误态显示;`test/panel-render.mjs` 新增用例用一份 201 特征的 GenBank 记录钉住它。
|
|
381
|
+
(顺带查明:同一处的特征表 `slice(0, 200)` **永远不会生效**——地图上限 200 会先抛错。
|
|
382
|
+
它保留,并注明这是表格自己的边界,不是渲染器的规则。)
|
|
383
|
+
|
|
384
|
+
### 2. 四条不可能失败的守卫
|
|
385
|
+
|
|
386
|
+
- `test/client-mount.mjs`:`const before = 0; assert.equal(before, 0)` 是常量与自身比较。
|
|
387
|
+
改为**统计注册次数**并断言为 0。突变实验(让面板宿主半去注册一个工具)现在以
|
|
388
|
+
`the panel host half registers no tool` 失败。
|
|
389
|
+
- 同一文件:profile 缺 `package.json` 时**静默降级**成不含本包的 bundle 列表,于是 v0.15.2
|
|
390
|
+
"两个侧边栏 tab 消失"的复现守卫整段不跑,摘要仍打印 "checks passed"。现在摘要明确写出
|
|
391
|
+
`EXCEPT the profile selection … did NOT run`。
|
|
392
|
+
- `test/benchmark-changed.mjs`:无 git 时把 POSIX 路径断言整段跳过、仍打印 passed。现在该
|
|
393
|
+
分支明确报告"这条断言没有跑",摘要也带上这句。
|
|
394
|
+
- `test/contract.mjs`:"负向对照"断言的是产物里不含 `function thisDeclarationDoesNotExist`
|
|
395
|
+
——一个不可能出现的字面量。改用**真实存在**的声明做对照:光栅化器(`svgpng.mjs`)不在
|
|
396
|
+
浏览器模块图里,它的最长声明必须**缺席**于产物。
|
|
397
|
+
|
|
398
|
+
### 3. 两处手工维护的计数改为推导
|
|
399
|
+
|
|
400
|
+
`test/benchmark-changed.mjs` 的 `57`、`test/contract.mjs` 的 `2` 改为从源推导(声明的工具名、
|
|
401
|
+
bundle 自己声明的卡片键列表)。突变实验:让两个工具同名 → `every declared tool must be
|
|
402
|
+
attributed to its modules` 失败,并指出是哪个工具。`contract.mjs` 里"15 个图片工具"**保持
|
|
403
|
+
硬编码**——它没有可推导的独立真相来源("是不是图片工具"没有在任何地方申报),那里的注释
|
|
404
|
+
已写明这是刻意的。
|
|
405
|
+
|
|
406
|
+
### 4. 规则第 9 条点名的两项,现在真的有突变实验
|
|
407
|
+
|
|
408
|
+
`preset-health.mjs` 的 specifier 检查与 order 撞号检查此前**无法被任何探针驱动**
|
|
409
|
+
(`drift-probe.mjs` 只导入 `compositionDrift`/`toolCountDrift`),而 rules.md 第 9 条声称它们
|
|
410
|
+
"已用突变实验验证"。两个判定被提取为纯函数并导出,`drift-probe` 现在驱动它们:相对
|
|
411
|
+
specifier 被报出、`config:` 下的 order 被正确读取(而非声明行的 `preset-<id>`)、与已发布
|
|
412
|
+
preset 撞号即失败。探针捕获的变异数从 **11 增至 14**。
|
|
413
|
+
|
|
414
|
+
### 5. 缺失的截断标志
|
|
415
|
+
|
|
416
|
+
`molbio_verify_sanger` 的氨基酸后果列表一直**静默截断在 50 条**:上限存在,输出里没有任何
|
|
417
|
+
迹象。现在返回 `aa_changes_truncated`,渲染文本也写出"还有更多未列出"。测试刻意造出约 100
|
|
418
|
+
处替换来钉住 `true` 这一侧,而不只是断言 `false`。
|
|
419
|
+
|
|
420
|
+
### 6. CI 的 harness 解析(workflow 第一次运行就红在这里)
|
|
421
|
+
|
|
422
|
+
**症状**:`.github/workflows/test.yml` 首次运行 —— GitHub 报告 **All jobs have failed**。
|
|
423
|
+
|
|
424
|
+
**根因**:`test/client-mount.mjs` 把 harness 路径**写死**成
|
|
425
|
+
`%APPDATA%\npm\node_modules\@deepseek-ai\dsh`,那只是普通 Windows 用户安装的位置;
|
|
426
|
+
`npm install --global` 在 CI runner 上装到 **Node 工具链的 prefix** 下,于是这个文件在
|
|
427
|
+
**第一条断言**("the harness packages must be installed at …")就死了,还没检查任何东西。
|
|
428
|
+
本地复现:把 `APPDATA` 指到别处即得到同一条断言失败。
|
|
429
|
+
|
|
430
|
+
**修法**:
|
|
431
|
+
|
|
432
|
+
- `client-mount` 改用与其它套件相同的解析器(`benchmark/harness.mjs` 的 `findHarnessRoot`),
|
|
433
|
+
它先问 `npm prefix -g`、并尊重 `DSH_HARNESS_ROOT`;解析不到就以该项目既有的约定
|
|
434
|
+
`process.exit(2)` 明确报告"无法检查",而不是抛一条令人费解的断言。
|
|
435
|
+
- workflow 在安装后**显式导出** `DSH_HARNESS_ROOT`(来自 `npm prefix -g`),并打印
|
|
436
|
+
`npm prefix -g` / 解析结果 / 是否存在;harness 没装在 npm 自称的 prefix 下时直接抛错。
|
|
437
|
+
diagnostic 那一步现在能单独回答"是环境没装好"还是"守卫发现了问题"——下一次红灯不必翻日志猜。
|
|
438
|
+
|
|
439
|
+
## [0.18.1] — 2026-10-10(两个解析器不再把多段特征压平;修 `^` 交界点的坐标)
|
|
440
|
+
|
|
441
|
+
**工具数不变(57),参数不变;`molbio_parse_genbank` / `molbio_parse_snapgene` 的输出各增两个字段。**
|
|
442
|
+
这一版来自一次**技术负债勘察**,修的是同一类缺陷:**数字看着都对,来源结构却被丢掉**——
|
|
443
|
+
三个都在"记录在案、无人断言"的状态下存活了很久。
|
|
444
|
+
|
|
445
|
+
### 1. 多段位置被压成单一区间,且原始位置串算完就丢
|
|
446
|
+
|
|
447
|
+
**症状**:`CDS join(1..10,50..60)` 报成 `start: 1, end: 60`——**60 bp,而真实的剪接特征是 21 bp**,
|
|
448
|
+
中间 39 bp 的间隔被无声地算进了长度;同理 `complement(join(70..80,90..95))` 报 `70-95`。
|
|
449
|
+
|
|
450
|
+
**根因**:`parseLocation` 把任何位置归约为 `min..max`(这是有意的,注释也这么写),
|
|
451
|
+
但它**已经把原始串解析出来存在 `raw` 里**,投影那一步没把它带出去;输出 schema 又是
|
|
452
|
+
`additionalProperties: false`——于是调用方**无法区分**"连续特征"与"被压平的多段特征"。
|
|
453
|
+
|
|
454
|
+
**修法(只补信息,不动数值)**:每个特征新增 `location`(源文件里原样写的位置串)与
|
|
455
|
+
`multi_part`(布尔)。`start`/`end` 仍是外边界——14 种真实位置写法逐个比对,
|
|
456
|
+
`start`/`end`/`strand` 与 0.18.0 **逐字节一致**。渲染文本内联标出
|
|
457
|
+
`CDS 1-60 [multi-part join(1..10,50..60)]`,并追加一行汇总提示。
|
|
458
|
+
|
|
459
|
+
**SnapGene 是同型缺陷**:多 `Segment` 同样被压平(`location` 重建为 `2102-2893,2894-2962`)。
|
|
460
|
+
这不是理论问题——**仓库自己的 pUC118 夹具里就有两个多段特征**(`AmpR` 就是两段)。
|
|
461
|
+
|
|
462
|
+
### 2. `123^124` 返回一个序列上并不存在的坐标
|
|
463
|
+
|
|
464
|
+
**症状**:`^` 交界点(第 123 与第 124 碱基**之间**)报成 `start: 123124, end: 123124`。
|
|
465
|
+
在 200 bp 序列上也不报错;直到喂给 `molbio_plasmid_map` 才以 `invalid span` 失败。
|
|
466
|
+
|
|
467
|
+
**根因**:清洗步骤 `raw.replace(/[<>\?^]/g, '')` **删掉 `^` 后把两个数字粘在了一起**
|
|
468
|
+
(`123^124` → `123124`)。而文件头注释一直声称 `^` 已被处理。
|
|
469
|
+
|
|
470
|
+
**修法**:`^` 先归一为区间分隔符,于是 `123^124` → `123..124`;`location` 仍保留原写法 `123^124`。
|
|
471
|
+
|
|
472
|
+
### 3. SnapGene 读不出位置的特征被静默丢弃
|
|
473
|
+
|
|
474
|
+
**修法**:`parseFeaturesXml` 把丢弃数经可选 stats 传出,`molbio_parse_snapgene` 在有丢弃时返回
|
|
475
|
+
`features_skipped`,渲染文本追加一行说明。此前这些注解从输出里**完全消失**。
|
|
476
|
+
|
|
477
|
+
### 4. 大质粒的 `.dna` 解码会崩,而宿主允许读到 50 MB
|
|
478
|
+
|
|
479
|
+
**症状**:序列以 ASCII 存储的 `.dna`(现代 SnapGene 的常规写法)在一段**约 20 万碱基**的序列上
|
|
480
|
+
抛出 `RangeError: Maximum call stack size exceeded`——一个普通的 BAC 级质粒,
|
|
481
|
+
远在 `MAX_DNA_BYTES`(50 MB)之下。侧边栏面板打开同一个文件会显示同样的报错。
|
|
482
|
+
|
|
483
|
+
**根因**:`decodeSequence` 用 `String.fromCharCode(...seqBytes)` 把**每个字节当作一个实参**展开。
|
|
484
|
+
引擎的实参上限在 20 万附近(实测 10 万通过、20 万失败),与文件大小无关——所以"允许读到 50 MB"
|
|
485
|
+
这条上限从来没有真正生效。
|
|
486
|
+
|
|
487
|
+
**修法**:改为按 8192 字节分块拼接(`subarray` 不复制),并把 `[...seqBytes].every(...)`
|
|
488
|
+
换成 `seqBytes.every(...)`(少一次 20 万元素的物化)。回归测试在 `test/smoke.mjs` 里用内存
|
|
489
|
+
构造一个 20 万碱基的 `.dna` 钉住这条路径。另外给**浏览器半**加了独立上限
|
|
490
|
+
(`MAX_PANEL_FILE_BYTES` = 5 MB,`build/panel-core.mjs`):面板在 UI 线程上解析,
|
|
491
|
+
超限时给一句明确的拒绝,而不是把标签页卡住;宿主侧的 50 MB 上限刻意更大,因为它不阻塞界面。
|
|
492
|
+
|
|
493
|
+
### 5. 一个由本次改动**避免**的静默错值:克隆时 location 会过期
|
|
494
|
+
|
|
495
|
+
`molbio_clone_simulate` / `molbio_golden_gate` 经 `remapFeatures` 回显特征。若只是给
|
|
496
|
+
`CLONE_FEATURE_SCHEMA` 补声明,源文件的 `location` 就会**与重写后的坐标并排出现**——
|
|
497
|
+
新加的信息反而制造出一个新的错值。因此重映射后的 `features` **剥离** `location`/`multi_part`
|
|
498
|
+
(它们描述的是载体,不是最终构建体),而 `dropped_features` 保留整条原始记录
|
|
499
|
+
(那里坐标未变,location 仍然准确)。
|
|
500
|
+
|
|
501
|
+
### 工程:行尾、契约守卫与 CI
|
|
502
|
+
|
|
503
|
+
- **`.gitattributes`(`* text=auto eol=lf`)**:仓库此前没有行尾声明,而 Windows 默认
|
|
504
|
+
`core.autocrlf=true` 会把提交的客户端产物物化成 CRLF。`build/client-bundle.mjs --check`
|
|
505
|
+
早就为此做了内容归一,但 **`test/contract.mjs` 的产物新鲜度断言仍是裸字符串相等**——
|
|
506
|
+
于是全新 clone 上必然误报 "stale",而那正是"真信号被误报淹没"的成因。实测:把产物写成
|
|
507
|
+
CRLF 后,旧比较得到 `false`(失败),新比较得到 `true`(通过)。现在两边都按内容比较,
|
|
508
|
+
仓库本身也不再发放这个不一致。二进制夹具(`*.dna` / `*.ab1`)显式声明为 binary。
|
|
509
|
+
- **CI(`.github/workflows/test.yml`)**:此前**不存在**——整套守卫只靠人手敲 `npm test`,
|
|
510
|
+
这正是本次勘察查出"两个版本只活在工作区"的土壤。现在 push / PR / 手动都跑:
|
|
511
|
+
安装**钉死版本**的 DSH(`0.1.7-rc.1`,与 roadmap 的基线一致;用 `@latest` 只会让红灯
|
|
512
|
+
指向环境而不是本仓库),先打印解析到的 harness 路径以便读懂失败,再 `npm test`
|
|
513
|
+
(Node 22 与 24 两档)。包本身零依赖,因此 CI 没有 install 步骤。
|
|
514
|
+
**跑在 `windows-latest` 上**:夹具把会话 cwd 设成 `C:/tmp`,而多处断言直接比较工具
|
|
515
|
+
`join` 之后的返回路径(`'C:\\tmp\\pTEST.svg'`),在 Linux runner 上只会因分隔符而失败——
|
|
516
|
+
那是在报告宿主的路径语义,不是在报告本仓库的缺陷。"让套件与平台无关"值得单独做,
|
|
517
|
+
但不是加 CI 的前提。
|
|
518
|
+
|
|
519
|
+
### 测试与 benchmark
|
|
520
|
+
|
|
521
|
+
- `test/smoke.mjs` 新增断言:`join()` 的多段标志与原文、连续位置**不**被标、
|
|
522
|
+
`^` 得到 `123..124`、真夹具 pUC118 的 AmpR 两段、以及丢弃计数。
|
|
523
|
+
- `benchmark/tasks/09-formats.json` 的 `genbank-parse` 增一条 `where:"tool"` 断言,把
|
|
524
|
+
`[multi-part 2102-2893,2894-2962]` 钉住——这条信息现在**同时**由单元测试与离线 benchmark 看守。
|
|
525
|
+
- 客户端产物已重建(两个解析器都在浏览器半);`bench --offline` 55/55。
|
|
526
|
+
|
|
527
|
+
## [0.18.0] — 2026-09-25(`molbio_plot` 补齐第二批:小提琴图 / 火山图 / 热力图)
|
|
528
|
+
|
|
529
|
+
**工具数不变(57)。** 图表能力到此完整:`molbio_plot` 共 **8 种** `kind`
|
|
530
|
+
(柱状/散点内联数组,折线/直方图/箱型/小提琴/火山/热力图读工作区 CSV/TSV)。
|
|
531
|
+
|
|
532
|
+
### 新增
|
|
533
|
+
|
|
534
|
+
- **`svgio.mjs` 连续色带**:`COLOR_RAMPS`(divergingRedBlue / viridis / blues / greys)+
|
|
535
|
+
`colorRamp(t, name)` 在色标之间线性插值、越界夹取、非数值不产生 NaN 颜色。
|
|
536
|
+
现有 `greyRamp`/`qualityColor` 是**离散分档**,保持不变——phred 分数本来只有三个有意义区间。
|
|
537
|
+
- **小提琴图**:高斯核密度(Silverman 带宽,`min(σ, IQR/1.34)` 抗离群点)+ 内部箱。
|
|
538
|
+
**修掉一个会让整张图空白实现缺陷**:网格原先写成 0..1,而样本在 22 附近、带宽 0.28,
|
|
539
|
+
每一项都落在 80 个带宽之外,`exp` 下溢为 0 —— 返回的密度整条全零。网格必须跨数据范围。
|
|
540
|
+
- **火山图**:`-log10(p)` × log2FC,虚线阈值,命中按上下调着色,`label_top` 标注头部命中。
|
|
541
|
+
**它不做任何统计检验**:`-log10` 只是展示变换,阈值是标注,p 值必须由表提供。
|
|
542
|
+
测试用"效应大但 p 无用"与"p 好但无效应"两个基因钉住这条边界。
|
|
543
|
+
- **热力图**:一格一个 `<rect>` + 连续色带 + 色标。支持**宽表**(首列行名,其余为样本列)
|
|
544
|
+
与**长表**(`columns.row/column/value`)两种版式,输出里回 `layout`。`scale`
|
|
545
|
+
(`none`/`row_zscore`/`log2`)是**显式申报的展示变换**(`scaled: true`),不是统计。
|
|
546
|
+
缺口格用中性灰,**不**用色带中点——"没测"不能读成"中等值"。
|
|
547
|
+
|
|
548
|
+
### 过程中查出的三个真实缺陷(都在交付前修掉)
|
|
549
|
+
|
|
550
|
+
1. **色标被推出画布**:先按整幅绘图区铺格子、再把色标外挂在右侧,色标的刻度文字落在画布之外。
|
|
551
|
+
改为先按色标宽度收窄绘图区再排格子。
|
|
552
|
+
2. **色带与格子方向相反**:格子对(最小=蓝)而色带反(顶=蓝),照色带读图会得出相反结论。
|
|
553
|
+
原因是给格子加的 `1 - position` 是双重取反。两处现在都直接映射。
|
|
554
|
+
3. **火山图分支标签被覆盖**:一次编辑把 `kind === 'line'` 改成了 `volcano`,于是火山图落进了
|
|
555
|
+
折线图分支(输出 `undefined points ... NaN`)。由端到端探针查获。
|
|
556
|
+
|
|
557
|
+
### 测试与 benchmark
|
|
558
|
+
|
|
559
|
+
- `test/charts.mjs` 从 17 项增到 **25 项**:新增色带(插值/夹取/未知名称回退)、核密度
|
|
560
|
+
(网格跨数据、峰归一化、恒定样本不 NaN)、小提琴(峰比谷宽)、火山(上调红/下调蓝/不变灰、
|
|
561
|
+
p 值边界拒绝)、热力图(**逐格像素颜色必须等于该值的色带颜色**、色标顶端与色带高端一致、
|
|
562
|
+
缺口格为中性灰、scale 申报、长宽两种版式尺寸互换)。
|
|
563
|
+
`sampleDocuments()` 增加三张样本图,"没有 unsupported / 没有缺字"那条断言随之覆盖它们。
|
|
564
|
+
- benchmark 新增 3 题(`chart-violin` / `chart-volcano` / `chart-heatmap`)与两个夹具
|
|
565
|
+
(`DE_CSV`、`HEATMAP_TSV`);`where: "tool"` 期望值全部按真实输出校准,离线门校验。
|
|
566
|
+
现共 **55 条任务**,覆盖仍是 57/57。
|
|
567
|
+
|
|
568
|
+
### 字体:SVG 改为 Times New Roman,并在根元素声明一次(PNG 不受影响)
|
|
569
|
+
|
|
570
|
+
全部图形(`svgio` 家族 + `plot.mjs` / `plasmid.mjs` / `logo.mjs` / `protein-structure.mjs`
|
|
571
|
+
各自的根 `<svg>`)现在声明
|
|
572
|
+
`'Times New Roman', 'Liberation Serif', 'Nimbus Roman', Times, serif`
|
|
573
|
+
(常量 `FIGURE_FONT`,定义在 `svgio.mjs`;`plot.mjs` 因不 import svgio 而本地重复一份,
|
|
574
|
+
注释写明"改一处要改两处")。选后两个替代字体是因为它们是 **Times 同度量**的,
|
|
575
|
+
Linux/macOS 上不会让图形重排。
|
|
576
|
+
|
|
577
|
+
**声明位置也统一了**:此前 `svgio` 家族把 `font-family` 写在**每个** `<text>` 上(一张图 6–84 个),
|
|
578
|
+
传统渲染器写在根上。现在**一律只在根 `<svg>` 上声明一次**,靠 SVG 的继承到达每个标签——
|
|
579
|
+
这也覆盖了调用方直接写进 `body` 的文字。`textRun`/`textSpanLines` 保留 `family` 参数作为
|
|
580
|
+
逐次覆盖的逃生口,默认不再输出该属性。
|
|
581
|
+
|
|
582
|
+
安全性依据:`font-family` 是**继承属性**,而浏览器半是把这些文档当**真 DOM 节点**插入
|
|
583
|
+
(`build/client-entry.mjs` 用 `DOMParser` + `importNode`),继承在那里同样成立——
|
|
584
|
+
所以根上声明一次不会让面板里的图谱/logo 掉字体。
|
|
585
|
+
|
|
586
|
+
**PNG 完全不受影响**:光栅化器忽略 `font-family`,仍用内置折线字体。这是"零依赖、无字体文件"
|
|
587
|
+
的必然代价,README 里已经把这条边界写清楚(SVG 给投稿、PNG 给模型看)。
|
|
588
|
+
|
|
589
|
+
### 新增第二条产物守卫:产物必须真的含有每个模块
|
|
590
|
+
|
|
591
|
+
`test/contract.mjs` 原有那条"提交的产物 == 当前源码构建的产物"**看不见一种缺失**:如果产物是
|
|
592
|
+
**更早**的模块图构建的、某个模块从来没进去过,那么"当前构建"与"提交的产物"两边都少它,
|
|
593
|
+
比较结果照样相等。
|
|
594
|
+
|
|
595
|
+
新守卫(第 16 条 check)从打包器拿到模块图,对每个模块取**源码里最长的一条顶层声明**、
|
|
596
|
+
去掉 `export` 关键字(打包器 lower 后留下的正是这个形式),再断言**产物文本里真的含有它**。
|
|
597
|
+
几个让它不至于"悄悄停止检查"的配套断言:检查到的模块数下限、模块总数与"检查数 + 无声明数"
|
|
598
|
+
必须相等、`lib.mjs`/`svgio.mjs`/`logo.mjs`/`font-metrics.mjs` 必须在图里,以及一条**反向对照**
|
|
599
|
+
(搜一个不存在的声明必须报"没有")。
|
|
600
|
+
|
|
601
|
+
**它证明了自己会失败**:拿 git HEAD 那个陈旧产物跑,它精确报出缺的两个模块——
|
|
602
|
+
`svgio.mjs`(`function textSpanLines`)与 `font-metrics.mjs`(`function characterAdvance`),
|
|
603
|
+
正是这次重跑构建才补上的那两个。
|
|
604
|
+
|
|
605
|
+
## [0.17.0] — 2026-09-25(`molbio_plot` 扩到五种图表,新增工作区 CSV/TSV 数据读取)
|
|
606
|
+
|
|
607
|
+
**工具数不变(57)。** `molbio_plot` 的 `kind` 从 `bar|scatter` 扩到
|
|
608
|
+
`bar|scatter|line|histogram|box`,并新增从工作区表格读数据的能力。这是**第一批**:
|
|
609
|
+
折线图、直方图、箱型图。第二批(小提琴图、火山图、热力图)尚未实现。
|
|
610
|
+
|
|
611
|
+
### 新增:`charts.mjs`(CSV/TSV 读取 + 三种图表)
|
|
612
|
+
|
|
613
|
+
- **数据来源**:`data_path`(工作区 CSV/TSV,按会话工作区解析后经 `ctx.fs` 读取)或
|
|
614
|
+
`data`(内联表格文本),`columns: {x, y, group, value}` 按**列名**选列。列名大小写与
|
|
615
|
+
空白不敏感,前缀唯一时接受简称;多义或无匹配时**报错并列出所有列名**,不猜。
|
|
616
|
+
- **解析器**:自动探测分隔符(逗号/制表/分号/竖线,按"每行出现次数一致"判定)、表头、
|
|
617
|
+
引号字段(含 `""` 转义)、CRLF、空字段、`NA`/`NaN`/`null`。**报错带行号与列名**;
|
|
618
|
+
引号数为奇数时点名"引号未闭合"而不是把锅甩给分隔符。
|
|
619
|
+
- **直方图**:分箱默认 **Freedman-Diaconis**(IQR 退化时回退到 Sturges,恒定样本单箱),
|
|
620
|
+
`bins` 可显式指定。**箱型图**:五数概括 + 1.5×IQR 须 + 离群点;四分位用**线性插值**,
|
|
621
|
+
与 `fastq-qc.mjs` 同一口径。**折线图**:`group` 列拆多条序列,x 非数值时按文件顺序等距
|
|
622
|
+
排布并在输出里说明(`categorical_x`)。
|
|
623
|
+
- 画法一律基于 `svgio.mjs`(`plot.mjs` 里那套重抄的 `escapeXml`/`niceStep` 原样不动,
|
|
624
|
+
以免改动既有柱状/散点的输出)。**图例画在绘图区之上**——v1 画在区内,既遮住第一个数据点,
|
|
625
|
+
又让像素断言把图例色块当成数据。
|
|
626
|
+
|
|
627
|
+
### 渲染文本现在带统计量
|
|
628
|
+
|
|
629
|
+
`tool_result` 的文本是**渲染投影**,所以箱型图/折线图/直方图的 `render` 现在把算好的
|
|
630
|
+
数字写进去(每序列中位数、四分位、范围、离群点;轴范围;箱数)。否则模型手里只有一张
|
|
631
|
+
读不出数值的图。`stats` 字段仍然原样返回。
|
|
632
|
+
|
|
633
|
+
### 测试与 benchmark
|
|
634
|
+
|
|
635
|
+
- `test/charts.mjs`(新增,17 项):解析器与统计量按手算值断言;三种图**按像素**断言
|
|
636
|
+
(条形落在自己的分箱内、count-0 分箱留出真空隙、箱体各自就位、离群点画在自己的值上、
|
|
637
|
+
同 x 的重复读数不横向散开)。发现并修掉三个真实缺陷:数值正则拒收一切小数、
|
|
638
|
+
条形越过分箱边界 1 px、缺失文件被报成"表里没有数据"。
|
|
639
|
+
- benchmark 新增 3 题(`chart-box-plot` / `chart-line-series` / `chart-histogram`),
|
|
640
|
+
表格文本放在 `benchmark/fixtures.mjs`,`run.mjs` 与 `verifications.mjs` 种入**同一份**,
|
|
641
|
+
这样离线的 `where: "tool"` 期望值是被真实工具输出校验过的。
|
|
642
|
+
- `package.json` 的 `files` 白名单与 `test:unit` 都已加入新模块(漏了就是"装完却缺东西"
|
|
643
|
+
的静默失败,见 [docs/rules.md](docs/rules.md) 第 4 条)。
|
|
644
|
+
|
|
645
|
+
## [0.16.0] — 2026-09-24(两个 benchmark 查出的缺陷:引物朝向 + 甲基化参考表)
|
|
646
|
+
|
|
647
|
+
**工具数不变(57),参数与输出 schema 不变。** 这一版修的是**两个"每个数字都对、但结论是错的"缺陷**,
|
|
648
|
+
两个都是 benchmark(不是单元测试)发现的,两个在修完之前都是"记录在案、刻意不断言"的状态。
|
|
649
|
+
清单见 [benchmark/README.md](benchmark/README.md) 的 Findings。
|
|
650
|
+
|
|
651
|
+
### 1. `molbio_design_primers` 把正向/反向引物**标反了**(roadmap 2.0)
|
|
652
|
+
|
|
653
|
+
**症状**:返回的引物对**按原样送进 `molbio_pcr_simulate` 得到 0 个产物**。600 bp 模板实测:
|
|
654
|
+
工具打印 `F 555-572` 与 `R 419-437`——`F` 在**下游**、`R` 在**上游**,两条引物朝外延伸。
|
|
655
|
+
|
|
656
|
+
**根因不在数值,而在两个数值之间的关系**:引擎在两条链上各扫一遍候选——
|
|
657
|
+
`scanCandidates(template, …)` 扫顶链(分子向右延伸,3' 端在坐标**高**的一端),
|
|
658
|
+
`scanCandidates(rc, …)` 扫反向互补(分子向左延伸,3' 端在坐标**低**的一端,映射字段叫 `anchor`)。
|
|
659
|
+
**符号与坐标被交叉写在了一起**:
|
|
660
|
+
|
|
661
|
+
```js
|
|
662
|
+
// 修复前:把顶链命中(f,右向)当 reverse 却用它的 start,
|
|
663
|
+
// 把 RC 命中(r,左向)当 forward 却用它的 anchor
|
|
664
|
+
forward: { sequence: f.sequence, start: f.anchor + 1, … }
|
|
665
|
+
reverse: { sequence: r.sequence, start: r.start + 1, … }
|
|
666
|
+
const ampliconStart = r.anchor; // 扩增子两端也一起反了
|
|
667
|
+
const ampliconEnd = fwdEnd;
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
**修法**:让角色与坐标同源——顶链命中(`f`)是 forward、用 `f.start`;RC 命中(`r`)是 reverse、
|
|
671
|
+
用 `r.anchor`;扩增子为 `f.start .. r.anchor + r.length - 1`。配套把配对窗口从"3' 端对齐"
|
|
672
|
+
改成**物理约束**(`fwdEnd < r.anchor`,两条引物不得重叠)——旧窗口只对"两条引物占同一个窗口"
|
|
673
|
+
这种退化几何成立。目标位点偏好(`target_distance`)也随之归属到正确的引物上。
|
|
674
|
+
|
|
675
|
+
**为什么只有 benchmark 能发现**:工具自己的每个字段都对(两条分子的序列、Tm、GC、结构分数全对),
|
|
676
|
+
错的是"哪条叫 F"。`test/smoke.mjs` 甚至把这个错误约定**写进了断言**
|
|
677
|
+
(`assert.equal(pair.amplicon.start, pair.reverse.start)`),所以套件全绿。
|
|
678
|
+
|
|
679
|
+
**新增串联回归**(`test/smoke.mjs`):对四组模板的**每一对**返回引物断言——
|
|
680
|
+
把 `forward`/`reverse` **原样**喂给 `molbio_pcr_simulate` 必须得到**恰好 1 个产物**,
|
|
681
|
+
产物大小等于报告的 amplicon、起止坐标等于报告的两条引物。这类"两个工具之间关系"的缺陷,
|
|
682
|
+
只有把两个工具**串起来**才看得见。
|
|
683
|
+
|
|
684
|
+
**顺带重建的四处既有夹具**(同一个退化几何的账单,不是放宽标准):
|
|
685
|
+
- TaqMan 的引物对坐标随之平移(探针几何本身是对的,`probeCandidates` 自己判断谁在上游);
|
|
686
|
+
- v12 的"错配救援"端到端夹具**不再可构造**:它原来靠 24 bp 模板 + `amplicon_max: 24`
|
|
687
|
+
让两条 18 bp 引物占同一窗口(合法最小扩增子其实是 18+18+1 = 37 bp)。对约 5000 个随机模板
|
|
688
|
+
(run/GC/Tm 三类失败、两种侧翼方案)做过搜索,**在引物不重叠的几何下找不到
|
|
689
|
+
"精确引物不存在、但错配引物存在"的模板**,所以该端到端断言改为**直接单元测试
|
|
690
|
+
`mismatchVariants` 的 3' 关键区闸门**(保护区 0 个区内位置、放开后 22 个),
|
|
691
|
+
并把"报告出来的错配"不变式保留在返回的引物对上;
|
|
692
|
+
- 引物二聚体夹具同理重建(`F-site + 1 bp + rc(R-site)`):现在**恰好一个**候选对,
|
|
693
|
+
严格阈值下被拒、放宽后进入;
|
|
694
|
+
- 甲基化夹具里 BamHI 的状态(见下)。
|
|
695
|
+
|
|
696
|
+
### 2. `METHYLATION_SENSITIVITY` 参考表与来源不符(roadmap 2.1)
|
|
697
|
+
|
|
698
|
+
**症状**(benchmark 发现):`molbio_methylation_check` 把 **BamHI 判为 dam 敏感(impaired)**,
|
|
699
|
+
理由是 `GGATCC` 内含 `GATC`。模型跑完工具后去查 NEB,发现**相反**的结论并如实报告——
|
|
700
|
+
于是"模型是对的、表是错的",任务只能刻意不断言谁对。
|
|
701
|
+
|
|
702
|
+
**机制**:旧表的构造规则是"**识别位点里含 GATC/CCWGG ⇒ 该酶敏感**"。这条规则只在**位点本身
|
|
703
|
+
包含甲基化位点**时有定义,而它恰好在两类酶上出错:
|
|
704
|
+
|
|
705
|
+
| 类别 | 例子 | 为什么错 |
|
|
706
|
+
| --- | --- | --- |
|
|
707
|
+
| 位点含 GATC/CCWGG,但酶**耐受**甲基化 | **BamHI**、BglII\*、BstYI\* | 位点包含只说明"甲基化碱基**能**被呈递",不说明酶被抑制 |
|
|
708
|
+
| 位点**不可能**包含该位点,靠侧翼才形成 | **EcoRV**(`GATATC` 无论侧翼如何都凑不出 `GATC`/`CCWGG`)、ClaI、XbaI、NruI、TaqI、BspEI | 需要**序列上的重叠检查**,不是表项 |
|
|
709
|
+
|
|
710
|
+
最终 **15/29 条**与来源不一致。修法**不是**再手抄一遍,而是:
|
|
711
|
+
- 每一项对着 **REBASE 的逐酶甲基化记录**核对(`build/rebase-audit.mjs` 可重新推导,
|
|
712
|
+
`build/rebase-audit.json` 留档)。REBASE 按颜色编码每条记录:绿=切、橙=(N% cleaved)=受损、
|
|
713
|
+
红=不切;修饰码(`m6`=Dam、`m4`=Dcm)是位点行上一行的 token;
|
|
714
|
+
- **BamHI 直接删除**:REBASE 对 Dam 甲基化 DNA 的记录是**切割**(MS#909,原文
|
|
715
|
+
"tested on Dam methylated phage and plasmid DNAs"),NEB 目录写 "Not sensitive to dam, dcm or
|
|
716
|
+
mammalian CpG methylation",且已有**需要 m6A 才能切**的 BamHI 突变体(J Mol Biol 285:1525, 1999);
|
|
717
|
+
- 另删 MluI/PvuII/XhoI(dam)与 BstNI/KpnI/NaeI/SacI/HincII(dcm);EcoRV 整条删除
|
|
718
|
+
(dam 与 dcm 都不可能重叠);**BspHI 从 blocked 降为 sensitive**(NEB 原话
|
|
719
|
+
"Impaired by overlapping dam methylation",REBASE 同时有 cut 与 not cut 记录);
|
|
720
|
+
- **刻意不改的**(避免反向过修):BglII/BstYI(位点确含 GATC,REBASE 在半甲基化**与**全甲基化
|
|
721
|
+
底物上都记录到受损:MS#2329/#1168、MS#2380/#2381/#835)、SmaI(六条 Dcm 记录在 impaired 与
|
|
722
|
+
not cut 之间交替——真实的上下文依赖)、ScaI(m4 下有 not cut 记录)、
|
|
723
|
+
AvaII/SexAI/StuI/ApaI(各自有 m4 not-cut 或 NEB "blocked by overlapping dcm methylation";SexAI
|
|
724
|
+
的 `ACCWGGT` 本身就含 `CCWGG`)。判据是**证据 vs 推断**:SmaI/ScaI 的位点同样装不下 `CCWGG`,
|
|
725
|
+
但它们各自**有** Dcm 记录,所以留下;HincII 一条都没有,所以删掉。
|
|
726
|
+
|
|
727
|
+
**语义澄清**:这张表是"**值得检查的酶**"清单,不是"位点"清单——`methylationSites`/`methylationImpact`
|
|
728
|
+
在序列里找**真实的** GATC/CCWGG,只有与**该酶自己的识别位点重叠**时才报告它。所以"位点含 GATC"
|
|
729
|
+
的酶(BclI/BstYI/BglII)只有在序列里**确实存在** GATC 时才被报告,这正是想要的。
|
|
730
|
+
|
|
731
|
+
**表项被删 ≠ 酶从报告里消失**:默认酶集合是"甲基化表 ∩ 酶切表",KpnI 因此退出了默认选择
|
|
732
|
+
(它本来就没有 dam/dcm 标记可报),但 `enzymes: ["common"]` 仍覆盖整张酶切表——
|
|
733
|
+
`test/smoke.mjs` 现在同时盯着这两件事,防止"删表项"变成"静默丢能力"。
|
|
734
|
+
|
|
735
|
+
### 3. 被这次修复作废的两条 benchmark 录音(诚实清单)
|
|
736
|
+
|
|
737
|
+
`test/fixtures/benchmark-traces.json` 是**真实模型响应**的冻结副本,用来证明判分器仍能判错、
|
|
738
|
+
没有退化成"给措辞打分"。其中 **`methylation-block` 与 `qpcr-primers` 两条录的是修复前的答案**
|
|
739
|
+
(模型当时**正确地**报告了工具的旧结论),因此**无法**满足修复后的任务断言。判分器的回归材料
|
|
740
|
+
只能用**真实模型运行**刷新(`node benchmark/run.mjs --model --task <id>` 后 `_freeze-trace.mjs`),
|
|
741
|
+
而**本机当前没有可用凭证**,所以:
|
|
742
|
+
|
|
743
|
+
- 两条录音就地标为 `answer_excluded_from_scoring: true` + `answer_excluded_reason`(含重录命令),
|
|
744
|
+
**没有删除、也没有手改答案**——手改出来的"模型说过这句话"是伪造的证据;
|
|
745
|
+
- `test/benchmark-score.mjs` **每次运行都打印**这两条挂起项(挂起是负债,不是免检),要求 `reason`
|
|
746
|
+
足够具体(> 40 字符),并断言**至少 8 条录音**仍在被验证;
|
|
747
|
+
- 两个任务的 `where: "tool"` 断言已按**修复后的真实输出**重写,所以 `--offline`(零成本、不调模型)
|
|
748
|
+
仍然全绿——它证明的是"期望值仍然为真",与录音能否重放无关。
|
|
749
|
+
|
|
750
|
+
**重录之后请删掉这两个标记**(`--model --task <id>` → `_freeze-trace.mjs`)。
|
|
751
|
+
|
|
752
|
+
### 4. 顺带:`benchmark/README.md` 的两条 Findings 结案
|
|
753
|
+
|
|
754
|
+
`molbio_design_primers` 朝向与 `molbio_methylation_check` 的 BamHI 两条从"记录、未修"改为
|
|
755
|
+
"已修(0.16.0)",各自附上验证方式与"为什么单元测试看不见"。两个 benchmark 任务的答案断言
|
|
756
|
+
也随之可以**断言引物角色**(`qpcr-primers`)与**BamHI 未被标记**(`methylation-block`)——
|
|
757
|
+
在修复之前,这两条断言都会惩罚一个发现了真 bug 的模型。
|
|
758
|
+
|
|
759
|
+
工具仍 57;无版本目录;包版本 0.15.3 → **0.16.0**。
|
|
760
|
+
|
|
761
|
+
## [0.15.3] — 2026-09-23(DSH 0.1.7-rc.1:两个侧边栏 tab 静默消失 —— 补回宿主锚点)
|
|
762
|
+
|
|
763
|
+
**症状**:右栏的 **Molbio** 与 **Papers** 两个 tab 在新版里没有了。工具本身正常(57 个,
|
|
764
|
+
preset 挂载无 `broken`),所以这是一次**只掉客户端半**的回归。
|
|
765
|
+
|
|
766
|
+
### 根因:客户端扫描只走宿主 Loader 树,而本包在宿主层已经没有任何行
|
|
767
|
+
|
|
768
|
+
DSH 的 `dsh-client-modules` 负责把 `dsh.client` 包的浏览器半编进 `window.__DSH_BOOT__`,
|
|
769
|
+
它的扫描条件是(`lib/index.js` 的 `processOne`):
|
|
770
|
+
|
|
771
|
+
```js
|
|
772
|
+
for (const entry of this.ctx.loader.entries()) {
|
|
773
|
+
if (entry.options.name !== entryName || entry.fiber === void 0 || entry.disabled) continue;
|
|
774
|
+
const source = this.resolveSource(entry); // 读这一行的包 manifest → dsh.client / exports["./client"]
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
即:**它只遍历宿主 Loader 的行**。preset 的行挂在隔离的一次性子树里,这个扫描**永远看不到**
|
|
778
|
+
(官方文档原话:"scans the host Loader's entries")。
|
|
779
|
+
|
|
780
|
+
而 0.13.0 那一轮,为了"57 个工具只在 molbio-lab 模式里加载"(这个目标是对的),
|
|
781
|
+
`cordis.patch.yml` 变成了**空列表**、工具行搬进了 preset。于是本包**失去了唯一的宿主行**,
|
|
782
|
+
客户端 bundle 再没有任何锚点可被发现——产物是好的、里面两个 tab 的注册也都在,
|
|
783
|
+
**但没有任何东西会去加载它**。
|
|
784
|
+
|
|
785
|
+
### 为什么全套件都是绿的
|
|
786
|
+
|
|
787
|
+
因为**没有任何一条断言在问"这个包还能不能被扫到"**:
|
|
788
|
+
|
|
789
|
+
- `test/client.mjs` 检查产物格式与数据通路(产物确实没问题);
|
|
790
|
+
- `test/panel-render.mjs` 直接把组件跑起来(组件确实没问题);
|
|
791
|
+
- `test/client-mount.mjs` 复刻的是**每一行**的扫描,而它把 `./host.mjs` 这类相对行
|
|
792
|
+
直接归为"非包"跳过——恰好跳过了唯一能锚定本包的那一行。
|
|
793
|
+
|
|
794
|
+
### 修法
|
|
795
|
+
|
|
796
|
+
新增 `host.mjs`:一个**惰性宿主锚点**(`export const name = 'molbio-client'`、
|
|
797
|
+
`apply()` 空实现),并在 `cordis.patch.yml` 里插一行:
|
|
798
|
+
|
|
799
|
+
```yaml
|
|
800
|
+
- insert:
|
|
801
|
+
- id: molbio-client
|
|
802
|
+
name: './host.mjs'
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
**不能拿 `index.mjs` 当锚点**——那是工具插件,import 它就会把 57 个工具塞回每个会话,
|
|
806
|
+
正是要避免的事。`packages/molbio-panel/index.mjs` 早就是这个模式(空宿主半边),
|
|
807
|
+
所以面板专用包的 tab 从没消失过——这次的教训是**根包也需要一个**。
|
|
808
|
+
|
|
809
|
+
### 验证(都是实跑,不是看代码)
|
|
810
|
+
|
|
811
|
+
| 证据 | 结果 |
|
|
812
|
+
| --- | --- |
|
|
813
|
+
| `--dump-config` | 新行解析为 `file://…/dsh-molbio-tools/host.mjs` |
|
|
814
|
+
| `window.__DSH_BOOT__`(起 scratch 服务取首页) | 修前**没有** `dsh-molbio-tools`;修后有条目 `{"id":"dsh-molbio-tools","url":"plugins/??dsh-molbio-tools/client.js&rev=…","inject":[sidebar-right, connection]}` |
|
|
815
|
+
| 拉取该 URL | HTTP 200、349 KB,内含 `sidebarRightTabs` / `molbio-panel` / `molbio-papers` / `sidebar.right.pane.tab` |
|
|
816
|
+
| `agentPresets/list` | 五个 preset 的 `broken` 全空(工具侧无回归) |
|
|
817
|
+
|
|
818
|
+
### 新增守卫(已用突变实验证明会失败)
|
|
819
|
+
|
|
820
|
+
`test/client-mount.mjs` 现在按"**该包是否被这个 profile 选中**"逐包断言:
|
|
821
|
+
声明了 `dsh.client`、且被选中的包,**必须有某个宿主平面的行能解析到它**。
|
|
822
|
+
|
|
823
|
+
- 它可以是很薄的一行(`./host.mjs`),也可以是真包名——但必须存在;
|
|
824
|
+
- 把锚点行改回空列表,测试立刻 FAIL 并打印修法(含"这就是侧边栏 tab 消失的原因");
|
|
825
|
+
- 未被该 profile 选中的包(面板专用包)跳过,因为"没装"是用户的选择,不是坏产物;
|
|
826
|
+
- 同时让这个测试**会解析相对/file 行**(按 patch 层所在目录解析、向上找最近的 manifest),
|
|
827
|
+
与 harness 的行为一致——它之前不会,所以才会漏掉。
|
|
828
|
+
|
|
829
|
+
工具仍 57;无版本目录;包版本 0.15.2 → **0.15.3**。
|
|
830
|
+
|
|
831
|
+
## [0.15.2] — 2026-09-23(`--changed`:按改动自动选任务;修 client bundle 的 CRLF 坑)
|
|
832
|
+
|
|
833
|
+
**工具数、插件行为、benchmark 题目均未改动。** 这一版让"改哪里测哪里"成为默认,
|
|
834
|
+
并修掉一个让"产物是否过期"这道守卫在 Windows 上喊狼来了的构建缺陷。
|
|
835
|
+
|
|
836
|
+
### 1. `--changed`:从改动反推该跑哪些题
|
|
837
|
+
|
|
838
|
+
```bash
|
|
839
|
+
node benchmark/run.mjs --model --changed # 只跑受影响的题;或告诉你"无事可跑"
|
|
840
|
+
node benchmark/run.mjs --list --changed # 先看判断结果,零成本
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
它**不是手写映射表**,而是从代码量出来的:
|
|
844
|
+
|
|
845
|
+
```
|
|
846
|
+
改动的文件 → 它是哪个模块(包根目录的 .mjs 集合)
|
|
847
|
+
→ 哪些模块(传递地)import 它
|
|
848
|
+
→ 那些模块用到的每个工具
|
|
849
|
+
→ 覆盖这些工具的每道题
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
实测:
|
|
853
|
+
|
|
854
|
+
```
|
|
855
|
+
$ touch crispr.mjs && node benchmark/run.mjs --list --changed
|
|
856
|
+
1 of 49 task(s) selected — --tools molbio_grna_design --changed
|
|
857
|
+
(from --changed; 2 module(s) affected: crispr.mjs, index.mjs)
|
|
858
|
+
|
|
859
|
+
$ touch lib.mjs && node benchmark/run.mjs --list --changed
|
|
860
|
+
48 of 49 task(s) selected — --changed (27 module(s) affected: align.mjs, cloning.mjs, …)
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
`lib.mjs` 选中几乎全部是**正确答案**而不是"收窄失败":它是几乎每个工具都依赖的共享库。
|
|
864
|
+
|
|
865
|
+
### 2. 首要目标是**不漏测**,所以不确定一律放大
|
|
866
|
+
|
|
867
|
+
漏测的失效模式是"没跑的测试是绿的",不可见。因此每一种不确定都回落到全量并**打印原因**:
|
|
868
|
+
|
|
869
|
+
| 情况 | 结果 |
|
|
870
|
+
|---|---|
|
|
871
|
+
| 没有 git / 没有 HEAD / 读不到插件结构 | 全量 |
|
|
872
|
+
| 认不出的文件(新模块、改名) | 全量 |
|
|
873
|
+
| `index.mjs`(它绑定全部工具) | 全量 |
|
|
874
|
+
| 受影响的工具没有任何任务覆盖 | 全量 |
|
|
875
|
+
| **只被别的模块 import** 的模块(如 `svgio.mjs`) | 局部——它 importers 的题 |
|
|
876
|
+
| 文档、CHANGELOG、`test/`、`benchmark/`、配置 | **`none`:无事可跑** |
|
|
877
|
+
|
|
878
|
+
`none` **刻意不等于** `full`:一次文档提交如果触发 49 次模型调用,`--changed` 这个默认值
|
|
879
|
+
就会被人关掉,然后全量又变成每次都跑——正是这个开关要终结的行为。它只在显式
|
|
880
|
+
`--changed` 时可达,其余一切照旧 fail-safe;两个方向都由 `test/benchmark-changed.mjs` 断言。
|
|
881
|
+
|
|
882
|
+
### 3. `--tools` 与 `--changed` 的两个算子
|
|
883
|
+
|
|
884
|
+
| 写法 | 含义 |
|
|
885
|
+
|---|---|
|
|
886
|
+
| `--tools X` | 按名字选 X(**并集**语义) |
|
|
887
|
+
| `--changed --tools X` | **交集**:在受影响集合里只留 X |
|
|
888
|
+
|
|
889
|
+
交集只会**减少**运行量,永远不会掩盖改动(危险方向是放大),所以允许;
|
|
890
|
+
输出里两个算子都打印,因为搞混它们就是"测了改动"和"测得比改动还少"的区别。
|
|
891
|
+
|
|
892
|
+
### 4. 修:client bundle 的 CRLF 让"产物过期"守卫在 Windows 上误报
|
|
893
|
+
|
|
894
|
+
`build/client-bundle-core.mjs` 在嵌入源码时按 `\n` 切行再逐行缩进,**没有先归一 CRLF**:
|
|
895
|
+
以 `\r` 结尾的行会被缩进追加在回车**之后**,于是产物长出源码里没有的尾随空白行——
|
|
896
|
+
**产物内容取决于 checkout 用的是 LF 还是 CRLF**。
|
|
897
|
+
|
|
898
|
+
后果是一道守卫失去可信度:`build/client-bundle.mjs --check` 用 `previous === text` 比**字节**,
|
|
899
|
+
而 Windows 上 `core.autocrlf` 会把产物物化成 CRLF,于是**内容完全相同也报 STALE**。
|
|
900
|
+
一个总在喊狼来了的守卫,等于没有守卫——正是它挡住了 `npm test`(`contract.mjs` 的
|
|
901
|
+
"产物新鲜度"那一条在本次会话里先报 FAIL,追下去才发现根因)。
|
|
902
|
+
|
|
903
|
+
两处都修好:生成器归一 CRLF(产物平台无关),检查器比**内容**而非字节。修完后
|
|
904
|
+
`node build/client-bundle.mjs` 重新生成的产物与已提交的那份**逐字节相同**——
|
|
905
|
+
即 0.15.2 **不需要更新任何产物**,只是让守卫恢复了它本该有的信号。
|
|
906
|
+
|
|
907
|
+
### 发版清单
|
|
908
|
+
|
|
909
|
+
| 项 | 内容 |
|
|
910
|
+
|---|---|
|
|
911
|
+
| 工具数 / 插件 | 不变 / 未改 |
|
|
912
|
+
| 客户端产物 | **未变**(修好后重建结果与已提交的一致,`git diff` 为空) |
|
|
913
|
+
| 新增 | `benchmark/changed.mjs`、`test/benchmark-changed.mjs` |
|
|
914
|
+
| 改 | `benchmark/run.mjs`(选择器 + `--changed`)、`build/client-bundle.mjs`、`build/client-bundle-core.mjs`、`benchmark/README.md`、`docs/workflow.md`、`package.json` |
|
|
915
|
+
| 预检 | `npm test`(**14 项**全绿) |
|
|
916
|
+
|
|
917
|
+
## [0.15.1] — 2026-09-23(benchmark 可按工具选择:`--tools`)
|
|
918
|
+
|
|
919
|
+
**工具数、插件行为、benchmark 题目本身都未改动。** 这一版只修一件事:
|
|
920
|
+
**改一个工具不该逼你跑全量。**
|
|
921
|
+
|
|
922
|
+
### 1. `--tools`:按工具选任务
|
|
923
|
+
|
|
924
|
+
```bash
|
|
925
|
+
node benchmark/run.mjs --model --tools primer_tm # 只跑涉及 molbio_primer_tm 的题
|
|
926
|
+
node benchmark/run.mjs --list --tools "primer_*" # 先看会选到哪些题,不花 tokens
|
|
927
|
+
node benchmark/run.mjs --replay --tools primer_tm # 用记录下来的响应免费复算
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
认三种写法:`molbio_primer_tm` / `primer_tm` / `primer_*`;也接受逗号或空格分隔的多项。
|
|
931
|
+
**匹配不到任何任务直接报错(退出码 2)**——那要么是拼错了,要么是该工具没有任务。
|
|
932
|
+
|
|
933
|
+
**为什么这是必要的而不是锦上添花**:之前的唯一选择器是 `--task <id>`,于是"测一个工具"
|
|
934
|
+
要先自己查出哪个任务覆盖它。结果就是**每次改动都跑全量**——而这正是上一版写下的规则
|
|
935
|
+
("改动的工具跑它自己的 benchmark")在实践中最容易被绕开的地方。选择单位应该是
|
|
936
|
+
**你改动的工具**,不是"恰好覆盖它的那个任务 id"。
|
|
937
|
+
|
|
938
|
+
选了 `--tools` 就**不看 tier**:你点名的工具,它所在的题一定跑,哪怕那题是 full-only。
|
|
939
|
+
|
|
940
|
+
### 2. 即使只跑一个工具,两道离线门仍跑**全量**
|
|
941
|
+
|
|
942
|
+
`--model --tools <一个工具>` 内部先执行完整的 `scoreSuiteOffline()`:49 条任务的
|
|
943
|
+
`where:"tool"` 断言全部重算。这是几百毫秒、不调模型的一步,而它正是"只跑一部分"能成立的
|
|
944
|
+
前提——**它把"跳过是因为没改"从记忆变成受检的断言**。代价是它在离线侧永远不省;
|
|
945
|
+
收益是不必为了"保险"而跑全量。
|
|
946
|
+
|
|
947
|
+
**它的盲区照旧**:离线门只看单次调用的**输出**,看不见"模型还选不选得对"。所以规则是
|
|
948
|
+
改了某工具的实现/描述 → `--tools <它>`;改了共享模块(影响面说不清)→ `--tier full`。
|
|
949
|
+
|
|
950
|
+
### 3. `covers` 不能虚报(守卫加强)
|
|
951
|
+
|
|
952
|
+
`--tools` 是按每个任务声明的 `covers` 选的,所以**虚报比漏报更危险**:一条
|
|
953
|
+
`covers: [X]` 而从不跑 X 的任务,会让 `--tools X` 跑一个根本没测 X 的题并报"通过"。
|
|
954
|
+
|
|
955
|
+
`test/benchmark-coverage.mjs` 新增断言:**每个 `covers` 里的工具,必须真的被那条任务
|
|
956
|
+
要求(`expect_tools`)或被它的离线调用执行到**。加上这条后立刻查出两条虚报并修掉:
|
|
957
|
+
`orientation` 声称覆盖 `molbio_gc_content` 却没要求它(已改成要求),
|
|
958
|
+
`restraint-simple-math` 把 `molbio_lab_math` 算进覆盖(其实它是一个**克制性**任务,
|
|
959
|
+
正确答案是不调工具,已改为 `covers: []`)。
|
|
960
|
+
|
|
961
|
+
为此 `covers` 允许为空数组(表示"这条题刻意不要求任何工具"),但
|
|
962
|
+
`covers: []` 与 `expect_tools` 非空并存仍然报错。
|
|
963
|
+
|
|
964
|
+
### 4. 报错变干净
|
|
965
|
+
|
|
966
|
+
用法错误(`--tools` 拼错、`--task` 不存在、两个互斥选项同时给)现在打印一行消息并
|
|
967
|
+
`exit 2`,不再是一段 stack trace。`--replay` 后面的报告路径改成可选值,
|
|
968
|
+
于是 `--replay --tools X` 不会把 `--tools` 当成文件名吞掉。
|
|
969
|
+
|
|
970
|
+
### 发版清单
|
|
971
|
+
|
|
972
|
+
| 项 | 内容 |
|
|
973
|
+
|---|---|
|
|
974
|
+
| 工具数 / 插件 | 不变 / 未改 |
|
|
975
|
+
| benchmark 题目 | 未改(仅 `orientation` 与 `restraint-simple-math` 的 `covers`/`expect_tools` 元数据) |
|
|
976
|
+
| 改 | `benchmark/run.mjs`(选择器 + `--tools`)、`benchmark/score.mjs`(`covers` 校验)、`test/benchmark-coverage.mjs`(虚报守卫)、`benchmark/README.md`、`docs/workflow.md` |
|
|
977
|
+
| 预检 | `npm test`(13 项全绿) |
|
|
978
|
+
|
|
979
|
+
## [0.15.0] — 2026-09-23(benchmark 覆盖全部 57 个工具;查出引物方向缺陷)
|
|
980
|
+
|
|
981
|
+
**工具数与插件行为均未改动**(57 个工具,领域模块一行未改)。这一版把上一版建立的 benchmark
|
|
982
|
+
从"12 个工具、15 题"扩到**57/57 个工具、49 题**,并把"改功能必须改 benchmark"写成机器守卫。
|
|
983
|
+
|
|
984
|
+
### 1. 覆盖:12 → 57 个工具
|
|
985
|
+
|
|
986
|
+
49 个任务分在 9 个文件里(`benchmark/tasks/*.json`,按文件名顺序拼接),每个任务声明
|
|
987
|
+
`covers`。**新增守卫 `test/benchmark-coverage.mjs` 断言 57/57 全覆盖**——加工具不加任务
|
|
988
|
+
直接红。它还守住 fixture 的**前提**(Golden Gate 载体必须无 BsaI 位点、诱变模板必须仍能
|
|
989
|
+
设计出引物)与 tier/占位符健全性。
|
|
990
|
+
|
|
991
|
+
**分档**:`core` 14 题(`npm run bench`,一个能力领域一个代表)、`full` 49 题
|
|
992
|
+
(`npm run bench:full`)。分档的理由是成本差一个数量级,而该跑哪档取决于改了什么。
|
|
993
|
+
|
|
994
|
+
### 2. 查出并记录一个真实的插件缺陷
|
|
995
|
+
|
|
996
|
+
**`molbio_design_primers` 把 forward/reverse 标反了。** 这是 benchmark 第一次查出一个
|
|
997
|
+
**功能性**缺陷(前几轮查出的全是 benchmark 自己的问题):
|
|
998
|
+
|
|
999
|
+
```
|
|
1000
|
+
tool F: ATTACTCCTGCTCTTCCCATAC 201-222 ← 等于正链 201-222
|
|
1001
|
+
tool R: CACTACTCCTCTGTACGCAC 105-124 ← 等于 rc(正链 105-124)
|
|
1002
|
+
amplicon: 105-222 (118 bp)
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
`F` 在 201-222、`R` 在 105-124,也就是 **F 在 R 的下游**——两条引物朝外延伸,
|
|
1006
|
+
**按工具打印的样子送进 `molbio_pcr_simulate` 得到 `0 product(s)`**。正确的那一对确实在
|
|
1007
|
+
这份输出里:把工具叫 `R` 的那条取反向互补,就是真正的正向引物;手工按扩增子跨度构造
|
|
1008
|
+
(`F = top(105-124)`、`R = rc(top(201-222))`)同一个模拟器报 `specific — 1 product(s) of
|
|
1009
|
+
118 bp`。**这是返回值的标注/朝向缺陷**,不是评分或 fixture 问题。
|
|
1010
|
+
|
|
1011
|
+
发现过程值得记:**是模型发现并说出来的**("the designer returned these two molecules with
|
|
1012
|
+
F/R reversed … the pair as printed does not amplify"),随后用 `pcr.mjs` 直接复核确认。
|
|
1013
|
+
|
|
1014
|
+
**状态:只记录,未修。** 修它属于引物设计引擎的朝向处理,且需要自己的回归测试
|
|
1015
|
+
("设计出的一对引物按原样必须模拟出恰好一个产物")。在修好之前,`qpcr-primers` 任务
|
|
1016
|
+
**刻意不断言模型把哪条叫 forward**,只断言两条分子与扩增子大小。详见
|
|
1017
|
+
[benchmark/README.md](benchmark/README.md) 的 Findings。
|
|
1018
|
+
|
|
1019
|
+
### 3. 又一轮"测的是 benchmark 自己"
|
|
1020
|
+
|
|
1021
|
+
full tier 首跑 35/49。逐条查完,**没有一条失败是模型的错**:
|
|
1022
|
+
|
|
1023
|
+
| 症状 | 真实原因 |
|
|
1024
|
+
|---|---|
|
|
1025
|
+
| 三条正确答案被判错(`qpcr-efficiency`/`hydropathy`/`conservation`) | 判分器的 `normalize()` 只折叠空白,不折叠 **Unicode 减号 U+2212**;`/-3\.3/` 匹配不了 `slope −3.30` |
|
|
1026
|
+
| `fasta-tools` 全线失败 | **runner 从没把任务点名的 `seqs.fa`/`reads.fq` 放进工作目录**,模型正确地拒绝瞎猜 |
|
|
1027
|
+
| `codon-optimize` 数量/长度全错 | 任务要求"顺便去掉 EcoRI 位点",模型传了 `avoid_enzymes`(**更对的做法**),而我钉的是不传时的数字 |
|
|
1028
|
+
| `taqman-assay` 候选数、`conservation` 的 `source:` 标签 | 都取决于模型自己选的窗口/输入形式,**两种都对** |
|
|
1029
|
+
| `qpcr-primers` | 就是上面那个引物方向缺陷——模型是对的,工具是错的 |
|
|
1030
|
+
|
|
1031
|
+
由此写下一条设计规则并写进 README:**钉住"请求所固定的东西",不要钉"实现在某一组参数下
|
|
1032
|
+
恰好返回的东西"**。
|
|
1033
|
+
|
|
1034
|
+
**修法**:`normalize()` 折叠 Unicode 减号与各种破折号;每个任务在自己的 scratch 工作区里
|
|
1035
|
+
跑(顺带解决"每次跑完仓库根目录多出一堆 SVG/FASTA/JSON");把上表里的断言逐条改成
|
|
1036
|
+
断言语义而非某次调用的数字。
|
|
1037
|
+
|
|
1038
|
+
### 4. 新增的两个守卫与两个 npm script
|
|
1039
|
+
|
|
1040
|
+
- `test/benchmark-coverage.mjs`:57/57 覆盖、fixture 前提、tier 健全、占位符全部可展开、
|
|
1041
|
+
`NETWORK_TASKS` 里不许有 tool 断言(联网工具的结果无法钉住)。
|
|
1042
|
+
- `bench:full` / `bench:list:full`:跑/列出全部 49 题。
|
|
1043
|
+
|
|
1044
|
+
`benchmark/verifications.mjs` 是新文件:一个任务的**期望文本**在 `tasks/`,产生它的
|
|
1045
|
+
**输入**在这里。分开放正是 `--offline` 能有意义的原因——两者不一致时会失败,而不是
|
|
1046
|
+
"断言了工具碰巧输出的任何东西"。
|
|
1047
|
+
|
|
1048
|
+
### 5. 在线任务与其边界
|
|
1049
|
+
|
|
1050
|
+
`molbio_pubmed_search` / `molbio_pubmed_abstract` 打的是活的 PubMed API,**返回什么取决于
|
|
1051
|
+
今天的 PubMed**,没有可钉住的值。它们的任务因此**只断言最终回答**(出现 PMID、出现标题),
|
|
1052
|
+
并在 `verifications.mjs` 的 `NETWORK_TASKS` 里登记;守卫会拒绝"登记为在线却仍有 tool
|
|
1053
|
+
断言"的任务。实测这两个工具在 headless profile 里**可用**(`dsh-web-search-deepseek` +
|
|
1054
|
+
`dsh-web-fetch-http` 随 base 挂载,凭据已就位)。
|
|
1055
|
+
|
|
1056
|
+
### 发版清单
|
|
1057
|
+
|
|
1058
|
+
| 项 | 内容 |
|
|
1059
|
+
|---|---|
|
|
1060
|
+
| 工具数 | 57(不变) |
|
|
1061
|
+
| 插件改动 | **无**(`index.mjs` 与所有领域模块未改) |
|
|
1062
|
+
| 客户端产物 | **无需重建** |
|
|
1063
|
+
| preset | 未改 |
|
|
1064
|
+
| 新增 | `benchmark/tasks/`(9 个文件)、`benchmark/verifications.mjs`、`benchmark/fixtures.mjs`、`benchmark/_probe-all.mjs`、`test/benchmark-coverage.mjs`、`test/fixtures/pUC118.gb` |
|
|
1065
|
+
| 改 | `benchmark/{README.md,run.mjs,score.mjs,sequences.mjs}`、`docs/{rules,workflow}.md`、`README.md`、`package.json`、`.gitignore` |
|
|
1066
|
+
| 删 | `benchmark/tasks.json`(拆成 `benchmark/tasks/*.json`) |
|
|
1067
|
+
| 预检 | `npm test`(13 项,含三道 benchmark 零成本门) |
|
|
1068
|
+
|
|
1069
|
+
## [0.14.0] — 2026-09-23(项目规范化 + 首个可用性 benchmark)
|
|
1070
|
+
|
|
1071
|
+
**工具数不变(57),插件行为零变化**——这一版不新增也不修改任何工具,改的是**这个项目怎么
|
|
1072
|
+
维护**,以及**它第一次能被测量**。因此它是 minor 而不是 patch:新增了包内公开面
|
|
1073
|
+
(`benchmark/` 目录、两个测试套件、若干 npm script)。
|
|
1074
|
+
|
|
1075
|
+
### 1. 文档重构:按"你要回答的问题"拆开
|
|
1076
|
+
|
|
1077
|
+
`docs/maintainer.md` 原来有 626 行,把"不能违反的规则""怎么操作""未来方向""历史教训"
|
|
1078
|
+
混在一起,后果是**发布纪律在同一份文件里出现了两遍**(「发布与更新流程」与末尾的
|
|
1079
|
+
「发版时唯一容易漏的一步」),两处内容已经不一致。现在拆成四份,每份一个职责,
|
|
1080
|
+
`maintainer.md` 变成导航页 + 60 秒速览 + 目录结构:
|
|
1081
|
+
|
|
1082
|
+
| 文档 | 回答的问题 |
|
|
1083
|
+
|---|---|
|
|
1084
|
+
| `docs/rules.md` | 什么**不能**违反(每条都注明它是哪次事故换来的) |
|
|
1085
|
+
| `docs/workflow.md` | **怎么做**:测试金字塔、客户端半、发布预检、DSH 升级 |
|
|
1086
|
+
| `docs/roadmap.md` | 接下来**往哪走**,以及明确不做的清单 |
|
|
1087
|
+
| `docs/history.md` | **已经发生了什么**、每个 bug 换来哪条规则 |
|
|
1088
|
+
|
|
1089
|
+
README 与 CHANGELOG 里原有的 `maintainer.md` 链接**全部保持有效**(该文件仍在)。
|
|
1090
|
+
|
|
1091
|
+
### 2. benchmark:第一次能回答"模型会不会用这套工具"
|
|
1092
|
+
|
|
1093
|
+
在这之前,仓库里**每一个检查都是离线的**:工具算得对不对(`smoke`)、图对不对
|
|
1094
|
+
(`svgpng`)、挂不挂得上(`preset-health`/`contract`)。**没有一个能回答"模型拿到一句
|
|
1095
|
+
自然语言的生物学请求,会不会找到正确的工具、把参数填对、把结论说出来"**——而这恰恰是
|
|
1096
|
+
这个工具集的价值所在。新增 `benchmark/`:
|
|
1097
|
+
|
|
1098
|
+
- **15 条任务 / 12 个工具 / 11 个类别**(序列、引物、克隆、酶切、qPCR、比对、组成、
|
|
1099
|
+
实验台分析、实验算术、可视化、克制性),判分拆成**三个独立维度**:
|
|
1100
|
+
`tools_ok`(工具选择)/ `args_ok`(参数:读工具结果)/ `answer_ok`(结论:读最终回答)。
|
|
1101
|
+
拆开是因为"它失败了"不可行动,而三者指向完全不同的地方。
|
|
1102
|
+
- **真实模型实测(dsh 0.1.7-alpha.2 + deepseek-flash,2026-09-23)**:
|
|
1103
|
+
**15/15 任务通过;工具选择 15/15、参数 15/15、结论 15/15**;69 次工具调用、
|
|
1104
|
+
2.47M tokens、355 s。报告在 `benchmark/reports/`(不进 git),跑法 `npm run bench`。
|
|
1105
|
+
**这个数字是单次样本,不是保证**:`benchmark/README.md` 记了一次实测的run-to-run
|
|
1106
|
+
差异(同一条任务两种都正确、措辞不同的回答),断言因此按**内容**而不是**措辞**写。
|
|
1107
|
+
- **但先看下面第 3 节**:要拿到这个 15/15,先得修掉五个"测量本身的错"——第一版跑出来的
|
|
1108
|
+
0/15 测的是 benchmark,不是插件。
|
|
1109
|
+
- **两道零成本离线门**(进 `npm test`):`bench --offline` 把每条 `where: "tool"` 断言
|
|
1110
|
+
拿去和**工具真实渲染出来的文本**比对(不是 JSON——见下);`test/benchmark-profile.mjs`
|
|
1111
|
+
逐字段比对 headless profile 与 preset 行清单,**上游加一行就会红**。
|
|
1112
|
+
- **benchmark 的 profile 是推导出来的,不是手写的**:`benchmark/profile.mjs` 从
|
|
1113
|
+
`preset/molbio-lab/agent.cordis.yml` 生成 `$DSH_HOME/profiles/molbio-bench`。
|
|
1114
|
+
之所以要它:**`dsh-headless` 明确拒绝跑在 agent preset 下的会话**
|
|
1115
|
+
(*the one-shot runner does not compose*),所以工具必须以**宿主行**挂载——
|
|
1116
|
+
这正是本包唯一拒绝发布的形状(bundle 的 `cordis.patch.yml` 是空列表)。
|
|
1117
|
+
推导 + 逐字段守卫,是让这份重建不会静默漂移的代价。
|
|
1118
|
+
|
|
1119
|
+
### 3. 抓到并修掉的五个"测量本身的错"
|
|
1120
|
+
|
|
1121
|
+
这一版最贵的一课是:**benchmark 的第一版测的是它自己**。修掉的是测量工具的缺陷,
|
|
1122
|
+
不是插件的缺陷——每一条都记进了代码注释与 `benchmark/README.md`:
|
|
1123
|
+
|
|
1124
|
+
1. **指令根本没送到模型**。`spawn(..., { shell: true })` 在 Windows 上**拼接** argv 而不
|
|
1125
|
+
转义,多行任务被截成**第一个词**(`"Save this text exactly…"` → `Save`)。第一次完整
|
|
1126
|
+
运行 15 题全废,而唯一症状是模型说"序列不在对话里"。现在改为**不用 shell**、
|
|
1127
|
+
直接以 `process.execPath` 调 `dsh` 的 JS 入口,并把任务走 **stdin**
|
|
1128
|
+
(`dsh-headless` 支持),另加一次**送达 preflight**(探针 token 不回显就整体拒绝运行)。
|
|
1129
|
+
2. **离线门和运行时用的不是同一份文本**。`where: "tool"` 断言在离线侧比对**原始 JSON**,
|
|
1130
|
+
而运行时的 trace **只带渲染文本**——于是离线全绿、实跑永远不可能命中。
|
|
1131
|
+
`scoreSuiteOffline` 现在渲染,`--offline` 真正覆盖了这条通路。
|
|
1132
|
+
3. **`tool_result` 按到达顺序归因**。改为按 `callId` 关联;没有对应调用的结果报
|
|
1133
|
+
`(unattributed)`,而不是赖给隔壁那次调用。
|
|
1134
|
+
4. **工具结果 8 KiB 截断把证据切掉了**。模型多跑 20 次网络检索后,早先的工具结果会掉出
|
|
1135
|
+
捕获窗口,于是断言失败而模型其实读到了。`foldEvents` 现在上报
|
|
1136
|
+
`resultBytes`/`resultsTruncated`,`scoreTrace` 把这种失败标成 `suspectTruncation`——
|
|
1137
|
+
**截断造成的失败不算模型的证据**。
|
|
1138
|
+
5. **六条断言在给措辞打分,而不是给内容打分**。逐条修掉并写进注释:字面量 `GAATTC`
|
|
1139
|
+
(模型写切点记法 `G^AATTC`)、要求"2 bands"(模型正确区分了"2 个片段"与"1 kb ladder
|
|
1140
|
+
上实际可见的 1 条带")、要求字面 `?`(headless 组合没有问答器,模型被要求在散文里提问)、
|
|
1141
|
+
以及把 `seq1/seq2` 参数顺序钉死的错配断言(`molbio_align` 的同一性是**对称的**,
|
|
1142
|
+
模型传 `sequence1 = read` 完全正确)。
|
|
1143
|
+
|
|
1144
|
+
**守卫**:新增 `test/benchmark-score.mjs`,用**真实运行的响应**(冻结在
|
|
1145
|
+
`test/fixtures/benchmark-traces.json`,`benchmark/_freeze-trace.mjs` 可重新生成)回放
|
|
1146
|
+
判分器,并证明**判分器自己还能失败**(callId 归因、截断上报、无工具调用必须判选择失败、
|
|
1147
|
+
塞一条不可能满足的断言必须 FAIL)。合成 trace 抓不到上面第 5 类 bug——它们是"模型怎么
|
|
1148
|
+
措辞"的性质,所以证据必须活过产生它的那次运行。
|
|
1149
|
+
|
|
1150
|
+
### 4. 仓库可移植性:清掉写死的用户路径
|
|
1151
|
+
|
|
1152
|
+
`test/smoke.mjs` 与 `test/map-card.mjs` 曾以
|
|
1153
|
+
`file:///C:/Users/18771/AppData/...` 硬导入 harness 的 `dsh-tools`——**这套测试只可能在一台
|
|
1154
|
+
机器上通过**,而且在别的机器上它会**静默导入另一个 harness**(与 `preset-health`/`contract`
|
|
1155
|
+
校验的那个不是同一个)。`harness.mjs` 里那条 `C:\Users\18771\...` 探测路径同样只在一台机器
|
|
1156
|
+
上是对的。现在 `benchmark/harness.mjs` 是**唯一**的 harness 定位实现
|
|
1157
|
+
(`DSH_HARNESS_ROOT` → `npm prefix -g` → 平台惯例),`preset-health`、`drift-probe`、
|
|
1158
|
+
`contract`、`smoke`、`map-card` 全部复用它——顺带消掉了 `drift-probe` 里那份重复的
|
|
1159
|
+
`packageInstalled`/`importPackage`/`readComposition`(重复的 `findHarnessRoot` 能让
|
|
1160
|
+
"变异实验"针对一个**不同的 harness** 通过,从而什么也没守住)。
|
|
1161
|
+
|
|
1162
|
+
### 5. 已知的插件数据疑点(记录,不判分)
|
|
1163
|
+
|
|
1164
|
+
`molbio_methylation_check` 把 BamHI 判为 `impaired by dam`(`GGATCC` 内含 `GATC`);
|
|
1165
|
+
实测中模型跑完工具后去查 NEB,报告 **BamHI 对 dam 不敏感**。任务因此**不断言模型是否同意
|
|
1166
|
+
工具**(它做了对的事:手抄参考表就该交叉核对),但这条分歧记在
|
|
1167
|
+
`benchmark/README.md` 的「Findings」里,作为下一版对着 REBASE 复核表的输入。
|
|
1168
|
+
|
|
1169
|
+
### 发版清单
|
|
1170
|
+
|
|
1171
|
+
| 项 | 内容 |
|
|
1172
|
+
|---|---|
|
|
1173
|
+
| 工具数 | 57(不变) |
|
|
1174
|
+
| 插件改动 | **无**(`index.mjs` 与所有领域模块未改) |
|
|
1175
|
+
| 客户端产物 | **无需重建**(`lib/`、`packages/molbio-panel/lib/` 未改) |
|
|
1176
|
+
| preset | `agent.cordis.yml` / `preset.patch.yml` 未改 |
|
|
1177
|
+
| 新增 | `benchmark/`(README、tasks.json、run/score/profile/harness/tools/sequences/_probe/_freeze-trace)、`test/benchmark-profile.mjs`、`test/benchmark-score.mjs`、`test/fixtures/benchmark-traces.json`、`docs/{rules,workflow,roadmap,history}.md` |
|
|
1178
|
+
| 改 | `docs/maintainer.md`(变导航页)、`README.md`、`.gitignore`、`package.json`(0.14.0 + scripts)、`test/{smoke,map-card,contract,drift-probe,preset-health}.mjs`(复用 harness 解析、去重) |
|
|
1179
|
+
| 预检 | `npm test`(12 项,含两道 benchmark 离线门)+ `git status --short` |
|
|
1180
|
+
|
|
1181
|
+
## [0.13.1] — 2026-09-22(适配 DSH 0.1.7-alpha.2;修 preset 排序碰撞)
|
|
1182
|
+
|
|
1183
|
+
**alpha.2 对插件是兼容的**,逐项实测过(不是"看代码觉得对"):
|
|
1184
|
+
|
|
1185
|
+
| 检查 | 结果 |
|
|
1186
|
+
|---|---|
|
|
1187
|
+
| 10 个套件(静态契约) | 全绿;`contract` 15/15、`drift-probe` 11/11、`preset-health` 30 行 |
|
|
1188
|
+
| 上游 `standard` preset(drift 基线) | **逐行未变**,本包行清单无需改动 |
|
|
1189
|
+
| `dsh-agent-preset` / `-registry` 的 Config schema | **未变**(仍只要求 `id`+`plugins` / `default`) |
|
|
1190
|
+
| **运行时**(scratch profile + 换端口起服务) | `agentPresets/list` 的 `broken` **为空**;`pluginInventory/list` 里 `tool-molbio` = **`fiberPhase: active`**,且**只**出现在 `molbio-lab`,其余四个 preset 与全局层都没有它 |
|
|
1191
|
+
|
|
1192
|
+
最后一行是关键:上一轮的教训是**静态 `--dump-config` 看不出 preset 是否真的活着**,所以这次按
|
|
1193
|
+
`docs/maintainer.md` 的判据做了真实运行时验证,而不是只跑测试。
|
|
1194
|
+
|
|
1195
|
+
### 修复:preset 的 `order: 2` 与上游 `ptc` 撞号
|
|
1196
|
+
|
|
1197
|
+
上一轮给本包 preset 写的 `order: 2` **与 DSH 自带的 `ptc` 相同**(上游占用 standard=1、
|
|
1198
|
+
ptc=2、minimal=3、cordis=4)。注册表按 `order ?? Infinity` 排序、**平手时以
|
|
1199
|
+
`id.localeCompare` 兜底**,于是 `molbio-lab` 与 `ptc` 的先后变成字母序的偶然——实测它被排到了
|
|
1200
|
+
**最后**,而不是列表中该在的位置。
|
|
1201
|
+
|
|
1202
|
+
改为 `order: 5`(上游之外的空号),顺序确定:standard → ptc → minimal → cordis → molbio-lab。
|
|
1203
|
+
|
|
1204
|
+
`preset-health.mjs` 增加**排序碰撞守卫**:本包 preset 的 order 若与任何上游 preset 相同即 FAIL,
|
|
1205
|
+
并用突变实验证明它会失败(把 order 改回 2):
|
|
1206
|
+
|
|
1207
|
+
```
|
|
1208
|
+
preset-health FAILED: 1 preset order collision(s) with a shipped preset
|
|
1209
|
+
- order 2 is already taken by the shipped preset "ptc" (ptc.patch.yml)
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
这条守卫防的是"肉眼看不出、只在选择器里顺序怪"的那类回归。
|
|
1213
|
+
|
|
1214
|
+
工具仍 57;无版本目录;包版本 0.13.0 → **0.13.1**。
|
|
1215
|
+
|
|
1216
|
+
## [0.13.0] — 2026-09-18(适配 DSH 0.1.7-alpha.1:修掉面板读文件全坏 + preset 迁移;工具仍 57)
|
|
1217
|
+
|
|
1218
|
+
**这一版是被 DSH 升级"考"出来的**,两个问题都属于本仓库最在意的那一类——**测试全绿,功能却坏了**。
|
|
1219
|
+
插件本体(57 个工具、宿主组合层)在新版上一直是好的;坏的是**浏览器面板读文件**和
|
|
1220
|
+
**preset 安装渠道**,而现有断言恰好都盯着**旧行为**,所以没有一个红灯。
|
|
1221
|
+
|
|
1222
|
+
### 修复 1(严重):0.1.7-alpha.1 上面板**打不开任何文件**
|
|
1223
|
+
|
|
1224
|
+
**症状**:右侧栏 Molbio 面板能看到文件列表,点任何 `.dna`/`.fasta` 都报错,质粒图谱永远画不出来。
|
|
1225
|
+
|
|
1226
|
+
**根因**:`readBytes()` 对**方法**做了特性探测(`readAll` 在就调它,否则调 `readBytes`),
|
|
1227
|
+
却对**值的形态**没有探测——两条分支最后都交给 `decodeBase64Bytes()`,而那个函数**只接受 base64
|
|
1228
|
+
字符串**:
|
|
1229
|
+
|
|
1230
|
+
```js
|
|
1231
|
+
if (typeof base64 !== 'string') throw new MolbioInputError('expected a base64 string …');
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1234
|
+
0.1.7-alpha.1 删掉了 `workspaceFiles.readAll`,`readBytes` 成了唯一路径,而它返回的 `data` 是
|
|
1235
|
+
**`Uint8Array`**(gateway 把原生字节抬成 base64 attachment,客户端再重组)。于是回退分支**必然抛错**。
|
|
1236
|
+
|
|
1237
|
+
**修法**:把 `decodeBase64Bytes` 换成 `decodeWorkspaceBytes`——**探测方法,归一化值**:字符串按
|
|
1238
|
+
base64 解,`Uint8Array` 原样透传(不复制,大质粒零额外开销)。
|
|
1239
|
+
|
|
1240
|
+
**为什么没被测出来(这版的重点)**:`test/contract.mjs` 曾**断言宿主必须实现 `readAll`**
|
|
1241
|
+
(0.1.7 上直接 FAIL),而 `test/panel-render.mjs` 的 Remote 桩件**只提供 `readAll`**——两条断言
|
|
1242
|
+
合起来把回退分支**整个罩住了**:真机上唯一能走的路,测试里一次都没走过。所以这一版:
|
|
1243
|
+
`panel-render.mjs` 的默认桩件改成**新版形态**(有 `readBytes`、**没有 `readAll`**),
|
|
1244
|
+
所有既有断言现在都跑在**真机实际路径**上;另加一块**旧形态回归**(`legacyReadAll`),
|
|
1245
|
+
两条分支都钉住。`contract.mjs` 改为钉 `read`/`readBytes`/`list` 与 gateway 的字节附件编码。
|
|
1246
|
+
|
|
1247
|
+
### 修复 2:preset 渠道在 0.1.7-alpha.1 上**完全失效**
|
|
1248
|
+
|
|
1249
|
+
0.1.7-alpha.1 移除了 `@deepseek-ai/dsh-agent-presets` 及其**目录发现**机制——
|
|
1250
|
+
新注册表**既不扫描目录、也不接受 preset 路径**(官方 README 原文)。preset 现在是 bundle patch
|
|
1251
|
+
里的一个 `@deepseek-ai/dsh-agent-preset` **行**,`config.plugins` 装原来的行清单。后果:
|
|
1252
|
+
|
|
1253
|
+
- `preset/molbio-lab/agent.cordis.yml` 成了**无消费者的孤儿**,选择器里再不会出现该模式;
|
|
1254
|
+
- `preset/install.mjs` 往 profile 写的 `- id: agent-presets` + `config.roots` 目标行**已不存在**,
|
|
1255
|
+
每次组合都打印 `patch: entry "agent-presets" not found`;
|
|
1256
|
+
- `~/.dsh/.agent-presets/molbio-lab/` 是**死副本**(且停在 v16 / 46 个工具)。
|
|
1257
|
+
|
|
1258
|
+
**修法**:新增生成器 `build/preset-patch.mjs`,把 `agent.cordis.yml`(**行清单**,仍是唯一手改处)
|
|
1259
|
+
包成 `preset/molbio-lab/preset.patch.yml`,并由 `dsh.bundle.patch` 声明为**第二个 patch 层**
|
|
1260
|
+
(宿主层 + preset 层)。**装完即出现**,不再有第 2 步、不再需要 `roots`。
|
|
1261
|
+
`preset/install.mjs` 改为**体检工具**:验证 bundle 是否被选中、真实跑一次
|
|
1262
|
+
`--dump-config` 确认 `preset-molbio-lab` 进了组合,并报出上述两处遗留及其清理方法。
|
|
1263
|
+
|
|
1264
|
+
**验证**(不是"看代码觉得对"):`dsh --profile molbio-web --dump-config` 退出 0,
|
|
1265
|
+
组合树里出现 `preset-molbio-lab` / `id: molbio-lab` / 33 行 `plugins`。
|
|
1266
|
+
|
|
1267
|
+
### 修复 3:preset 里的**相对 specifier 在 0.1.7-alpha.1 上根本不解析**
|
|
1268
|
+
|
|
1269
|
+
上面两项修完后,实测仍然**加载失败**——而且是只在真机上才看得见的那一种:
|
|
1270
|
+
|
|
1271
|
+
```
|
|
1272
|
+
Molecular Biology Lab — 加载失败
|
|
1273
|
+
```
|
|
1274
|
+
|
|
1275
|
+
真因不在本仓库的组合,而在**模块说明符的写法**。preset 的 `tool-molbio` 行原本写的是相对路径
|
|
1276
|
+
`./plugins/dsh-molbio-tools-v20/index.mjs`(沿用 0.1.6 及更早的写法)。在 0.1.7-alpha.1 上,
|
|
1277
|
+
|
|
1278
|
+
- preset **能挂载**(`agentPresets/list` 无 `broken`),
|
|
1279
|
+
- 但那一行**永远不会被 import**:roster 报
|
|
1280
|
+
`tool-molbio (./plugins/dsh-molbio-tools-v20/index.mjs): never started`,
|
|
1281
|
+
`compositionInventory` 里它的 `fiberPhase` 是 `null`。
|
|
1282
|
+
|
|
1283
|
+
改成**包名** `name: 'dsh-molbio-tools'` 即恢复正常(实测 `fiberPhase: active`)。顺带把
|
|
1284
|
+
`cordis.patch.yml` 里那条宿主层 `tool-molbio` 也去掉了:它在**全局层**注册同样的 57 个工具,
|
|
1285
|
+
与本包"工具只属于 molbio-lab 这一个模式"的设计相抵触(也是两处重复注册的来源)。
|
|
1286
|
+
现在 bundle 的两个 patch 层分工明确——`cordis.patch.yml` **什么都不插**(为空列表,留给将来
|
|
1287
|
+
真正宿主层的东西),`preset.patch.yml` 声明 preset 并携带工具行。
|
|
1288
|
+
|
|
1289
|
+
**验证方式(本轮的关键教训)**:不再只跑静态检查。用一个 scratch profile
|
|
1290
|
+
(`--from-default-profile web` + link 本包)真起一个 `dsh web` 到另一个端口,带 cookie 认证调
|
|
1291
|
+
`/api/agentPresets/list` 与 `/api/pluginInventory/list`,读 `broken` / `fiberPhase` 字段。
|
|
1292
|
+
`broken` 与 `fiberPhase` 是仅有的两个能证明"preset 真的活着"的字段,静态
|
|
1293
|
+
`--dump-config` **看不出**这个问题。
|
|
1294
|
+
|
|
1295
|
+
> **顺带删除的负重:`preset/molbio-lab/plugins/dsh-molbio-tools-v11…v20/`。**
|
|
1296
|
+
> 版本目录机制是为规避**相对文件 URL** 的 ESM 模块缓存而设的:preset 行写相对路径,于是
|
|
1297
|
+
> 每次发版都要把全部 `.mjs` 复制进一个新目录(本次删除前是 **195 个文件 / 5.2 MB**)。
|
|
1298
|
+
> 改用包名后这个理由不复存在,十个目录与 `preset-health.mjs` 的**镜像检查**一并删除。
|
|
1299
|
+
> 腾出的位置由一条更有价值的检查补上:**preset 行不得写相对路径**——正是本轮的真因,
|
|
1300
|
+
> 已用突变实验证明它会失败(把 specifier 改回 `./plugins/…`,`preset-health` 立即 FAIL)。
|
|
1301
|
+
|
|
1302
|
+
### 测试基线的迁移
|
|
1303
|
+
|
|
1304
|
+
`preset-health.mjs` / `drift-probe.mjs` 的**上游基线**改成
|
|
1305
|
+
`@deepseek-ai/dsh-web-app/presets/standard.patch.yml`(旧包的 `presets/standard/agent.cordis.yml`
|
|
1306
|
+
已消失——`drift-probe` 之前是**直接崩溃**,`preset-health` 则因 `existsSync` 守卫**静默跳过**整段
|
|
1307
|
+
漂移比对,等于悄悄停止看守)。两侧都通过新的 `presetPlugins()` 从 `config.plugins` 取行;
|
|
1308
|
+
`--check` 不再允许静默跳过:找不到基线会明确告警。
|
|
1309
|
+
|
|
1310
|
+
一处静默陷阱顺手修掉:`drift-probe` 的 `driftText` 对**已展平**的行列表再调一次 `orderedRows`,
|
|
1311
|
+
会重新进入每个 group 行的 `config`、把子行**发两遍**,表现为"位置 14 顺序不符"的假漂移。
|
|
1312
|
+
|
|
1313
|
+
工具仍 57。**不再有版本目录**:`preset/molbio-lab/plugins/` 已整体删除,preset 行按包名引用本包。
|
|
1314
|
+
|
|
1315
|
+
## [0.12.0] — 2026-09-18(v20:修掉比对器丢残基 + 长叶名折行;工具仍 57)
|
|
1316
|
+
|
|
1317
|
+
**这一版处理的是两个"沉默的错"。** 一个是**数据完整性**:渐进比对会把放不下的末端残基**静默丢掉**
|
|
1318
|
+
(11 bp 对 10 bp 返回 10 列,第 11 个碱基消失),而系统发生学从被截断的数据出发是最坏的一类错误。
|
|
1319
|
+
另一个是**画面正确性**:树图的长叶名被画成一行、**直接压到相邻标签上**,图看着"有东西"但读不出来。
|
|
1320
|
+
两者都不抛错、都不影响其它套件,正是本项目反复强调的那种"全绿但错了"的故障。
|
|
1321
|
+
|
|
1322
|
+
### 修复 1:`msa.mjs` 渐进比对丢尾部残基(v19 发现、v20 修复)
|
|
1323
|
+
|
|
1324
|
+
**根因不是"忘了补缺口",而是补错了位置。** 半全局(末端缺口免费)的评分**允许**最优路径在任一
|
|
1325
|
+
序列末尾之前停下——对 `ACGTACGTACG`(11)与 `ACGTACGTAC`(10),DP 在 `(10,10)` 得 40 分,而在
|
|
1326
|
+
`(11,10)` 只有 30 分,所以"丢掉最后一个 G"**确实是分数最优的选择**。真正的问题是:路径**起点之前**
|
|
1327
|
+
的残基有 `while (i > 0)` / `while (j > 0)` 两个循环补成悬垂列,**终点之后**的残基却从来没有人发出来。
|
|
1328
|
+
于是路径保守地选了"不碰它",它就消失了。
|
|
1329
|
+
|
|
1330
|
+
修法:把端点之后的残基作为**纯悬垂列**追加到结果里(`suffixA`/`suffixB` 单独按左到右顺序收集,
|
|
1331
|
+
避免被 traceback 末尾那一次 `reverse()` 搅乱——第一版就是踩了这个坑,输出成 `GACGTACGTAC`)。
|
|
1332
|
+
`score` **不变**,因为悬垂列计 0 分,和被它替换掉的末端缺口同分。
|
|
1333
|
+
|
|
1334
|
+
- **不变式**:每条输出行去掉缺口后**逐字符等于它的输入序列**(顺序也算)。测试逐条断言这一点,
|
|
1335
|
+
而不是断言某个列数——列数只是这条不变式的推论。
|
|
1336
|
+
- **`coverageShortfall` 从 `index.mjs` 抽到 `phylo.mjs` 并导出**:v19 加的"覆盖度不足"WARNING
|
|
1337
|
+
现在**不该再对真实输出触发**,但守卫本身必须留着(它会抓到未来的回归)。测试因此**用一个故意
|
|
1338
|
+
截断的行去驱动它**,证明它仍然会响——"没人见过失败的守卫不算守卫"。
|
|
1339
|
+
- **行为变化**:从 v15 起的所有比对输出在**存在悬垂**时列数会变(变多)。等长输入**完全不变**
|
|
1340
|
+
(没有悬垂可补)。v15–v19 的已知值断言已逐条复核:`smoke.mjs` 里唯一钉住旧行为的那一条
|
|
1341
|
+
(`alignment_columns === 10`,注释写着"比对着会 pad 而不是插 gap")**钉的正是这个 bug**,
|
|
1342
|
+
已改成 11 并加了"覆盖度警告必须不再出现"。
|
|
1343
|
+
|
|
1344
|
+
### 修复 2:`svgpng.mjs` 支持 `<tspan>` 多行文本(长叶名不再互相压)
|
|
1345
|
+
|
|
1346
|
+
光栅化器此前把 `<text>` 里的**任何**嵌套标签当作"这一段文本结束",`<tspan>` 因此落进
|
|
1347
|
+
`unsupported` 并被当作独立文本——**同一段文字被画两遍**,看起来就是两行字叠在一起。
|
|
1348
|
+
|
|
1349
|
+
- **解析器**:`<text>` 现在是唯一支持嵌套的地方,产出 `runs`(每个 `<tspan>` 一个)。`x`/`y`
|
|
1350
|
+
绝对定位与 `dx`/`dy` 相对偏移都支持;空的自闭合 `<tspan dy="14"/>` 也会推进基线("空行")
|
|
1351
|
+
——这正是把标签折成多行的机制。
|
|
1352
|
+
- **诚实边界**:`<tspan>` 带 `transform`、`<tspan>` 里再嵌 `<tspan>`,都**计入 `unsupported`**
|
|
1353
|
+
并沿用原有文本,而不是猜一个位置画出来。测试同时钉住"报告"和"不误画"。
|
|
1354
|
+
- **`font-metrics.mjs`(新模块)**:把字体的**字宽(0.6 em)与每 em 网格数**从光栅化器里抽出来,
|
|
1355
|
+
这是唯一能让"排版时预留的宽度"和"真正画出来的宽度"是同一个数、不会各自漂移的办法。
|
|
1356
|
+
`svgpng.mjs` 与 `svgio.mjs` 都 import 它;它**不进客户端产物**(`svgpng.mjs` import
|
|
1357
|
+
`node:zlib`,所以浏览器半永远不能 import 这两者)。
|
|
1358
|
+
- **`svgio.textSpanLines` / `svgio.wrapTextLines`(新助手)**:按**真实字宽**折行(而不是 v19 那个
|
|
1359
|
+
手写的 `字数 × 字号 × 0.62` 估算)。长标识符在**分隔符之后**断行(`_`、`-`、`.`、`|`、`:`),
|
|
1360
|
+
因为把 `Escherichia_coli_K12_MG1655_chromosome_complete` 从词中间劈开读起来像损坏;
|
|
1361
|
+
没有分隔符时按字数硬断,**绝不允许溢出预算**。折行**无损**(断点只吃掉空白)。
|
|
1362
|
+
|
|
1363
|
+
### 改进:树图的矩形布局给叶名真正的列宽
|
|
1364
|
+
|
|
1365
|
+
- 叶名按预留列宽折成多行 `<tspan>`(列宽 = 面板宽度的 40%,下限 90 px);**字号只在必要时才缩小**
|
|
1366
|
+
(下限 5 px),因为把标签缩小到读不了比折行更糟。行距按"每叶一行必须放得下"来收敛。
|
|
1367
|
+
- 折行**只在超宽时发生**:短标签是单行、几何与 v19 几乎一致。**但并非逐字节相同**——v19 用
|
|
1368
|
+
`字数 × 字号 × 0.62`(每字符 6.82 px @11)预留,现在用真实字宽(每字符 6.6 px),所以预留的
|
|
1369
|
+
标签列略宽、分支尖端相应左移几像素。这是把估算换成真值的结果,不是回归。
|
|
1370
|
+
- 环形与扇形布局**不折行**:旋转过的文本没有按真实字宽测量,硬折会算出错误的行数;两者都在面板
|
|
1371
|
+
宽度外画标签,`svgpng.mjs` 对旋转运行的处理也保持原样。
|
|
1372
|
+
|
|
1373
|
+
### 顺带修掉:`preset.yml` 的工具数停在 52(v19 漏改)
|
|
1374
|
+
|
|
1375
|
+
v19 把工具从 52 加到 57,README、组合注释、工具表都改了,**唯独预设选择器里那行描述还写着
|
|
1376
|
+
"52 个 molbio_* 工具"**——用户在整个 v19 周期看到的是错的数字,而没有任何检查会发现它。
|
|
1377
|
+
已改为 57 并补上 v19/v20 的新能力说明。**更重要的是补了守卫**:`preset-health` 现在把
|
|
1378
|
+
`preset.yml` 描述里的工具数与**插件真正注册的数量**对比(`toolCountDrift`,导出给 `drift-probe`
|
|
1379
|
+
驱动),声称 52 而实际 57 会**失败并阻断发布**;同时确认"没有工具数描述的文案"不会被逼着加一个。
|
|
1380
|
+
|
|
1381
|
+
### 测试与验证方式
|
|
1382
|
+
|
|
1383
|
+
- **`test/smoke.mjs`**:v20 段改写为**残基守恒不变式**——对多组长度不等的输入,逐条断言
|
|
1384
|
+
`行.replace(/-/g,'') === 输入序列`(含顺序),并断言对齐列数一致;直接断言
|
|
1385
|
+
`coverageShortfall` 对**故意截断的行**仍然报 `u2 (10 of 11 bases kept)`。
|
|
1386
|
+
- **`test/svgpng.mjs`(18 → 22 项)**新增四项**像素级**断言(不是文本断言):
|
|
1387
|
+
`<tspan>` 三行各自落在自己的基线上、**行间是空的**(塌到一条基线上会失败);`dy` 相对堆叠
|
|
1388
|
+
逐级下移、**相邻两个基线之间确实空着**(证明是移动而不是重印);自闭合空 `<tspan>` 推进一个
|
|
1389
|
+
空行;`<tspan>` 的 `transform` 被**报告**而不是猜着画;以及**长叶名的树图**——扫描标签列,
|
|
1390
|
+
断言至少每叶两行、且 4 个标签块之间**至少留出 3 处空白间隔**(v19 的单行长标签会把这个数字
|
|
1391
|
+
压到 0 或 1,也就是"压在一起")。这一条是这次修复的**图像回归守卫**。
|
|
1392
|
+
- **`test/drift-probe.mjs`(11 项)**新增三条驱动 `toolCountDrift`:描述正确时**零噪音**
|
|
1393
|
+
(含"把 90+ 限制酶、2–50 条序列这类其它数字不当成工具数")、写明 v19 那种 52 vs 57 的
|
|
1394
|
+
**过时描述必须被抓到**、没有工具数描述时**不被逼迫**。
|
|
1395
|
+
- **`test/preset-health.mjs`**:新增工具数比对;镜像检查报
|
|
1396
|
+
`mirror OK: dsh-molbio-tools-v20 matches the package root module-for-module`。
|
|
1397
|
+
|
|
1398
|
+
### 发版要点
|
|
1399
|
+
|
|
1400
|
+
- **工具仍 57 个**(README 的工具表不变)。改动的是**正确性**,不是工具数量。
|
|
1401
|
+
- **必须新建 preset 目录**:`preset/molbio-lab/plugins/dsh-molbio-tools-v20/`(30 个模块:
|
|
1402
|
+
v19 的 29 个里 `index.mjs`/`msa.mjs`/`phylo.mjs`/`svgio.mjs`/`svgpng.mjs` 有改动,
|
|
1403
|
+
**新增 `font-metrics.mjs`**),`tool-molbio` 行已指向 v20。
|
|
1404
|
+
- **客户端产物已同批重建**:`lib/client.js` 与 `packages/molbio-panel/lib/client.js`
|
|
1405
|
+
(334.3 KB → 335.9 KB)——`msa.mjs` 在浏览器半里也用(面板的比对数据通路),改了就必须重建。
|
|
1406
|
+
`font-metrics.mjs`/`svgio.mjs`/`svgpng.mjs` 三者**都不进客户端**。
|
|
1407
|
+
- `package.json`:`files` 白名单新增 `font-metrics.mjs`(漏了就是"装完却没有这个功能"的静默失败),
|
|
1408
|
+
版本 0.12.0;`packages/molbio-panel` 版本 0.1.1 → 0.1.2(产物已变)。
|
|
1409
|
+
- route-B 用户重启 profile 即可;复制渠道用户请重拷 `agent.cordis.yml` 与新的 v20 目录。
|
|
1410
|
+
- 升级说明一句话:**"比对器不再静默丢掉放不下的末端残基(每条输出行的残基与输入逐字符一致),
|
|
1411
|
+
系统发生树的长叶名会折行而不是压在一起;顺带修掉了预设描述里停了一版的工具数"**。
|
|
1412
|
+
|
|
1413
|
+
## [0.11.0] — 2026-09-18(v19:实验台分析五件套;工具 52 → 57)
|
|
1414
|
+
|
|
1415
|
+
**这一版补的是"实验台第一问"。** 之前 52 个工具覆盖了"设计、构建、验证",但一个测序/分析型
|
|
1416
|
+
用户最先问的五件事没有工具回答:**数据质量好不好**、**这个基因能不能表达**、**这对引物还会在
|
|
1417
|
+
哪儿扩增**、**这段是不是启动子/起点**、**这些样品谁跟谁近**。方向出自
|
|
1418
|
+
[docs/capability-gap-survey.md](docs/capability-gap-survey.md) 的"必做 top-5",实现计划与逐项口径见
|
|
1419
|
+
[docs/v19-plan.md](docs/v19-plan.md)。
|
|
1420
|
+
|
|
1421
|
+
### 新增(5 个工具 + 6 个模块)
|
|
1422
|
+
|
|
1423
|
+
- **`molbio_fastq_qc`**(`fastq-qc.mjs`):读级质控——每碱基质量(均值 + **四分位/10-90 分位**)、
|
|
1424
|
+
每读质量直方图、每碱基 A/C/G/T/N 含量、每读 GC 分布(含 FastQC 同款**理论正态近似**叠加)、
|
|
1425
|
+
长度分布(含直方图与 N50)、**完整序列精确重复率**、过度代表序列、内置公开接头片段扫描、
|
|
1426
|
+
Q20/Q30 与"每碱基均值从第几位跌破 Q30/Q20";另出六面板 SVG 报告。**每个数字都以数组返回**,
|
|
1427
|
+
报告只是同一批数字的画法。
|
|
1428
|
+
- **`molbio_codon_usage`**(`codon.mjs`):CAI(Sharp & Li 1987)、逐密码子 RSCU、Nc(Wright 1990
|
|
1429
|
+
GC3 分箱期望)、GC3/GC123、罕见密码子清单(位置 + 宿主频率 + 相对适应性)、CDS 内 CpG 观测/期望、
|
|
1430
|
+
隐藏终止子与起始/终止密码子检查、可选滑窗局部 CAI;宿主 E. coli / 酵母 / 人。
|
|
1431
|
+
- **`molbio_phylogenetic_tree`**(`phylo.mjs`):p-distance / Jukes-Cantor / Kimura 2P / **Tamura-Nei**
|
|
1432
|
+
校正距离、UPGMA 与**真正实现的邻接法**、bootstrap 支持度(**显式种子、同种子逐字节复现**)、
|
|
1433
|
+
严格/多数共识树、Newick **读写往返**、矩形/环形/扇形三种 SVG 布局与比例尺。
|
|
1434
|
+
- **`molbio_pcr_simulate`**(`pcr.mjs`):双链引物位点搜索、产物大小与坐标、逐位错配明细、
|
|
1435
|
+
**`three_prime_exact`(3' 必须精确匹配的碱基数)与 `max_mismatches` 分离**、IUPAC 简并匹配、
|
|
1436
|
+
环状模板跨 origin 产物、对额外模板(载体等)做错引导筛查、预期凝胶图(复用 `plot.renderGel`)。
|
|
1437
|
+
- **`molbio_gc_composition`**(`composition.mjs`):滑窗 GC、**两套 CpG 岛阈值**
|
|
1438
|
+
(Gardiner-Garden 1987 / Takai & Jones 2002)、GC/AT skew 与**累积 skew 的 ori/ter 指示**、
|
|
1439
|
+
16 个二核苷酸的观测/期望、词频、Shannon 熵、语言复杂度、N50/L50;出三面板图。
|
|
1440
|
+
- **`svgio.mjs`**:4 张新图共用的绘图助手(标尺/坐标轴/面板/图例/配色)。**宿主侧专用**——
|
|
1441
|
+
它和 `svgpng.mjs` 都不进客户端产物,测试盯着这一点。
|
|
1442
|
+
|
|
1443
|
+
### 设计决定(逐条都有测试或明确口径支撑)
|
|
1444
|
+
|
|
1445
|
+
- **"饱和"要有解**:JC 的 p ≥ 0.75、K2P/TN93 参数非正时校正距离**在数学上没有解**。工具把该对
|
|
1446
|
+
记进 `saturated_pairs` 并把距离**夹到模型上限**——返回巨大假值会污染整棵树,直接报错又会拒绝
|
|
1447
|
+
用户合法的分析请求。
|
|
1448
|
+
- **3' 端是独立的旋钮**:`max_mismatches` 与 `three_prime_exact` 分开,是因为生物学上它们不同
|
|
1449
|
+
(中段错配仍能延伸,3' 错配通常不能)。默认 3 bp 精确,测试同时钉住"默认拒绝"与"设 0 后接受"。
|
|
1450
|
+
- **小样本不该有输出**:过度代表序列的门槛是 `max(20 reads, 0.1% × 样本)`,所以 8 条读的样本
|
|
1451
|
+
**合法地报告 0 条**。测试同时钉住"小样本 0 条"与"30 条样本里 24 条同一序列被报出"。
|
|
1452
|
+
- **两张密码子表不许悄悄打架**:`codon.mjs` 新增完整的 61 密码子频率表,而 `protein.mjs` 里的
|
|
1453
|
+
`CODON_USAGE` 是**优化偏好**排序,两者本意不同(60 个氨基酸×宿主里有 18 个首选不一致)。
|
|
1454
|
+
测试因此断言三件事而不是"全等":频率表 61 个密码子齐全、家族和为 1、优化表的首选**不是**
|
|
1455
|
+
罕见密码子(w 不低于家族最大值的一半)。
|
|
1456
|
+
- **环形/扇形布局的半径是线性映射**:真实距离常在 0.01 量级,按 `depth/max` 归一化会把整棵树
|
|
1457
|
+
塌缩成一个点(第一版就是这样,人眼复核时发现);现在半径在观测区间上线性铺满面板,比例尺
|
|
1458
|
+
负责交代真实单位。**扇形只画拓扑**并写明"长度不成比例"——等角布局本来就不编码距离。
|
|
1459
|
+
- **诚实的 out-of-scope 清单进 README**:BAM/CRAM、比对、组装、BLAST、ML/贝叶斯树、参考库类分析
|
|
1460
|
+
逐条写明 blocker,并指明每类在本工具集里的本地替代品。
|
|
1461
|
+
|
|
1462
|
+
### 修复:DSH 0.1.6-alpha.2 漂移(两项,都会让现有安装出问题)
|
|
1463
|
+
|
|
1464
|
+
1. **preset 少了一行上游新增的 `tool-plugin-manager`**(`disabled: true`)。DSH 0.1.6-alpha.2 的
|
|
1465
|
+
shipped `standard` 预设新增该行,而本组合的"逐行结构比对"守卫**正确地拦下了发布**。
|
|
1466
|
+
已按上游逐行补齐(含注释位置),`preset-health` 恢复 `OK`:30 行全部可挂载。
|
|
1467
|
+
2. **`test/contract.mjs` 绑定了 minifier 的空格**:断言写死 `ctx.slots.provideRoot({ hooks: {`
|
|
1468
|
+
这一**单空格拼写**,而 alpha.2 把同一调用排成四行缩进。契约没变(同一个 check 里紧跟的
|
|
1469
|
+
空白容忍正则一直是过的),所以这是**断言绑格式**而非产品故障。已改为空白容忍,并新增一条
|
|
1470
|
+
**不空转自检**:四种拼写(单行/多行/紧凑/多空格)都必须通过,而把 `hooks` 改名成 `hookz`
|
|
1471
|
+
必须失败——否则"放宽成不绑格式"会悄悄退化成"什么都不检查"。
|
|
1472
|
+
|
|
1473
|
+
### 顺带发现并上报(不是本版修复范围)
|
|
1474
|
+
|
|
1475
|
+
- **渐进比对会丢掉无法安放的末端残基**(`msa.mjs` 既有行为,v15 起就在):11 bp 的序列与
|
|
1476
|
+
10 bp 的序列比对后返回 10 列,那条 11 bp 的序列被静默截断。v19 **不去改比对器**,但新建树的
|
|
1477
|
+
工具会**逐个序列核对"非缺口字符数 == 输入长度"**,不一致就在 `notes` 里 WARNING 点名
|
|
1478
|
+
(`u2 (10 of 11 bases kept)`)。系统发生学从被截断的数据出发是最坏的一类静默错误。
|
|
1479
|
+
比对器本身的修复记在 [docs/maintainer.md](docs/maintainer.md) 路线图里。
|
|
1480
|
+
|
|
1481
|
+
### 测试与验证方式
|
|
1482
|
+
|
|
1483
|
+
- **`test/svgio.mjs`(新套件,16 项)**:共享助手的几何手算值(`linearScale` 反向区间、
|
|
1484
|
+
`niceTicks` 的取整网格与负区间、`indexScale` 往返、panel 内缩、textRun 的可选属性、
|
|
1485
|
+
axis 的 x/y 两种布局、图例与配色函数),最后用**每一个助手**拼一份文档光栅化,断言
|
|
1486
|
+
`unsupported` 与 `missing_glyphs` 都为空。
|
|
1487
|
+
- **`test/svgpng.mjs` 新增第 14 项「v19 图把墨迹画在数据所在的位置」**:不只断言"能渲染",
|
|
1488
|
+
还按**坐标矩形采样**断言内容位置——FASTQ 报告三列面板各自有内容、树图主体与叶标签分列左右、
|
|
1489
|
+
扇形布局**必须画满**(塌缩成一个点会失败,这条正是半径 bug 的回归守卫)、凝胶的梯子与两条
|
|
1490
|
+
样品泳道各有条带、组成图的 CpG 岛阴影确实覆盖左半。八份真实产物(4 张 v19 + 原有)都进
|
|
1491
|
+
"真实渲染器产物必须落在支持子集内"的清单。
|
|
1492
|
+
- **`test/smoke.mjs` 新增五个工具的手算断言块**(工具数 52 → 57,注册数由日志与
|
|
1493
|
+
`attach_image` 计数 11 → 15 共同看守):FASTQ 的 8 条读夹具逐位置均值/四分位/Q20/Q30/重复率/
|
|
1494
|
+
接头/过度代表;密码子表的完整性与 CAI/RSCU/Nc/GC3/CpG 已知值(含"家族大小从 4 变 3 会让
|
|
1495
|
+
CAI 从 1.0 变成 0.4444"这条真 bug 的回归);距离矩阵逐格手算 + 饱和夹取 + 四 taxa 的**四点条件**;
|
|
1496
|
+
PCR 的产物坐标/序列逐字符、错配与 3' 规则、环状跨 origin 序列、错引导双带;组成分析的岛边界/
|
|
1497
|
+
skew 手算值(G 富集/C 富集等长段的 ±0.5)/"高 GC 但无 CpG 不算岛"。
|
|
1498
|
+
- **`test/preset-health.mjs` 新增镜像检查**:组合指向的 `vN` 目录必须与包根**逐字节一致**
|
|
1499
|
+
(多一个少一个模块都报错)。已用突变实验证明它会失败——"升级了却什么都没变"正是模块缓存规则
|
|
1500
|
+
要防的那种静默失败,而任何挂载检查都看不见它(旧模块照样挂得上)。
|
|
1501
|
+
- **`npm test` 从 9 个套件变为 10 个**(新增 `svgio`)。
|
|
1502
|
+
|
|
1503
|
+
### 发版要点
|
|
1504
|
+
|
|
1505
|
+
- **工具 52 → 57**;`attach_image` 覆盖的画图工具 11 → 15。
|
|
1506
|
+
- **必须新建 preset 目录**:`preset/molbio-lab/plugins/dsh-molbio-tools-v19/`(29 个模块:v18 的 23 个
|
|
1507
|
+
里 `index.mjs`/`lib.mjs`/`protein.mjs` 有改动,**新增 `svgio.mjs`** 与 5 个分析模块),
|
|
1508
|
+
`tool-molbio` 行已指向 v19;`node test/preset-health.mjs` 报 `OK` 且镜像检查通过。
|
|
1509
|
+
- **客户端产物已同批重建**(`lib.mjs`/`protein.mjs` 在浏览器半里也用,改了就必须重建):
|
|
1510
|
+
`lib/client.js` 与 `packages/molbio-panel/lib/client.js`。**`svgio.mjs` 与 5 个新模块都不进客户端**。
|
|
1511
|
+
- `package.json`:`files` 白名单**必须**含 6 个新模块(漏了就是"装完却没这个功能"的静默失败),
|
|
1512
|
+
版本 0.11.0,新增 `test:svgio` 脚本并接入 `test:unit`。
|
|
1513
|
+
- route-B 用户重启 profile 即可;复制渠道用户请重拷 `agent.cordis.yml` 与新的 v19 目录。
|
|
1514
|
+
- 升级说明一句话:**"多了五个实验台分析工具(测序质量、密码子优化、系统发生树、in-silico PCR、
|
|
1515
|
+
CpG/GC 组成),画图工具都能把图交给模型看;另外修好了在 DSH 0.1.6-alpha.2 上 preset 少一行、
|
|
1516
|
+
contract 测试绑格式这两个问题"**。
|
|
1517
|
+
|
|
1518
|
+
## [0.10.0] — 2026-09-17(v18:让模型"看见"自己产出的图;工具仍 52)
|
|
1519
|
+
|
|
1520
|
+
**这一版解决的是"图给谁看"。** 之前 11 个画图工具只写 SVG:人看得见(还会自动打开),
|
|
1521
|
+
**模型看不见**——它只能读路径和数值。harness 自带模型可见的 `read_image`(PNG/JPEG/WebP/GIF),
|
|
1522
|
+
但不收 SVG,所以"让模型自查凝胶条带/图谱"一直差这一步。
|
|
1523
|
+
|
|
1524
|
+
### 关键设计决定:为什么不是 `png_path`(**原计划不可实现**)
|
|
1525
|
+
|
|
1526
|
+
原方案是加 `png_path` 参数、在工作区写一个 PNG 文件。**在 DSH 0.1.6-alpha.1 上这条路走不通**,
|
|
1527
|
+
三层证据(都来自本机安装的 harness):
|
|
1528
|
+
|
|
1529
|
+
1. `dsh-fs/README.md`:**"Text-only mutations by contract** — … binary-safe mutations remain
|
|
1530
|
+
deferred";
|
|
1531
|
+
2. `dsh-fs-local/lib/index.js`:`writeText → writeFileAtomic` 把调用方的**字符串按 UTF-8 落盘**
|
|
1532
|
+
(想用 latin-1 夹带字节会被 UTF-8 编码器替换而损坏),文本读取还会
|
|
1533
|
+
`subarray(0, 8192).includes(0)` 拒收 NUL——**连读回来都做不到**;
|
|
1534
|
+
3. 全树检索**没有任何 `writeBytes`**,`FsErrorCode` 里只有 `FS_NOT_TEXT`。
|
|
1535
|
+
|
|
1536
|
+
绕开它只能直接 `node:fs` 或用 `ctx.get('subprocess')` 起进程写盘,两者都逃出"所有写入经 `ctx.fs`
|
|
1537
|
+
并携带会话 `sandboxPolicy`"这条纪律,**明确拒绝**。于是改用 harness 提供的二进制安全通路:
|
|
1538
|
+
`ctx.attachments.saveImage()` + 工具结果里的 **image content block**——这正是它自己 `read_image`
|
|
1539
|
+
的实现方式(`dsh-llm` 的 `ImageBlock = { type: 'image', attachment: ImageAttachmentRef }`)。
|
|
1540
|
+
`test/contract.mjs` 现在把这条链路的每一环都钉住,并**专门断言 fs 缝仍然没有二进制写入**:
|
|
1541
|
+
哪天上游补上了,这条断言会失败,提醒维护者回头补当年的 `png_path`。
|
|
1542
|
+
|
|
1543
|
+
### 新增
|
|
1544
|
+
|
|
1545
|
+
- **`svgpng.mjs` — 零依赖 SVG→PNG 光栅化器**(宿主侧专用,`client` 产物不含它)。只覆盖本包
|
|
1546
|
+
会生成的 SVG 子集,**不是通用渲染器**:`rect`(含 `rx`、`width="100%"`)、`line`、`circle`、
|
|
1547
|
+
`polygon`、`polyline`、`path`(M/L/H/V/C/S/Q/T/A/Z,绝对与相对)、`text`(font-size/anchor/
|
|
1548
|
+
dominant-baseline/textLength/lengthAdjust/`rotate`)、hex 颜色、`fill-opacity`、`stroke-width`、
|
|
1549
|
+
`stroke-dasharray`、`stroke-linecap`,2× 超采样后盒式降采样。PNG 编码自写
|
|
1550
|
+
(IHDR/IDAT/IEND + 自算 CRC32,deflate 用内置 `node:zlib`)。**文字用内置折线字体**
|
|
1551
|
+
(6×7 网格、矢量描边、随字号缩放,无字体文件):覆盖可打印 ASCII 与 `· ° ± — – … ≈ μ α ─`,
|
|
1552
|
+
**非 ASCII(例如中文标题)不会被画出来**,且会记进 `missing_glyphs` 而不是画成乱码。
|
|
1553
|
+
**不支持的构造一律计数上报**(未知元素、`<g>` 的 transform、`url(#gradient)` 这类画法),
|
|
1554
|
+
绝不静默丢弃——`test/svgpng.mjs` 断言四个真实渲染器的八份产物 `unsupported` 与
|
|
1555
|
+
`missing_glyphs` **都必须为空**,因此以后新增 SVG 构造会在这里失败,而不是从图里消失。
|
|
1556
|
+
- **11 个画图工具的可选 `attach_image: true`**:`plasmid_map` / `plasmid_map_file` /
|
|
1557
|
+
`clone_simulate` / `golden_gate` / `grna_design` / `qpcr_efficiency` / `plot` /
|
|
1558
|
+
`virtual_gel` / `sequence_logo` / `helical_wheel` / `hydropathy_plot`。打开后工具把同一张图
|
|
1559
|
+
光栅化并提交为附件,结果里多一个 `image` 对象(`attachment_id`/`media_type`/`bytes`/
|
|
1560
|
+
`width`/`height`),`output.render` 在文本旁多返回一个 **image block**,模型**当场看到图**,
|
|
1561
|
+
不需要再走一次 `read_image`。**默认关**:不传参的调用**一个字节都不提交、结果形状完全不变**。
|
|
1562
|
+
报告工具(序列分析、引物设计等 41 个)**一个都没动**。
|
|
1563
|
+
- 三条实现纪律与随之而来的测试:
|
|
1564
|
+
- **能力门照抄 harness 的规则**:`exec.agent.session.requestHeader().config` → provider/model →
|
|
1565
|
+
`ctx.get('llm').resolveModelInfo()` → 检查 `inputModalities.includes('image')`,与
|
|
1566
|
+
`read_image` 的 `assertImageCapableRoute` 同源(`contract.mjs` 盯着这条规则本身);
|
|
1567
|
+
- **永不失败**:无附件服务 / 文本路由 / 路由解析不出 / 渲染不了 / 存储拒收,全部降级为纯文本,
|
|
1568
|
+
并在 `image_note` 里**点名原因**;SVG 照写、`auto_view` 照旧、调用照成功;
|
|
1569
|
+
- **`smoke.mjs` 覆盖 9 条**:不传参数时零提交且仍是单 text block;传了以后提交的确实是 PNG
|
|
1570
|
+
(签名 + IHDR 尺寸与凝胶画布手算值一致);结果字段与第二个 block 的 `attachment` 逐字段一致;
|
|
1571
|
+
11 个工具逐个断言参数与输出字段、且总数恰好 11;四条降级路径各自不改成功性;附加图片时
|
|
1572
|
+
`render` 仍产出文本。
|
|
1573
|
+
|
|
1574
|
+
### 修复
|
|
1575
|
+
|
|
1576
|
+
- **线性质粒图谱一直被裁掉 3' 端**(真 bug,被这次的"图"暴露):`renderPlasmidMap` 无论拓扑都写
|
|
1577
|
+
`viewBox="0 0 840 840"`,而 `renderLinear` 画布是 960×260、内容一直画到 x≈900——**浏览器里同样
|
|
1578
|
+
裁掉**(最后一个 ruler 标注与骨架末端)。现在按拓扑选画布(线性 960×260 / 环形 840×840),
|
|
1579
|
+
并删掉 `renderLinear` 里重复的白底矩形。`test/svgpng.mjs` 新增回归:线性图的栅格必须是 960×260
|
|
1580
|
+
且 **x≥880 必须有墨迹**。客户端产物已同批重建(`plasmid.mjs` 在浏览器半里也用)。
|
|
1581
|
+
|
|
1582
|
+
### 测试与验证方式(这一版是第一次能"看着自己的产物"验证)
|
|
1583
|
+
|
|
1584
|
+
- **`test/svgpng.mjs`(新套件,17 项)**:用**与编码器不同实现**的 CRC(无表位运算)与裸
|
|
1585
|
+
`inflate` 把字节解回像素,再做手算几何断言——rect 的四个边界像素、圆心/半径外、描边居中与
|
|
1586
|
+
dash 空档、`fill-opacity` 混合到中灰、`fill="none"` 不填充、`text-anchor` 三个锚点的墨迹框、
|
|
1587
|
+
cap height ≈0.7 em、`dominant-baseline` 居中、`textLength` 压到指定宽度、`rotate(-90)` 把基线
|
|
1588
|
+
转到旋转点左侧;外加确定性(同输入两次字节相同)、错误路径(非 SVG / 无尺寸 / 非零 viewBox
|
|
1589
|
+
原点 / 超像素上限 / CJK 缺字上报 / `encodePng` 参数校验)、以及"光栅化器不得进入客户端产物"。
|
|
1590
|
+
还带两个开发用出口:`--sheet <png>` 导出整张字形表、`--preview <dir>` 导出每种图各一张
|
|
1591
|
+
——**改字形后必须这样人眼复核一遍**。
|
|
1592
|
+
- **`test/smoke.mjs` 的 v18 段**(见上)与 **`test/contract.mjs` 的两个新 check**(图片交接链路
|
|
1593
|
+
的每一环;以及"fs 缝仍然是纯文本"这条反向守卫)。`npm test` 从 8 个套件变为 9 个。
|
|
1594
|
+
- **人眼复核**:八份真实产物(环形/线性质粒图谱、凝胶、logo、螺旋轮、疏水性图、柱状、散点)
|
|
1595
|
+
与字形表都经 harness 自己的图像解码器(`read_image`)读回**逐张看过**;过程中修掉两处:
|
|
1596
|
+
`@` 字形画成了 `a`(螺旋轮副标题里的 `max 0.15 @ 2` 就是证据),以及上面那个线性图谱裁剪。
|
|
1597
|
+
|
|
1598
|
+
### 发版要点
|
|
1599
|
+
|
|
1600
|
+
- **工具仍 52 个**,README 的工具表不变;新增的是可选参数与一个模块。
|
|
1601
|
+
- **必须新建 preset 目录**(插件 `.mjs` 有改动):`preset/molbio-lab/plugins/dsh-molbio-tools-v18/`
|
|
1602
|
+
(v17 的 22 个模块里 `index.mjs`/`plasmid.mjs` 更新,**新增 `svgpng.mjs`**,其余逐字节相同),
|
|
1603
|
+
`tool-molbio` 行已指向 v18;`node test/preset-health.mjs` 报 `OK`。
|
|
1604
|
+
- `package.json`:`files` 白名单**必须**含 `svgpng.mjs`(漏了就是"装完却没有这个功能"的静默失败),
|
|
1605
|
+
新增 `test:svgpng` 脚本并接入 `test:unit`。
|
|
1606
|
+
- route-B 用户重启 profile 即可;复制渠道用户请重拷 `agent.cordis.yml` 与新的 v18 目录。
|
|
1607
|
+
- 升级说明一句话:**"画图工具现在可以把图直接交给模型看(调用时加 `attach_image: true`);
|
|
1608
|
+
工具数量与用法不变,另修掉了线性质粒图谱右端被裁掉的问题"**。
|
|
1609
|
+
|
|
1610
|
+
## [0.9.1] — 2026-09-17(DSH 0.1.6-alpha.1 漂移修复:**preset 曾无法挂载**;工具仍为 52)
|
|
1611
|
+
|
|
1612
|
+
**背景**:本仓库在 DSH `0.1.5-alpha.2` 上发布,随后机器上的 DSH 升到 `0.1.6-alpha.1`。
|
|
1613
|
+
升级后 `npm test` 挂了 **2 项**,其中一项不是测试问题,而是**用户可见的故障**:
|
|
1614
|
+
`Molecular Biology Lab` 预设**根本挂不上**。包版本与 preset 版本目录**均不变**(无插件 `.mjs` 改动,
|
|
1615
|
+
工具仍 52 个)——但修复必须发出,因为现有安装会照抄包内这份组合文件。
|
|
1616
|
+
|
|
1617
|
+
### 修复(真实故障;`test/preset-health.mjs` 抓到,但当时只打印"drift note"并退出 0)
|
|
1618
|
+
|
|
1619
|
+
- **preset 引用了不存在的 provider 包**:组合里的 `workflow-worker-thread` 行指向
|
|
1620
|
+
`@deepseek-ai/dsh-workflow-worker-thread`,而**没有任何已发布的 DSH 安装这个包**
|
|
1621
|
+
(本机 `0.1.6-alpha.1` 只有 `@deepseek-ai/dsh-workflow-ptc`)。该行 `unresolvable`,
|
|
1622
|
+
预设 mount 失败,选择器里点开就是加载错误。已改为上游 `standard` 的
|
|
1623
|
+
`workflow-ptc`(`config: { provider: spawn }`,与本组合原意一致)。
|
|
1624
|
+
- **静默的行为偏差**:`tool-ralph` 在包内组合里是**启用**的,而上游 `standard` 明确
|
|
1625
|
+
`disabled: true`(工具描述把 `ralph` 限制在"人明确要求"的运行,且完成判定是 worker
|
|
1626
|
+
自报而非独立评估)。已按上游改回 `disabled: true`。想用的话按上游注释复制一份 preset 并去掉
|
|
1627
|
+
`disabled`,不要在这里偷偷打开。
|
|
1628
|
+
- **组合文件与上游逐行对齐**:吸收 `0.1.6-alpha.1` 的上游文本(`persona` 的 `suffix`/`prefix`
|
|
1629
|
+
顺序、`delegation` 注释、fork 注释、`present` 说明),并删掉上游已移除的
|
|
1630
|
+
`tool-subagent-report` 段落。现在 `git diff --no-index` 对比安装的 `standard` 只剩**一个 hunk**:
|
|
1631
|
+
末尾的 `tool-molbio` 行(加上文首新增的"维护契约"注释)。这本身就是漂移审计手段。
|
|
1632
|
+
|
|
1633
|
+
### 加固(这一版真正的"清障"成果)
|
|
1634
|
+
|
|
1635
|
+
- **`test/preset-health.mjs` 的漂移检查从"只比 id"升级为"比行"**:逐行比对**行序**、
|
|
1636
|
+
`name`、`disabled`、`isolate`、`config`,并把**漂移从 note 提升为 FAILURE**
|
|
1637
|
+
(v17 的 `worker-thread` 就是被"note + 退出 0"放过去的)。`tool-molbio` 是唯一允许的额外行,
|
|
1638
|
+
允许清单在文件顶部显式声明。
|
|
1639
|
+
- **`test/drift-probe.mjs`(新增)**:没人见过失败的守卫不算守卫。用**变异组合**驱动
|
|
1640
|
+
`compositionDrift`——幻影 provider 行、被丢掉的 `disabled`、改名、改 config、改 isolate、
|
|
1641
|
+
丢行、多余行、行序错乱——断言每一种都被抓到,且对**当前这份组合零噪音**。
|
|
1642
|
+
`preset-health.mjs` 因此改为 `if (import.meta.main) await main()` 并导出该函数(Node ≥ 22,
|
|
1643
|
+
`package.json` 已加 `engines`)。
|
|
1644
|
+
- **`test/contract.mjs` 的 hook-prop 规则断言不再绑定 minify 形态**:`0.1.6-alpha.1` 把
|
|
1645
|
+
`standardHookPropName` 从 `function $c(t){…}` 编成了**类方法** `$c(t){…}`,原来的
|
|
1646
|
+
"整个函数体"正则因此失败(面板本身没问题)。现在断言的是**契约**:稳定导出名
|
|
1647
|
+
`standardHookPropName: <symbol>` + 该符号仍实现 `use${首字母大写}${其余}` 这条规则
|
|
1648
|
+
(允许 `slice`/`substring`、可选括号、箭头/方法/函数三种写法)。
|
|
1649
|
+
- **产物新鲜度检查不再依赖 `spawnSync`**:受限沙箱拒绝管道 stdio(EPERM),原检查只能
|
|
1650
|
+
"无法验证"——恰好是自动化发版处最需要它的时候。打包器的生成逻辑已抽到
|
|
1651
|
+
**`build/client-bundle-core.mjs`**(`createGenerator({ entry, baseDir }) → { order, ids, renderBundle }`),
|
|
1652
|
+
`build/client-bundle.mjs` 变成薄 CLI(新增 `--check`:只报告陈旧、不落盘),
|
|
1653
|
+
`test/contract.mjs` **在进程内**重算期望产物并与已提交文件逐字节比较。
|
|
1654
|
+
字节级等价已实测:重构后重跑打包器,`git status lib packages` 为空。
|
|
1655
|
+
|
|
1656
|
+
### 分发注意
|
|
1657
|
+
|
|
1658
|
+
- **route-B(已注册 preset 根,推荐渠道)用户**:组合文件是每次挂载重新读取的,
|
|
1659
|
+
升级包后**重启 profile 即可**,不需要重新 `add`。
|
|
1660
|
+
- **复制渠道用户**(把 `preset/molbio-lab/` 拷到 `~/.dsh/.agent-presets/`):请重新复制这份
|
|
1661
|
+
`agent.cordis.yml`(`plugins/` 目录若还在 v16/v17 可不动;组合文件的模块缓存规则不适用)。
|
|
1662
|
+
- 本机 `~/.dsh/.agent-presets/molbio-lab/` 还留着一份 **v16 的老副本**,同样带
|
|
1663
|
+
`workflow-worker-thread`;route-B 生效时它不参与,但若曾用复制渠道请按上面重拷。
|
|
1664
|
+
|
|
1665
|
+
### 发版记录
|
|
1666
|
+
|
|
1667
|
+
- **工具与 preset 目录不动**:无插件 `.mjs` 改动,**没有新建 `v18` 目录**,`tool-molbio`
|
|
1668
|
+
行仍指向 `dsh-molbio-tools-v17`;`package.json` 的 `version` **0.9.0 → 0.9.1**(修复版),
|
|
1669
|
+
tag `v0.9.1`。
|
|
1670
|
+
- 发版前按 `docs/maintainer.md` 的三步预检:`npm test`(8 套件全绿)→
|
|
1671
|
+
`node build/client-bundle.mjs --check`(两个产物均报 up to date)→ `git status --short` 干净。
|
|
1672
|
+
- 升级说明一句话:**"修复在 DSH 0.1.6-alpha.1 上 Molecular Biology Lab 预设无法挂载的问题
|
|
1673
|
+
(工具数量与用法不变)"**。
|
|
1674
|
+
|
|
1675
|
+
### 仅文档:v18 路线图补入 DSH 0.1.6 新能力的可用性勘察
|
|
1676
|
+
|
|
1677
|
+
**不改代码、不改 preset 目录、不改包版本**,只更新 `docs/maintainer.md` 的路线图,把
|
|
1678
|
+
0.1.6-alpha.1 的"面板内终端 / computer use"逐包核对结果写成 v18 的输入(含证据、门槛与
|
|
1679
|
+
"不要再重复勘察"的否定清单):
|
|
1680
|
+
|
|
1681
|
+
- **面板内终端:已经在用,零改动。** 浏览器终端(`ctx.terminalController` + xterm.js 标签页)随
|
|
1682
|
+
`dsh-web-app` 出厂即启用,与本包右栏面板**同座位共存**(靠 tab `id`/`kind`/guide order 区分,
|
|
1683
|
+
`id` 重复会抛异常)。它与 agent 侧的 `ctx.terminals` 是**两套互不相通的实现**,且面板
|
|
1684
|
+
**不把终端输出送给模型**。
|
|
1685
|
+
- **v18 候选 1(推荐)**:给绘图工具增加**可选 PNG 输出**,让模型能自己调用 `dsh-tool-fs` 的
|
|
1686
|
+
`read_image` 看图谱/凝胶/logo。现状是"只写 SVG、`read_image` 不收 SVG",所以差这一步;
|
|
1687
|
+
不需要视觉模型、不需要 computer use。
|
|
1688
|
+
- **v18 候选 2**:用 `ctx.documentPreviews.register({ id, extensions, loading: 'bytes-complete' })`
|
|
1689
|
+
+ `sidebar.right.tab.document` 座位把 `.pdb/.cif/.sdf/.mol` 放进本包自己的标签页。**范围要说清**:
|
|
1690
|
+
这是容器(座位/注册表/字节加载),不是现成 3D 查看器。
|
|
1691
|
+
- **v18 候选 3(需先验证)**:持久 shell 进 preset。要加 `dsh-terminal` + `dsh-terminal-bash` +
|
|
1692
|
+
`dsh-tool-pwsh-persistent`;`sandbox`/`subprocess` 已在 `dsh-base`。两个硬约束:**工具名冲突**
|
|
1693
|
+
(`pwsh`/`bash` 与一次性工具重名,必须禁用一次性行,并同步 `preset-health` 的允许清单与组合头部)
|
|
1694
|
+
与**唯一待验证点**——从 preset 发布 `ctx.terminals` 需要 `isolate: { terminals: true }` 分组,
|
|
1695
|
+
shipped preset 有同类先例但没有 terminal 的先例。
|
|
1696
|
+
- **明确不在本版**:computer use 的 agent 侧能力(无截图/鼠标/键盘工具、无 OCR、无辅助功能树;
|
|
1697
|
+
全树 `screenshot` 只出现一次且是否定句);agent 驱动浏览器终端(无 `dsh-tool-terminal`)。
|
|
1698
|
+
同批勘察的 MCP(`dsh-mcp-client` 未被任何 shipped bundle 挂载;stdio server **不受文件沙箱约束**)
|
|
1699
|
+
与 hooks(兼容适配器,不是插件扩展点)作为备选记录在案,不进路线图主线。
|
|
1700
|
+
|
|
1701
|
+
### 仅文档:新增 `docs/capability-gap-survey.md`(v18 的选型依据)
|
|
1702
|
+
|
|
1703
|
+
不改代码、不改 preset 目录、不改工具数量的第二份规划文档:把"52 个已发布工具相对主流生信
|
|
1704
|
+
生态缺什么"逐项过筛——九个来源家族(商业质粒/克隆套件、实验台网页计算器、Biopython/EMBOSS/
|
|
1705
|
+
SeqKit、标准序列统计、微生物组、群体遗传、蛋白预测、比对后处理、测序 QC),每条候选都要同时
|
|
1706
|
+
通过「(a) 对实验台真的有用于 (b) 能用几百行无依赖、无外部数据/二进制/网络/参考库的确定性算法
|
|
1707
|
+
实现」两道筛。产出:**40 条排序候选**(含 ext?/纯 JS?/规模/价值四列)、**必做 top-5**、
|
|
1708
|
+
**"想做但不可行"清单(每条点名确切阻断原因:二进制格式 / 参考数据库 / 模型权重 / 网络 /
|
|
1709
|
+
算力规模)**,以及全部依赖的 URL 出处(并标注了哪些论断因 403/无正文而只算部分核实)。
|
|
1710
|
+
它的价值不只是候选池:§3 的否定清单可以直接搬进 README 的"不做什么",让用户不再要求本工具
|
|
1711
|
+
做 BLAST/BAM/ML 树。
|
|
1712
|
+
|
|
1713
|
+
## [0.9.0] — 2026-09-13(v17:TaqMan 探针、多重 PCR、甲基化/双酶切、蛋白结构图;工具 46 → 52)
|
|
1714
|
+
|
|
1715
|
+
**路线图 v17 方向的第一批:五个新工具 + 一个新的 preset 版本目录**。全部为 v11–v13 引擎
|
|
1716
|
+
之上的确定性纯计算,零依赖不变。
|
|
1717
|
+
|
|
1718
|
+
### 新增
|
|
1719
|
+
|
|
1720
|
+
- **`taqman.mjs` + `molbio_design_taqman`**:TaqMan(水解探针)测定设计。先用标准引物
|
|
1721
|
+
引擎设计 qPCR 尺寸的扩增子(**默认 70–200 bp**,可用 `primer_options` 覆盖),再在
|
|
1722
|
+
扩增子内的缺口里放探针并施加探针规则:**5' 端不得为 G**(淬灭)、探针 Tm 至少高出较热
|
|
1723
|
+
引物 `min_tm_delta`(默认 5 °C)、无单碱基重复/串联重复、自互补有界、且与两条引物都
|
|
1724
|
+
不形成稳定二聚体。每条候选给出探针序列/坐标/方向/Tm 边距/引物 3' 端距离与**逐项公开的
|
|
1725
|
+
排序罚分**。几何采用「扩增子两个缺口」模型:`orientation: "forward"` 从正向引物 3' 端
|
|
1726
|
+
向外读(标准设计),`"reverse"` 从反向引物 3' 端向外读并报反向互补——一个缺口放不下探针
|
|
1727
|
+
时自动回退到另一个,并在 `notes` 里说明。`primer_options` 的 snake_case 键经显式映射
|
|
1728
|
+
转成引擎的 camelCase(**0.9.0 开发中抓到并修掉的真 bug:不映射时用户传的窗口会被静默
|
|
1729
|
+
忽略而落到默认值**)。
|
|
1730
|
+
- **`multiplex.mjs` + `molbio_multiplex_check`**:多重 PCR 互扰检查。报告(1)面板内**所有**
|
|
1731
|
+
引物对的 any/3'-anchored 二聚体 Tm,超过阈值(默认 47 °C)的标为 conflict;(2)每条引物
|
|
1732
|
+
3' 尾的**非预期退火**——自身模板上的脱靶位点(允许配置错配)与**其他模板上的完全匹配**
|
|
1733
|
+
(多重交叉反应;**模板序列相同的 target 视为同一个模板**,否则每条引物都会"交叉"到自己的
|
|
1734
|
+
扩增子);(3)扩增子大小**是否可在胶上分辨**(<20 bp 判为 indistinguishable、<40 bp 判为
|
|
1735
|
+
close),以及可执行的重设计建议。
|
|
1736
|
+
- **`protein-structure.mjs` + `molbio_helical_wheel` / `molbio_hydropathy_plot`**:
|
|
1737
|
+
- 螺旋轮(Schiffer-Edmundson 投影):残基按 3.6 残基/圈(100°/残基)落在圆周上,按
|
|
1738
|
+
疏水/极性/酸性/碱性着色并生成 SVG;同时给出整段与**滑动窗口(默认 11 残基,Eisenberg
|
|
1739
|
+
标准)**的最大疏水矩 μH(Eisenberg 共识标度)、疏水残基比例与提示。字形用绝对字号 +
|
|
1740
|
+
`textLength` 定位,逐字形断言不超残基圆(沿用餐 0.6.0 序列标识图的教训)。
|
|
1741
|
+
- Kyte-Doolittle 疏水性图:滑动窗口(默认 9;跨膜段建议 19–21)、GRAVY、**达到阈值
|
|
1742
|
+
(默认 1.6,经典跨膜判据)的峰**及逐残基 profile,写成 SVG 并自动打开。
|
|
1743
|
+
- **`methylation.mjs` + `molbio_methylation_check` / `molbio_double_digest`** 与
|
|
1744
|
+
`lib.mjs` 的两张参考表(`METHYLATION_SENSITIVITY`、`ENZYME_BUFFERS`/`BUFFERS`):
|
|
1745
|
+
- **甲基化检查**:找出序列中每个 Dam(GATC)与 Dcm(CCWGG,双链向)位点,报告哪些酶的
|
|
1746
|
+
识别位点与之重叠,并把每个酶分类为 `cuts` / `impaired` / `blocked` / `no_site`——这正是
|
|
1747
|
+
"酶没问题、但用 dam⁺/dcm⁺ 宿主提的质粒切不动"的经典场景。默认检查对象是甲基化表覆盖且
|
|
1748
|
+
**消化酶表里确实存在**的 21 个酶(表里的 AvaII/MboI/EcoRII/PspGI/TaqI/HphI/BstNI 不在
|
|
1749
|
+
消化表中,报 0 位点是误导),也可传 `["common"]` 查全表。
|
|
1750
|
+
- **双酶切**:两个酶各自的切点/片段、合并切点与合并片段(线性/环状),以及**两者是否共用
|
|
1751
|
+
buffer**(标准 NEB 系列;无共用 buffer 时明确给出"顺序酶切/换用厂商双酶切 buffer"的建议),
|
|
1752
|
+
并标出某酶在本模板上无位点、或两酶切在同一磷酸二酯键的情形。
|
|
1753
|
+
- **两张表都是手工转录的速查数据**,每个结果都随附 `METHYLATION_DATA_NOTE` /
|
|
1754
|
+
`BUFFER_DATA_NOTE`,要求对照厂商当前表格复核后才可作为实验依据。
|
|
1755
|
+
- **客户端半**:`build/browser-api.mjs` 把 v17 的纯计算面(甲基化/buffer 表、探针与多重
|
|
1756
|
+
分析、蛋白图渲染器)一并再导出,**面板本身在 v17 未改动**;产物重建(184.9 KB →
|
|
1757
|
+
332.9 KB),`npm run build:client` 与所有客户端测试已同步。
|
|
1758
|
+
|
|
1759
|
+
### 修复(实现过程中发现并修掉的真实缺陷)
|
|
1760
|
+
|
|
1761
|
+
- **探针几何(三次纠正,最终为"缺口模型")**:初版把引物名字当成了模板坐标顺序,导致
|
|
1762
|
+
探针窗口在多数扩增子上为空;第二版把"正向引物 3' 端 + 上游引物 3' 端"混用,产出**与引物
|
|
1763
|
+
重叠的探针**;第三版才落到"扩增子物理缺口 + 从开缺口的引物向外读"这一条规则,并由
|
|
1764
|
+
smoke 对**每一条返回的测定**断言:探针等于模板切片(或反向互补)、不与任一引物重叠、
|
|
1765
|
+
距离正是从开缺口引物 3' 端量起、5' 端非 G、无 run、Tm/GC 在窗口内。
|
|
1766
|
+
- **`primer_options` 全表静默失效**:见上(snake_case → camelCase 映射)。
|
|
1767
|
+
- **几何之外的静默数据问题**:`amplicon.length` 未给坐标时序列化为 `undefined`(非 lossless
|
|
1768
|
+
JSON,被 harness 的输出校验拒绝)——改为字段整体缺省;`methylation` 的 `in_methylation_table`
|
|
1769
|
+
字段未进 schema——补齐。
|
|
1770
|
+
- **`test/contract.mjs` 的新鲜度断言在受限沙箱下误导**:`spawnSync` 被沙箱拒绝(EPERM)时
|
|
1771
|
+
原来打印"the bundler runs cleanly",容易误读成打包器崩了;现在把 spawn 失败与打包器失败
|
|
1772
|
+
分开报告,并给出人工核对命令。
|
|
1773
|
+
|
|
1774
|
+
### preset 与分发
|
|
1775
|
+
|
|
1776
|
+
- **新建 `preset/molbio-lab/plugins/dsh-molbio-tools-v17/`**(版本目录规则:插件 `.mjs` 有
|
|
1777
|
+
改动,必须新建目录),`agent.cordis.yml` 的 `tool-molbio` 行指向 v17,`preset.yml` 描述
|
|
1778
|
+
同步到 52 个工具。
|
|
1779
|
+
- **`package.json` 版本 0.8.0 → 0.9.0**;`files` 白名单覆盖新增 `.mjs`(`files` 用的是包根
|
|
1780
|
+
相对路径,`.mjs` 逐个列出——`taqman.mjs` / `multiplex.mjs` / `protein-structure.mjs` /
|
|
1781
|
+
`methylation.mjs` 已加入)。
|
|
1782
|
+
- **测试脚本跨平台化**:原来的 `"test": "node a && node b && …"` 依赖 npm 的 shell,在
|
|
1783
|
+
Windows 的 cmd/PowerShell 下 `npm test` 直接失败(`&&` 不是 node 的语法)。改成
|
|
1784
|
+
`test:unit` / `test:client:node` 子脚本 + `node --run` 串联,`npm test` 现在在
|
|
1785
|
+
cmd/PowerShell/git-bash 下行为一致;`docs/maintainer.md` 的预检说明同步。
|
|
1786
|
+
|
|
1787
|
+
### 测试
|
|
1788
|
+
|
|
1789
|
+
`test/smoke.mjs` 新增 v17 段(工具 46 → **52** 个注册,逐工具输出 schema 校验):
|
|
1790
|
+
|
|
1791
|
+
- **TaqMan**:pUC118[1200,1800) 上 7 条测定;对每条断言几何与探针规则的不变式;钉住排第一
|
|
1792
|
+
的测定(序列/坐标/Tm 61.71 °C/边距 0.81/Tm 边距距离 6)与一条"缺口在反向引物一侧"的测定
|
|
1793
|
+
(距离 67 从反向引物 3' 端量起);探针 Tm 与 `lib.primerTm` 同源;5 条 Tm 边距不足的
|
|
1794
|
+
amplicon 逐条上报;四条选项错误路径(含嵌套 `primer_options`)。
|
|
1795
|
+
- **多重 PCR**:4 个真实引物对的固定面板——24 条引物对交互、3 条跨 target 二聚体(69.1 /
|
|
1796
|
+
66.03 / 61.23 °C)、大小冲突 164 vs 168 bp(indistinguishable)与 103 vs 83 bp(close);
|
|
1797
|
+
相同模板不交叉、不同模板共享 3' 尾(完全匹配仅 8 bp 尾)判为交叉反应;无坐标不产生大小
|
|
1798
|
+
冲突;四条错误路径。
|
|
1799
|
+
- **蛋白图**:14 残基两亲性肽的手算值(μH 0.353、窗口最大 0.584 @3、疏水 10/极性 1/碱性 3、
|
|
1800
|
+
首残基 90°/单位圆坐标);69 残基蛋白的疏水图手算值(GRAVY −0.13、峰 4-10/23-27/52-59、
|
|
1801
|
+
首窗口 = 残基 1-5 均值、窗口 21 平滑后 2 峰);SVG 逐字形/逐顶点断言;四条错误路径。
|
|
1802
|
+
- **甲基化/双酶切**:手工夹具(3 个 ClaI 位点被 Dam **blocked**、BamHI **impaired**、EcoRI/
|
|
1803
|
+
HindIII/XbaI 可用、Dcm 位点不与选择中的酶重叠);pUC118 全质粒(dam 15 / dcm 5、8 个可用
|
|
1804
|
+
酶);EcoRI+HindIII 环状双酶切(切点 927/876 → 3111+51 bp,CutSmart 等 4 个共用 buffer);
|
|
1805
|
+
无共用 buffer 的 BstXI+SmaI;无位点与共享切点两条建议;错误路径。
|
|
1806
|
+
|
|
1807
|
+
## [0.8.0] — 2026-09-12(安装渠道 B:把 preset 注册进 profile,升级不再需要复制)
|
|
1808
|
+
|
|
1809
|
+
**新增 `preset/install.mjs`**:把**已安装包内**的 preset 目录注册为 profile 的额外 preset
|
|
1810
|
+
扫描根,补上"`dsh plugin add` 装了工具、却不出现在预设选择器里"这个缺口。配置改一次,
|
|
1811
|
+
之后每个版本只跑 `dsh plugin --profile <p> update dsh-molbio-tools`,**不需要再复制、也不
|
|
1812
|
+
需要再改配置**。
|
|
1813
|
+
|
|
1814
|
+
- 机制:在 profile 的 `cordis.patch.yml` 追加一条 `- id: agent-presets` 补丁,其 `config.roots`
|
|
1815
|
+
增加 `{ path: <已安装包>/preset, trust: system }`。`config` 是整体替换,因此重述 `default: standard`。
|
|
1816
|
+
- 为什么值得做:复制渠道把 preset 冻结在复制那一刻,每次发布(版本目录规则要求新建
|
|
1817
|
+
`dsh-molbio-tools-vN`)都要重新复制;注册渠道的 root 路径**跨版本恒定**(`link:` 是符号链
|
|
1818
|
+
接、npm/tarball 是真实目录,实测从 `link:` 换成 tarball 后路径不变),升级因此只动包、不动配置。
|
|
1819
|
+
- 脚本性质:零依赖、幂等(已注册即零写入)、写入前备份 `cordis.patch.yml`、写完用
|
|
1820
|
+
`dsh --profile <p> --dump-config` 自检;对没有 roster 行的 profile(headless/sdk)**先检查
|
|
1821
|
+
bundle 列表再明确拒绝**,而不是写一个必然被 loader 拒绝的补丁。支持 `--dry-run` / `--check`。
|
|
1822
|
+
- 实测覆盖:组合树解析与 root 出现、幂等、`--check`、`--dry-run` 不落盘、无 roster 行被守卫、
|
|
1823
|
+
发现层对已安装副本判定 `healthy` 且四个内置 preset 未受影响、升级(换安装来源)后路径不变;
|
|
1824
|
+
补丁形状另用 harness 自己的 YAML 解析器跑 5 种边界(裸 `[]` 占位符、无尾换行、CRLF、已有条目)。
|
|
1825
|
+
- 两个由实测抓到的坑已写进文档:新 profile 的 patch 文件是**裸 `[]`**,追加会产生第二个顶层
|
|
1826
|
+
节点(`end of the stream or a document separator is expected`),必须替换占位符;`roots.path`
|
|
1827
|
+
是 `path.resolve` 解析的,**相对路径随进程 CWD 漂移**,必须写绝对路径。
|
|
1828
|
+
- **工具数量不变(46 个);preset 版本目录不变**——本次只新增包内 `preset/install.mjs` 与文档,
|
|
1829
|
+
未改任何随 preset 分发的 `.mjs`,按版本目录规则无需新建 `v17`。
|
|
1830
|
+
- 新增 [docs/route-b.md](docs/route-b.md):用法、机制、升级 Runbook(含"别提前删旧 `vN` 目录"
|
|
1831
|
+
等三条纪律)、与复制渠道的取舍表、实测方法与已知限制。
|
|
1832
|
+
|
|
1833
|
+
## [0.7.2] — 2026-09-11(修复:座位未声明就注册 → DSH 拒绝启动)
|
|
1834
|
+
|
|
1835
|
+
**修复 HARNESS "Failed to load plugins"**(`failed to apply loader entry …(dsh-molbio-panel):
|
|
1836
|
+
slot "tool.call.toolview" is not declared (a parent entry's children table must declare it)`)。
|
|
1837
|
+
|
|
1838
|
+
客户端座位只有在**拥有它的那条 entry 在自己的 `children` 表里声明之后**才存在:`sidebar.right.pane.tab`
|
|
1839
|
+
由右栏的 `rightbar.session` entry 声明,而 `tool.call.toolview` 是 ui-tool 的
|
|
1840
|
+
`conversation.chat.node` entry 的**子座位**。0.7.1 的卡片用裸 `ctx.slots.register()` 抢这个座位,
|
|
1841
|
+
启动图里没有任何东西保证我们的 entry 排在 ui-tool 之后——`register()` 于是抛上述 SlotCore 异常,
|
|
1842
|
+
异常从本包的 `apply()` 逃出即是**加载器 entry 失败**,也就是整个 Web GUI 拒绝启动(不是"少一个
|
|
1843
|
+
tab")。四个右栏座位当时只是碰巧安全(本包 inject 了右栏提供的服务),同样的竞态依然存在。
|
|
1844
|
+
|
|
1845
|
+
- 修法:**所有**座位声明改走 `ctx.slots.inject(seat, cb)`(ui-tool / ui-skill / ui-sidebar-right /
|
|
1846
|
+
ui-sidebar-documentpreview 都是这个写法):座位已声明则立即执行,未声明则等声明到达,
|
|
1847
|
+
重声明(epoch)时先撤销旧贡献再重放,贡献随本 fiber 销毁。
|
|
1848
|
+
- 新增 `test/slots-stub.mjs`:按 shell 的 SlotCore 语义复刻"未声明座位 `register()` 必抛
|
|
1849
|
+
`… is not declared (a parent entry's children table must declare it)`"与 `inject` 的等待/重放规则。
|
|
1850
|
+
- `test/client.mjs` 改为从**一个座位都没声明**的最坏启动顺序开始:断言 `apply()` 不抛、四个右栏
|
|
1851
|
+
座位在声明后落地、调用卡座位一直等到 ui-tool 声明才落地,并当场复刻那条异常消息本身。
|
|
1852
|
+
- `test/contract.mjs` 增第 10 项:钉住 shell 仍拒绝未声明座位的注册、slots 服务仍提供
|
|
1853
|
+
`inject(key, callback)`、官方包仍用它抢这两个座位,且**本包产物里每一处 `slots.register`
|
|
1854
|
+
都必须落在对应座位的 `slots.inject` 里**(数量与配对都断言)。
|
|
1855
|
+
|
|
1856
|
+
工具数量不变(46 个);preset 版本目录不变(本次只动客户端产物与测试,未改随 preset 分发的 `.mjs`)。
|
|
1857
|
+
|
|
1858
|
+
## [0.7.1] — 2026-09-10(图谱调用卡:`tool.call.toolview`)
|
|
1859
|
+
|
|
1860
|
+
**新增图谱调用卡**:`molbio_plasmid_map` 与 `molbio_plasmid_map_file` 的调用**直接在对话里
|
|
1861
|
+
画出这次产出的质粒图谱**(摘要行 + 写入路径),不再只给一个文件路径;调用失败时显示错误
|
|
1862
|
+
文本与 Inspect 入口。
|
|
1863
|
+
|
|
1864
|
+
- 宿主侧:两个 map 工具新增 `output.presentationMeta(args, value)` 投影——这是官方文档
|
|
1865
|
+
写明的结构化数据通道(`presentResult`/`presentCall` 内置 Web 客户端不消费),且只有
|
|
1866
|
+
ROOT 调用会执行。SVG 随 meta 传输设 256 KB 上限:超限时改带 `svg_omitted` 与字节数,
|
|
1867
|
+
卡片降级为提示 + 路径(图谱仍写在文件里)。投影是纯计算(图谱标记走一份有界内存缓存,
|
|
1868
|
+
不读文件)且不抛——它跑在调用成功之后,抛错会把成功调用标成失败。
|
|
1869
|
+
- 客户端侧:新增 `tool.call.toolview` 卡片(按工具名取键)。它**校验而非信任** meta:
|
|
1870
|
+
缺失/异种/异形/超限/失败一律降级为提示,绝不在对话里抛错;SVG 用 DOM 注入,保持可选中。
|
|
1871
|
+
- `define()` 包装器转发 `presentationMeta`(此前只转发 schema/render,投影会被静默丢弃——
|
|
1872
|
+
由新测试当场抓到)。
|
|
1873
|
+
- 测试:新增 `test/map-card.mjs`(真实工具 → 投影 → 卡片读取 → 渲染出 SVG,含四条降级
|
|
1874
|
+
路径);`test/contract.mjs` 增第 9 项,钉住"工具层仍调用 `output.presentationMeta` 且只对
|
|
1875
|
+
root 调用"与"卡片座位仍按工具名取键"。
|
|
1876
|
+
|
|
1877
|
+
工具数量不变(46 个)。
|
|
1878
|
+
|
|
1879
|
+
## [0.7.0] — 2026-09-10(浏览器内面板:bundle 渠道 dsh.client 双面包)
|
|
1880
|
+
|
|
1881
|
+
**新增两个右栏面板**:**Molbio**(列出会话工作区的序列文件,选中即画图:`.dna`/`.gb`/
|
|
1882
|
+
`.gbk` → 质粒图谱 + 特征表,`.fa`/`.fasta` → 序列标识图)与 **Papers**(把
|
|
1883
|
+
`molbio_paper_*` 维护的 `papers.json` 渲染成可搜索的阅读列表 + 详情面板)。解析与渲染在
|
|
1884
|
+
浏览器里跑的是本仓库自己的模块,与 Node 工具**同一份源码**——不落盘 SVG、不弹系统查看器、
|
|
1885
|
+
不经过工具调用。
|
|
1886
|
+
|
|
1887
|
+
- `build/client-bundle.mjs`:零依赖打包器,产出 DSH 客户端加载器要求的 **lazy-CJS**
|
|
1888
|
+
产物(`window.__ModuleLoader__.load({id, factory})`)。官方 `tsdown.client.ts` 预设未随
|
|
1889
|
+
npm 发布,故按加载器契约复刻;同时把纯净度门禁前移成构建期检查(禁 `node:` 内建、
|
|
1890
|
+
禁裸 specifier、禁动态 `import()`)。**一趟构建产出两个交付包**。
|
|
1891
|
+
- `build/browser-api.mjs` / `panel-core.mjs` / `client-entry.mjs`:浏览器安全面、面板数据
|
|
1892
|
+
通路(无 React、可在 Node 单测)、以及两个右栏 tab 的注册与组件。
|
|
1893
|
+
- `packages/molbio-panel`:**面板专用包**(宿主半边空实现)——装它只加面板,不会把 46 个
|
|
1894
|
+
工具注入该 profile 的每个会话;`dsh-molbio-tools` 则是"工具 + 面板"通道。
|
|
1895
|
+
- `package.json`:`exports["./client"]` + `dsh.client {platform: web, inject}` +
|
|
1896
|
+
两个客户端包的 optional peer;`build`/`lib`/`packages` 进入发布白名单;新增
|
|
1897
|
+
`build:client` 脚本。**工具侧无变化(仍 46 个)**,预设渠道与 bundle 渠道可共存。
|
|
1898
|
+
- 测试(6 个套件):
|
|
1899
|
+
- `test/client.mjs`:按加载器方式执行产物(注册形状、**注册期零全局写入**)+ 两条数据
|
|
1900
|
+
通路(真实 pUC118 夹具、文献库投影/搜索/错误路径);
|
|
1901
|
+
- `test/panel-render.mjs`:**真实组件**在最小钩子宿主里跑完整状态机(无 React、无 DOM),
|
|
1902
|
+
断言列表/图谱/特征表/logo/搜索/标签/空库/坏库/卸载中止;
|
|
1903
|
+
- `test/client-mount.mjs`:复刻宿主侧图扫描,验证两个包能挂上、依赖可解析;
|
|
1904
|
+
- `test/contract.mjs`:把面板与宿主的**运行时契约**钉在已安装的 DSH 上(钩子 prop 命名
|
|
1905
|
+
规则、root hook 提供方、`workspaceFiles` 的方法与 wire 形状、本包自己的注册与实参顺序、
|
|
1906
|
+
以及发布白名单是否真的带上客户端产物)。
|
|
1907
|
+
- 文档:新增 `docs/client-panel.md`(实现记录:产物格式、三条硬约束、服务契约、上限、
|
|
1908
|
+
验证边界与未验证项)。
|
|
1909
|
+
|
|
1910
|
+
> 说明:本版本的面板已在本地 web profile 安装并通过全部静态/契约验证,**页面内的实际观感
|
|
1911
|
+
> 需重启一次 `dsh web` 后在右栏确认**(新插件行在启动时组合)。
|
|
1912
|
+
|
|
1913
|
+
## [0.6.0] — 2026-09-10(preset 目录 v16)
|
|
1914
|
+
|
|
1915
|
+
**序列标识图与 CRISPR gRNA 设计(路线图 v16 方向)。** 工具总数 44 → 46。
|
|
1916
|
+
|
|
1917
|
+
### 新增
|
|
1918
|
+
|
|
1919
|
+
- **`logo.mjs` + `molbio_sequence_logo`**:把保守性分析背后的逐列碱基组成画成 SVG
|
|
1920
|
+
序列标识图。字母高度 = 信息量 Rᵢ = log₂4 − (Hᵢ + e_n),纵轴 bits(0-2);堆叠高度
|
|
1921
|
+
按频率分配,经典配色。频率只按残基统计(缺口排除并逐列上报),简并碱基按碱基集合
|
|
1922
|
+
摊分权重。`small_sample`(默认开)扣小样本熵校正 e_n = (K−1)/(2·ln2·n);
|
|
1923
|
+
`score_type: "frequency"` 切成纯频率图。输入 `alignment` / `sequences` /
|
|
1924
|
+
`fasta_path`,写 SVG 后自动打开。
|
|
1925
|
+
- **`crispr.mjs` + `molbio_grna_design`**:SpCas9 约定的 PAM 锚定 gRNA 设计。双链扫描
|
|
1926
|
+
20 nt protospacer,逐条报告 1-based 顶链坐标、链向、GC%、NN Tm、Primer3 式
|
|
1927
|
+
self-any/self-end、种子自互补、发夹 Tm、poly-T,以及逐项公开的启发式评分
|
|
1928
|
+
(GC/Tm/poly-T/同聚/自互补/种子/发夹/**每个脱靶 8 分**/PAM 前一位 G +4)。
|
|
1929
|
+
硬过滤(GC 上下限、poly-T、3' 自互补、G/C 同聚)可调且报告被过滤条数;
|
|
1930
|
+
`check_off_target`(默认开)复用 v12 mispriming 的 k-mer 索引思路做**错配容差脱靶
|
|
1931
|
+
搜索**(仅替换、PAM 必须完好、种子末端 2 位不错配),默认只搜得分最高的 200 条候选。
|
|
1932
|
+
可选 `save_path`(订购 CSV)与 `map_path`(带 gRNA 标记的质粒图谱)。
|
|
1933
|
+
|
|
1934
|
+
### 修复(实现过程中发现并修掉的真实缺陷)
|
|
1935
|
+
|
|
1936
|
+
- **反向链 protospacer 长度错误**:初版把 `PAM + guideLength` 整段反向互补,产出的是
|
|
1937
|
+
23 nt 而不是 20 nt 的"guide"。已改为 PAM 下游的 20 nt,并在 smoke 里加了
|
|
1938
|
+
`sequence.length === 20` 与「顶链切片反向互补 = guide」两条不变式。
|
|
1939
|
+
- **logo 字形溢出列宽**:初版用 `font-size = 高度/0.72`,2 bits 的列会算出 200+ px 的
|
|
1940
|
+
字号、横向压到邻列。改为「字号同时受列宽与图高约束 + `textLength` 压缩宽字形」,
|
|
1941
|
+
smoke 里逐字形断言 `font-size`/`textLength` 不超列宽。
|
|
1942
|
+
- **脱靶计数把自己的靶点算成脱靶**:同一位置可能有多条变体拼写命中(guide 是其自身
|
|
1943
|
+
反向互补时更多),已改为按「位置 + 链向」在截断前排除并计数。
|
|
1944
|
+
|
|
1945
|
+
### 其它
|
|
1946
|
+
|
|
1947
|
+
- `logo.mjs` / `crispr.mjs` 进入 `files` 白名单;preset 组合的 `tool-molbio` 行与
|
|
1948
|
+
`preset.yml` 描述同步到 v16 / 46 个工具。
|
|
1949
|
+
|
|
1950
|
+
## [0.5.1] — 2026-09-10
|
|
1951
|
+
|
|
1952
|
+
**发布修复:preset 在 DSH 0.1.5-alpha.2 上无法挂载;新增 preset 健康检查。**
|
|
1953
|
+
|
|
1954
|
+
插件代码(`.mjs`)与 v0.5.0 完全一致,仍为 v15 版本目录——本次只改 preset 组合与文档,
|
|
1955
|
+
因此**不需要新版本目录**(`plugins/dsh-molbio-tools-v15/` 逐字节未变)。
|
|
1956
|
+
|
|
1957
|
+
### 修复
|
|
1958
|
+
|
|
1959
|
+
- **`persona` 行在 DSH 0.1.5-alpha.2 上硬挂载失败**:组合里写的是
|
|
1960
|
+
`config: { text: ... }`,而当前 `@deepseek-ai/dsh-persona` 的 `Config` 要求
|
|
1961
|
+
`prefix`(`suffix` 可选)——校验报 `$.prefix missing required value`,整个
|
|
1962
|
+
Molecular Biology Lab 预设无法挂载,会话无法选择该模式。已按官方 `standard`
|
|
1963
|
+
预设改回 `prefix` + `suffix` 两段。
|
|
1964
|
+
- **补回 `present` 行**:`@deepseek-ai/dsh-tool-present` 是官方非 `minimal` 预设都带的
|
|
1965
|
+
工具(显式声明工作区交付物)。此前与 `standard` 同步时漏掉了该行,导致 molbio 会话
|
|
1966
|
+
缺少 `present`——而本插件的主要产物(图谱/凝胶/曲线 SVG)正是要靠它声明交付。
|
|
1967
|
+
它只消费宿主侧 `fs`/`session`/`tools`,不发布服务,无需 realm。
|
|
1968
|
+
|
|
1969
|
+
### 新增
|
|
1970
|
+
|
|
1971
|
+
- **`test/preset-health.mjs`**:把 preset 组合里的**每一行** config 交给该包自己的
|
|
1972
|
+
`Config` schema 校验(与 Loader 挂载时同一套判定,但不启动 harness),并检查
|
|
1973
|
+
「行指向的模块是否存在」与「相对官方 `standard` 预设的行差异」。上面那个
|
|
1974
|
+
persona 故障正是它能捕获、而 `test/smoke.mjs` 结构上不可能捕获的一类问题——冒烟测试
|
|
1975
|
+
证明插件可用,证明不了组合可挂载。
|
|
1976
|
+
用法:`node test/preset-health.mjs [composition.yml] [--dsh <harness 根目录>]`。
|
|
1977
|
+
- `package.json` 增加 `test` / `test:smoke` / `test:preset` 脚本;`files` 白名单纳入
|
|
1978
|
+
`preset/`、`test/`、`docs/`,npm 渠道也能拿到可直接复制的 preset 目录。
|
|
1979
|
+
|
|
1980
|
+
### 文档
|
|
1981
|
+
|
|
1982
|
+
- README:修正「全部 39 个工具」的过时表述(v10 时代遗留,现为 44),补充 preset 安装
|
|
1983
|
+
说明与健康检查入口。
|
|
1984
|
+
- `docs/maintainer.md`:发布流程加入 preset 健康检查步骤;重新核对 DSH 0.1.5-alpha.2。
|
|
1985
|
+
|
|
1986
|
+
## [0.5.0] — 2026-08-22(preset 目录 v15)
|
|
1987
|
+
|
|
1988
|
+
**多序列比对与保守性分析。**
|
|
1989
|
+
|
|
1990
|
+
- 新增 `msa.mjs`:渐进式多序列比对——两两/谱-谱全局比对用**仿射缺口罚分**
|
|
1991
|
+
(match +4 / mismatch −4 / 缺口开 −6 / 延伸 −2)且**末端缺口免费**(半全局),
|
|
1992
|
+
合并顺序由 **5-mer 距离 + UPGMA** 引导树决定,谱-谱打分用和-对(sum-of-pairs);
|
|
1993
|
+
合并时新缺口按"一旦有缺口、永远有缺口"整列延伸。输入 2-50 条、单条 ≤ 3000 bp、
|
|
1994
|
+
总长 ≤ 30000 bp;输出顺序与输入一致。
|
|
1995
|
+
- 新增 `molbio_msa_align`:返回比对后序列与两两同一性统计,可 `save_path` 写出比对 FASTA。
|
|
1996
|
+
- 新增 `molbio_conservation`:共识序列(最高频碱基 ≥50%,否则给出 IUPAC 简并码)、
|
|
1997
|
+
逐列 identity、熵基保守性(1 − H/2)、保守/可变列统计(`threshold` 默认 0.8);
|
|
1998
|
+
输入可以是已比对序列(`alignment`)、原始序列或 FASTA,后两者自动先比对。
|
|
1999
|
+
- 工具总数 42 → 44。
|
|
2000
|
+
|
|
2001
|
+
## [0.4.0] — 2026-08-20(preset 目录 v14)
|
|
2002
|
+
|
|
2003
|
+
**正确性修复 + 图片自动查看。**
|
|
2004
|
+
|
|
2005
|
+
- 修正线粒体密码子表中 `AGA`/`AGG` 的终止语义。
|
|
2006
|
+
- Sanger 验证:氨基酸后果改为**密码子局部**判定,支持框内缺失。
|
|
2007
|
+
- IIS 酶**反向位点**的顶链切点位置修正(Golden Gate 的 zPrime 连接因此正确)。
|
|
2008
|
+
- 新增 `view.mjs`:所有生成图片的工具(质粒图谱、克隆/Golden Gate 的 `map_path`、
|
|
2009
|
+
标准曲线 `plot_path`、通用绘图、虚拟凝胶)写完 SVG 后用**操作系统默认应用自动打开**
|
|
2010
|
+
(Windows `Invoke-Item`、macOS `open`、桌面 Linux `xdg-open`/`$BROWSER`、WSL 转译路径);
|
|
2011
|
+
逐调用 `auto_view: false` 可关,headless 与 `MOLBIO_AUTO_VIEW=0` 自动跳过,
|
|
2012
|
+
结果里 `auto_viewed` 回显交接结果。
|
|
2013
|
+
|
|
2014
|
+
## [0.3.0] — 2026-08-19(preset 目录 v13)
|
|
2015
|
+
|
|
2016
|
+
**反应条件、Golden Gate、酶目录、虚拟凝胶。**
|
|
2017
|
+
|
|
2018
|
+
- 两个引物设计工具新增盐/浓度旋钮 `na_mm`/`mg_mm`/`dntp_mm`/`primer_nm`
|
|
2019
|
+
(默认 50/1.5/0.8/200),驱动 Tm 的 von Ahsen 2001 盐校正与发夹/二聚体折叠浓度,
|
|
2020
|
+
输出 `conditions` 回显实际条件。
|
|
2021
|
+
- 两个引物设计工具新增 3' 目标位置偏好 `target_position` + `target_penalty`
|
|
2022
|
+
(SNP 分型/定点设计),报告 `target_distance`。
|
|
2023
|
+
- 新增 `molbio_enzyme_lookup`:90+ 酶目录查询;给定序列时报告**双链向**切点
|
|
2024
|
+
(IIS 酶的反向识别位点,`molbio_restriction_sites` 不报告)。
|
|
2025
|
+
- 新增 `molbio_golden_gate`:IIS 酶多片段组装——自动设计唯一/非回文/非互补的 4 bp
|
|
2026
|
+
突出端,检查酶不切片段,生成可下单的 `fragments_to_order`,拼出最终质粒并平移特征;
|
|
2027
|
+
载体盒子位点保留在骨架(标准 destination 行为,下一级换酶)。
|
|
2028
|
+
- 新增 `molbio_virtual_gel`:log₁₀ 迁移率模型的虚拟琼脂糖凝胶图 + 分子量 ladder。
|
|
2029
|
+
- 新增 `molbio_enzyme_lookup` / `molbio_golden_gate` / `molbio_virtual_gel`,
|
|
2030
|
+
工具总数 39 → 42。
|
|
2031
|
+
|
|
2032
|
+
## [0.2.0] — 2026-08-18(preset 目录 v12)
|
|
2033
|
+
|
|
2034
|
+
**引物设计的 Primer3 对齐与错配容差。**
|
|
2035
|
+
|
|
2036
|
+
- 结构筛查改为 Primer3 同款热力学模型:自互补(self-any/self-end)比对分
|
|
2037
|
+
(match +1 / mismatch −1 / gap −0.25,阈值 8.0/3.0)、发夹与引物二聚体用同一套
|
|
2038
|
+
SantaLucia NN 参数折成 Tm(默认阈值 47 °C)、3' 端稳定性(末 5 碱基 ΔG(37 °C))
|
|
2039
|
+
与末 5 碱基 GC 数、GC clamp 0-3 连续分级。
|
|
2040
|
+
- 引物设计支持**错配容差**:`max_mismatches > 0` 时可用最少的替换挽救无解窗口
|
|
2041
|
+
(3' 末端碱基永不错配,默认保护 3' 端 5 bp 关键区),每处错配逐条报告并计入排序罚分;
|
|
2042
|
+
精确引物永远优先。跨内含子设计在 spliced/genomic 双坐标下报告错配。
|
|
2043
|
+
- 新增非特异结合检查 `check_mispriming`:3' 尾在模板双链上的额外退火位点。
|
|
2044
|
+
|
|
2045
|
+
## [0.1.0] — 2026-08-16(preset 目录 v10/v11)
|
|
2046
|
+
|
|
2047
|
+
**首个公开发布:39 个 molbio 工具 + Molecular Biology Lab 预设渠道。**
|
|
2048
|
+
|
|
2049
|
+
- 序列分析、引物设计与检查(含跨内含子 qPCR)、酶切模拟、SnapGene `.dna` /
|
|
2050
|
+
GenBank 解析与 SVG 质粒图谱、克隆模拟(酶切-连接 / Gibson)、Sanger 验证、
|
|
2051
|
+
蛋白工具、qPCR 与绘图、文献库与协议/实验记录。
|
|
2052
|
+
- 新增 **Molecular Biology Lab 专属模式 preset**:工具只在选择该模式的会话中出现,
|
|
2053
|
+
不污染其它场景;同时保留官方 bundle(`dsh plugin add`)渠道。
|
|
2054
|
+
- 确立**版本目录规则**(每次更新新建 `dsh-molbio-tools-vN/`),因为 DSH 的 standing
|
|
2055
|
+
挂载按 ESM 文件 URL 缓存模块,原地改文件不会生效。
|