dsh-molbio-tools 0.19.0 → 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.
Files changed (49) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +65 -21
  3. package/align.mjs +28 -0
  4. package/batch.mjs +185 -0
  5. package/benchmark/README.md +27 -8
  6. package/benchmark/sequences.mjs +7 -0
  7. package/benchmark/tasks/02-primers.json +132 -19
  8. package/benchmark/tasks/03-restriction.json +113 -29
  9. package/benchmark/tasks/04-cloning.json +167 -34
  10. package/benchmark/tasks/05-plasmids.json +172 -29
  11. package/benchmark/tasks/06-analysis.json +387 -100
  12. package/benchmark/tasks/09-formats.json +132 -23
  13. package/benchmark/tools.mjs +28 -2
  14. package/benchmark/verifications.mjs +17 -1
  15. package/build/release-preflight.mjs +2 -2
  16. package/crispr.mjs +392 -78
  17. package/docs/history.md +10 -0
  18. package/docs/maintainer.md +8 -6
  19. package/docs/roadmap.md +22 -8
  20. package/docs/rules.md +1 -1
  21. package/docs/workflow.md +7 -7
  22. package/fastq-qc.mjs +735 -735
  23. package/index.mjs +573 -36
  24. package/lib/client.js +665 -86
  25. package/multiplex.mjs +87 -0
  26. package/package.json +9 -4
  27. package/packages/molbio-panel/lib/client.js +665 -86
  28. package/packages/molbio-panel/package.json +1 -1
  29. package/preset/molbio-lab/agent.cordis.yml +4 -4
  30. package/preset/molbio-lab/preset.patch.yml +2 -2
  31. package/preset/molbio-lab/preset.yml +1 -1
  32. package/silent-site.mjs +286 -0
  33. package/snapgene.mjs +17 -1
  34. package/taqman.mjs +163 -7
  35. package/test/contract.mjs +14 -0
  36. package/test/release.mjs +23 -3
  37. package/test/smoke.mjs +377 -3
  38. package/benchmark/reports/20260923-142935-suite.json +0 -545
  39. package/benchmark/reports/20260923-142935-suite.md +0 -30
  40. package/benchmark/reports/20260923-190149-suite.json +0 -2024
  41. package/benchmark/reports/20260923-190149-suite.md +0 -235
  42. package/benchmark/reports/20260923-192913-suite.json +0 -1856
  43. package/benchmark/reports/20260923-192913-suite.md +0 -158
  44. package/benchmark/reports/20260923-200212-suite.json +0 -3587
  45. package/benchmark/reports/20260923-200212-suite.md +0 -163
  46. package/benchmark/reports/20260923-202830-suite.json +0 -3129
  47. package/benchmark/reports/20260923-202830-suite.md +0 -99
  48. package/benchmark/reports/20260923-203856-suite.json +0 -71
  49. package/benchmark/reports/20260923-203856-suite.md +0 -16
@@ -76,11 +76,11 @@ Measured, and printed so the answer can be audited:
76
76
 
77
77
  ```
78
78
  $ touch crispr.mjs && node benchmark/run.mjs --list --changed
79
- 1 of 56 task(s) selected — --tools molbio_grna_design --changed
79
+ 1 of 61 task(s) selected — --tools molbio_grna_design --changed
80
80
  (from --changed; 2 module(s) affected: crispr.mjs, index.mjs)
81
81
 
82
82
  $ touch lib.mjs && node benchmark/run.mjs --list --changed
83
- 55 of 56 task(s) selected — --changed (27 module(s) affected: align.mjs, cloning.mjs, …)
83
+ 55 of 61 task(s) selected — --changed (27 module(s) affected: align.mjs, cloning.mjs, …)
84
84
  ```
85
85
 
86
86
  `lib.mjs` selecting nearly everything is the **correct** answer, not a failure to
@@ -102,7 +102,7 @@ you changed, so its task runs even if it is full-only.
102
102
  | `--tools a,b,c` / `primer_*` | the union | you changed a few |
103
103
  | `--changed --tools X` | the INTERSECTION | "of what I changed, test X" |
104
104
  | (default) | **core** — 14 tasks | a broad smoke of the catalog |
105
- | `--tier full` | all 56, 58/58 tools | before a release |
105
+ | `--tier full` | all 61, 60/60 tools | before a release |
106
106
 
107
107
  The two `--tools` operators are stated because confusing them is the difference
108
108
  between testing what you changed and testing **less** than that: alone it unions,
@@ -125,7 +125,7 @@ widens to the full suite, with the reason printed.
125
125
  | a module **only** imported by other modules (`svgio.mjs`) | partial — its importers' tasks |
126
126
  | docs, changelog, `test/`, `benchmark/`, config | **`none`: nothing to run** |
127
127
 
128
- `none` is deliberately NOT `full`. A documentation commit that triggers 56 model
128
+ `none` is deliberately NOT `full`. A documentation commit that triggers 61 model
129
129
  calls is how a `--changed` default gets switched off and the full suite goes back
130
130
  to running every time — the exact behaviour this option exists to prevent. It is
131
131
  only reachable with an explicit `--changed`, so everything else still fails safe,
@@ -134,7 +134,7 @@ and `test/benchmark-changed.mjs` asserts both directions.
134
134
  ### The part that still runs in full, on purpose
135
135
 
136
136
  A model run with `--changed` or `--tools` first executes the **whole** offline
137
- gate: all 56 tasks' `where: "tool"` expectations are recomputed against the shipped
137
+ gate: all 61 tasks' `where: "tool"` expectations are recomputed against the shipped
138
138
  tools (no model, a few hundred milliseconds). That is what makes partial runs safe
139
139
  — the expected values are re-proven for the tasks you skipped, so "skipped because
140
140
  unchanged" is a checked claim, not a remembered one.
@@ -168,7 +168,7 @@ asserting whatever the tool happened to emit.
168
168
 
169
169
  Two guards make "someone forgot" fail loudly instead of quietly:
170
170
 
171
- - `test/benchmark-coverage.mjs` asserts **58/58 registered tools are covered by
171
+ - `test/benchmark-coverage.mjs` asserts **60/60 registered tools are covered by
172
172
  at least one task**. Adding a tool without a task is a red build. It also
173
173
  asserts the fixtures' premises (the Golden Gate backbone is BsaI-free, the
174
174
  mutagenesis template still admits a primer pair), that a `covers` entry names a
@@ -209,7 +209,7 @@ recorded tool text, and it says so instead of silently scoring empty text).
209
209
 
210
210
  ## The headless profile (`benchmark/profile.mjs`)
211
211
 
212
- The model-facing way to get the 58 tools is the **Molecular Biology Lab** agent
212
+ The model-facing way to get the 60 tools is the **Molecular Biology Lab** agent
213
213
  preset. The benchmark cannot use it, and the reason is structural rather than
214
214
  configurational — `dsh-headless/lib/index.js` refuses an adoption whose session
215
215
  records a preset:
@@ -368,6 +368,25 @@ These are recorded rather than scored, because each one is a question about the
368
368
  plugin or the harness, not about the model. The first is a **measured plugin
369
369
  defect**.
370
370
 
371
+ ### `molbio_extract_region` advertised `sequence`, but the resolver only read `vector`
372
+
373
+ The 0.20.0 full run (61 tasks) failed `region-extract` on **arguments**: the model
374
+ called `molbio_extract_region` with the parameter the schema advertises
375
+ (`sequence`), and the tool answered "provide either vector (a sequence) or
376
+ vector_path". The schema/reality split was the defect: `resolveVector` read only
377
+ `args.vector`, so `sequence` was silently ignored. **Fixed in 0.20.0** by
378
+ accepting every declared inline alias (`vector`/`sequence`/`reference`), with a
379
+ smoke regression that calls the tool through the advertised `sequence` argument.
380
+
381
+ The same run also showed four **assertion** failures rather than product defects:
382
+ `plasmid-digest` (the model wrote `2,840`, the assertion `2840`),
383
+ `protein-props` (`3,935` vs `3935`), `codon-optimize` (the model said
384
+ `EcoRI-free`, the assertion demanded `no EcoRI`), and `chart-volcano` (the task
385
+ asked for `de.svg`, the assertion pinned `volcano.svg`). `--replay` turns all four
386
+ to PASS; the region-extract live rerun was blocked by the DSH sandbox approval
387
+ for headless subprocesses, so the last measured score is **60/61**, with the
388
+ underlying bug fixed and pinned by smoke.
389
+
371
390
  ### `molbio_design_primers` prints the forward/reverse roles reversed
372
391
 
373
392
  **Confirmed by direct inspection, not by reading a model's claim.**
@@ -548,7 +567,7 @@ implementation happens to return for one set of arguments.**
548
567
  and `args_ok` both true is a *reporting* finding, not a tool bug.
549
568
  - **Multimodal hand-off.** `attach_image` is exercised as an argument at most; no
550
569
  assertion inspects a decoded PNG (that is `svgpng.mjs` and `contract.mjs`).
551
- - **Most of the toolset is now covered, but shallowly.** 56 tasks touch all 58
570
+ - **Most of the toolset is now covered, but shallowly.** 61 tasks touch all 60
552
571
  tools; most tools appear in exactly one task, and several appearances are a
553
572
  single argument check rather than a full workflow. Breadth is asserted by
554
573
  `test/benchmark-coverage.mjs`; depth is not, and cannot be.
@@ -165,6 +165,13 @@ export const NAMED_SEQUENCES = {
165
165
  cds: FIXTURES.CDS,
166
166
  mutagenesis_template: FIXTURES.MUTAGENESIS_TEMPLATE,
167
167
  sanger_read: FIXTURES.SANGER_READ,
168
+ // v0.20.0: the family-geometry fixtures. The Cas12a target is a 5' TTTV PAM
169
+ // followed by a 23 nt protospacer; the Cas13a target is a 28 nt spacer with a
170
+ // 3' H PFS. Keeping them here means the instruction and the offline grader
171
+ // cannot diverge.
172
+ cas12a_target: 'AAAATTTAACGTACGTACGTACGTACGTACGCCCCTTTCGGGGGGGGGGG',
173
+ cas13_target: 'AAAAACGTACGTACGTACGTACGTACGTACGTACCCCCCCCCC',
174
+ silent_cds: 'ATGGCTGAATTCGGTTAAGCTTAA',
168
175
  /**
169
176
  * The three FASTQ reads the `fastq-qc` task pastes in.
170
177
  *
@@ -12,26 +12,54 @@
12
12
  "id": "primer-qc",
13
13
  "category": "primers",
14
14
  "tier": "full",
15
- "covers": ["molbio_primer_check", "molbio_primer_tm"],
15
+ "covers": [
16
+ "molbio_primer_check",
17
+ "molbio_primer_tm"
18
+ ],
16
19
  "instruction": "I have these two primers already ordered and I want to know whether they are any good together before I run them. Report the Tm of the forward primer, and flag anything structurally worrying about the pair.\n\nForward: ATTACTCCTGCTCTTCCCATAC\nReverse: CACTACTCCTCTGTACGCAC",
17
20
  "timeout_ms": 240000,
18
- "expect_tools": ["molbio_primer_check"],
21
+ "expect_tools": [
22
+ "molbio_primer_check"
23
+ ],
19
24
  "assertions": [
20
- { "where": "tool", "pattern": "primer1: 22 bp, GC 45\\.45%", "note": "the forward primer's own metrics" },
21
- { "where": "tool", "pattern": "primer2: 20 bp, GC 55%" },
22
- { "where": "tool", "pattern": "self-any 7 \\(≤8\\)", "note": "the reverse primer's self-complementarity, right at the Primer3 limit" },
23
- { "where": "tool", "pattern": "pair \\(dimer\\): score 14", "note": "the pair's dimer score" },
24
- { "where": "answer", "pattern": "53\\.9|54\\b", "note": "the forward primer's Tm, 53.97 °C" }
25
+ {
26
+ "where": "tool",
27
+ "pattern": "primer1: 22 bp, GC 45\\.45%",
28
+ "note": "the forward primer's own metrics"
29
+ },
30
+ {
31
+ "where": "tool",
32
+ "pattern": "primer2: 20 bp, GC 55%"
33
+ },
34
+ {
35
+ "where": "tool",
36
+ "pattern": "self-any 7 \\(≤8\\)",
37
+ "note": "the reverse primer's self-complementarity, right at the Primer3 limit"
38
+ },
39
+ {
40
+ "where": "tool",
41
+ "pattern": "pair \\(dimer\\): score 14",
42
+ "note": "the pair's dimer score"
43
+ },
44
+ {
45
+ "where": "answer",
46
+ "pattern": "53\\.9|54\\b",
47
+ "note": "the forward primer's Tm, 53.97 °C"
48
+ }
25
49
  ]
26
50
  },
27
51
  {
28
52
  "id": "taqman-assay",
29
53
  "category": "primers",
30
54
  "tier": "full",
31
- "covers": ["molbio_design_taqman"],
55
+ "covers": [
56
+ "molbio_design_taqman"
57
+ ],
32
58
  "instruction": "Design a TaqMan (5' nuclease) assay on this template. I want primers plus a hydrolysis probe between them, and I want the probe to sit at least a few degrees above the primers in Tm.\n\nTemplate:\n{template:400:7}",
33
59
  "timeout_ms": 300000,
34
- "expect_tools": ["molbio_design_taqman"],
60
+ "expect_tools": [
61
+ "molbio_design_taqman"
62
+ ],
35
63
  "assertions": [
36
64
  {
37
65
  "where": "tool",
@@ -48,21 +76,39 @@
48
76
  "pattern": "vs the hotter primer",
49
77
  "note": "the probe-vs-primer Tm margin, which the tool reports per candidate"
50
78
  },
51
- { "where": "answer", "pattern": "probe|Probe" }
79
+ {
80
+ "where": "answer",
81
+ "pattern": "probe|Probe"
82
+ }
52
83
  ]
53
84
  },
54
85
  {
55
86
  "id": "intron-qpcr",
56
87
  "category": "primers",
57
88
  "tier": "full",
58
- "covers": ["molbio_design_intron_primers"],
89
+ "covers": [
90
+ "molbio_design_intron_primers"
91
+ ],
59
92
  "instruction": "I need a qPCR assay that will NOT amplify contaminating genomic DNA. Here is the genomic sequence and the two exon coordinates — design a pair where the forward primer spans the exon-exon junction. Tell me the spliced amplicon size and how far apart the primers sit on the genome.\n\n{seq:intron_genomic}\n\nExons: 1-400 and 701-1100 (1-based).",
60
93
  "timeout_ms": 300000,
61
- "expect_tools": ["molbio_design_intron_primers"],
94
+ "expect_tools": [
95
+ "molbio_design_intron_primers"
96
+ ],
62
97
  "assertions": [
63
- { "where": "tool", "pattern": "forward primer spans an exon-exon junction", "note": "the property that makes the assay genomic-DNA-proof" },
64
- { "where": "tool", "pattern": "spliced amplicon 320-406 \\(87 bp\\), genomic span 387 bp" },
65
- { "where": "tool", "pattern": "junction 12\\+6 bp", "note": "bases contributed on each side of the junction" },
98
+ {
99
+ "where": "tool",
100
+ "pattern": "forward primer spans an exon-exon junction",
101
+ "note": "the property that makes the assay genomic-DNA-proof"
102
+ },
103
+ {
104
+ "where": "tool",
105
+ "pattern": "spliced amplicon 320-406 \\(87 bp\\), genomic span 387 bp"
106
+ },
107
+ {
108
+ "where": "tool",
109
+ "pattern": "junction 12\\+6 bp",
110
+ "note": "bases contributed on each side of the junction"
111
+ },
66
112
  {
67
113
  "where": "answer",
68
114
  "pattern": "splic|junction|genomic|intron|87|387",
@@ -74,12 +120,19 @@
74
120
  "id": "multiplex-panel",
75
121
  "category": "primers",
76
122
  "tier": "full",
77
- "covers": ["molbio_multiplex_check"],
78
- "instruction": "I want to run these two primer pairs in the same multiplex tube. The first pair is on target A (template below), the second on target B. Check them against each other and tell me whether they interfere — dimers, unintended annealing, or bands I will not be able to tell apart.\n\nTarget A template:\n{template:400:7}\n\nPair A: forward ATTACTCCTGCTCTTCCCATAC (Tm 58.4), reverse CACTACTCCTCTGTACGCAC (Tm 58.9)\nPair B: forward GGGTTTCCCAAATTTGGGCCCA, reverse TTTGGGCCCAAATTTGGGAAAC\nTemplate B:\n{template:400:8}",
123
+ "covers": [
124
+ "molbio_multiplex_check"
125
+ ],
126
+ "instruction": "I want to run these two primer pairs in the same multiplex tube. The first pair is on target A (template below), the second on target B. Check them against each other and tell me whether they interfere — dimers, unintended annealing, or bands I will not be able to tell apart. Also give me the recommended annealing temperature for the panel and a starting primer concentration for each pair.\n\nTarget A template:\n{template:400:7}\n\nPair A: forward ATTACTCCTGCTCTTCCCATAC (Tm 58.4), reverse CACTACTCCTCTGTACGCAC (Tm 58.9)\nPair B: forward GGGTTTCCCAAATTTGGGCCCA, reverse TTTGGGCCCAAATTTGGGAAAC\nTemplate B:\n{template:400:8}",
79
127
  "timeout_ms": 300000,
80
- "expect_tools": ["molbio_multiplex_check"],
128
+ "expect_tools": [
129
+ "molbio_multiplex_check"
130
+ ],
81
131
  "assertions": [
82
- { "where": "tool", "pattern": "multiplex panel: 2 target\\(s\\) on 2 template\\(s\\), 4 primer\\(s\\)" },
132
+ {
133
+ "where": "tool",
134
+ "pattern": "multiplex panel: 2 target\\(s\\) on 2 template\\(s\\), 4 primer\\(s\\)"
135
+ },
83
136
  {
84
137
  "where": "tool",
85
138
  "pattern": "primer-primer interactions above the dimer threshold: [0-9]+",
@@ -94,6 +147,66 @@
94
147
  "where": "answer",
95
148
  "pattern": "dimer|interfere|conflict|anneal",
96
149
  "note": "the panel verdict. Note the measured behaviour this task must tolerate: a model that FIRST checks each pair on its own template often finds — correctly — that a pair as written does not amplify at all, and answers that instead. That is a better answer than 'no dimer conflict', not a worse one."
150
+ },
151
+ {
152
+ "where": "tool",
153
+ "pattern": "annealing temperature: [0-9.]+ °C \\(range [0-9.]+-[0-9.]+ °C\\)",
154
+ "note": "the panel-level annealing recommendation (lowest primer Tm minus the offset)"
155
+ },
156
+ {
157
+ "where": "tool",
158
+ "pattern": "primer concentration plan \\(nM\\):",
159
+ "note": "the starting primer-concentration plan"
160
+ },
161
+ {
162
+ "where": "answer",
163
+ "pattern": "anneal|temperature|concentration",
164
+ "note": "the model reports the new planning outputs, not only the dimer verdict"
165
+ }
166
+ ]
167
+ },
168
+ {
169
+ "id": "taqman-mgb",
170
+ "category": "primers",
171
+ "tier": "full",
172
+ "covers": [
173
+ "molbio_design_taqman"
174
+ ],
175
+ "instruction": "Design a TaqMan assay with an MGB probe on this template. Use an MGB probe, write the ordering sheet to mgb-order.csv, and tell me the probe length and Tm window the designer used, the top-ranked probe, and the 5'/3' modifications on its ordering row.\n\nTemplate:\n{template:400:7}",
176
+ "timeout_ms": 300000,
177
+ "expect_tools": [
178
+ "molbio_design_taqman"
179
+ ],
180
+ "assertions": [
181
+ {
182
+ "where": "tool",
183
+ "pattern": "Probe chemistry: TaqMan MGB",
184
+ "note": "the MGB chemistry is selected, not silently the standard one"
185
+ },
186
+ {
187
+ "where": "tool",
188
+ "pattern": "13-20 nt, Tm 48-60",
189
+ "note": "the MGB length/Tm window, on the unmodified NN scale"
190
+ },
191
+ {
192
+ "where": "tool",
193
+ "pattern": "P TTGCTACGCCTGT",
194
+ "note": "the top-ranked MGB probe"
195
+ },
196
+ {
197
+ "where": "tool",
198
+ "pattern": "ordering CSV \\([0-9]+ oligo row\\(s\\)\\) saved to",
199
+ "note": "the ordering sheet was written"
200
+ },
201
+ {
202
+ "where": "tool",
203
+ "pattern": "MGB probes: the reported probe Tm is the unmodified DNA NN estimate",
204
+ "note": "the tool refuses to fabricate a modified Tm"
205
+ },
206
+ {
207
+ "where": "answer",
208
+ "pattern": "MGB|unmodified|modif",
209
+ "note": "the model explains the MGB Tm caveat or the modifications"
97
210
  }
98
211
  ]
99
212
  }
@@ -12,67 +12,144 @@
12
12
  "id": "plasmid-digest",
13
13
  "category": "restriction",
14
14
  "tier": "full",
15
- "covers": ["molbio_restriction_sites"],
15
+ "covers": [
16
+ "molbio_restriction_sites"
17
+ ],
16
18
  "instruction": "I have pUC118.dna in my working directory. Show me the restriction map for EcoRI, HindIII and PvuII on the circular plasmid: where each enzyme cuts, and what fragments each one produces. Then tell me which of the three cuts the plasmid more than once.",
17
19
  "timeout_ms": 240000,
18
- "expect_tools": ["molbio_restriction_sites"],
19
- "fixtures": { "pUC118.dna": "test/fixtures/pUC118.dna" },
20
+ "expect_tools": [
21
+ "molbio_restriction_sites"
22
+ ],
23
+ "fixtures": {
24
+ "pUC118.dna": "test/fixtures/pUC118.dna"
25
+ },
20
26
  "assertions": [
21
- { "where": "tool", "pattern": "EcoRI \\(G\\^AATTC\\):[\\s\\S]*?cut at 927", "note": "EcoRI's single cut on pUC118" },
22
- { "where": "tool", "pattern": "HindIII \\(A\\^AGCTT\\):[\\s\\S]*?cut at 876" },
23
- { "where": "tool", "pattern": "PvuII \\(CAG\\^CTG\\):[\\s\\S]*?cut at 785" },
24
- { "where": "tool", "pattern": "PvuII[\\s\\S]*?2 fragment\\(s\\): 2840, 322 bp", "note": "PvuII is the double cutter" },
25
- { "where": "answer", "pattern": "PvuII" },
26
- { "where": "answer", "pattern": "2840" },
27
- { "where": "answer", "pattern": "322" }
27
+ {
28
+ "where": "tool",
29
+ "pattern": "EcoRI \\(G\\^AATTC\\):[\\s\\S]*?cut at 927",
30
+ "note": "EcoRI's single cut on pUC118"
31
+ },
32
+ {
33
+ "where": "tool",
34
+ "pattern": "HindIII \\(A\\^AGCTT\\):[\\s\\S]*?cut at 876"
35
+ },
36
+ {
37
+ "where": "tool",
38
+ "pattern": "PvuII \\(CAG\\^CTG\\):[\\s\\S]*?cut at 785"
39
+ },
40
+ {
41
+ "where": "tool",
42
+ "pattern": "PvuII[\\s\\S]*?2 fragment\\(s\\): 2840, 322 bp",
43
+ "note": "PvuII is the double cutter"
44
+ },
45
+ {
46
+ "where": "answer",
47
+ "pattern": "PvuII"
48
+ },
49
+ {
50
+ "where": "answer",
51
+ "pattern": "2[,.]?840",
52
+ "note": "the larger PvuII fragment; accepts the thousands separator a human writes"
53
+ },
54
+ {
55
+ "where": "answer",
56
+ "pattern": "322"
57
+ }
28
58
  ]
29
59
  },
30
60
  {
31
61
  "id": "double-digest-buffer",
32
62
  "category": "restriction",
33
63
  "tier": "core",
34
- "covers": ["molbio_double_digest"],
64
+ "covers": [
65
+ "molbio_double_digest"
66
+ ],
35
67
  "instruction": "I have a 512 bp linear construct in the workspace as a sequence I will paste, but simpler: use the sequence below. Cut it with EcoRI and HindIII together. Do these two enzymes share a reaction buffer, and what fragment sizes should I see on the gel?\n\n{seq:construct}",
36
68
  "timeout_ms": 240000,
37
- "expect_tools": ["molbio_double_digest"],
69
+ "expect_tools": [
70
+ "molbio_double_digest"
71
+ ],
38
72
  "assertions": [
39
- { "where": "tool", "pattern": "shared buffer\\(s\\):[^\\n]*CutSmart", "note": "a shared buffer, so a single-tube digest is fine" },
73
+ {
74
+ "where": "tool",
75
+ "pattern": "shared buffer\\(s\\):[^\\n]*CutSmart",
76
+ "note": "a shared buffer, so a single-tube digest is fine"
77
+ },
40
78
  {
41
79
  "where": "tool",
42
80
  "pattern": "→ 206, 205, 101 bp",
43
81
  "note": "THREE fragments, not two: the two site insertions add 6 bp each, so the two sites are 206 bp apart and the digest yields three pieces. This was probed, not reasoned about — the first draft of the fixture documented '200 + 300 bp' and was wrong."
44
82
  },
45
- { "where": "answer", "pattern": "206" },
46
- { "where": "answer", "pattern": "205" },
47
- { "where": "answer", "pattern": "101" }
83
+ {
84
+ "where": "answer",
85
+ "pattern": "206"
86
+ },
87
+ {
88
+ "where": "answer",
89
+ "pattern": "205"
90
+ },
91
+ {
92
+ "where": "answer",
93
+ "pattern": "101"
94
+ }
48
95
  ]
49
96
  },
50
97
  {
51
98
  "id": "pcr-simulation",
52
99
  "category": "pcr",
53
100
  "tier": "full",
54
- "covers": ["molbio_pcr_simulate"],
101
+ "covers": [
102
+ "molbio_pcr_simulate"
103
+ ],
55
104
  "instruction": "Check whether this primer pair amplifies the template I am pasting, and tell me the product size and coordinates. Use the forward primer as given and the reverse primer as given — both are written 5' to 3' as I would order them.\n\nTemplate (512 bp):\n{seq:construct}\n\nForward primer: {seq:pcr_forward}\nReverse primer: {seq:pcr_reverse}",
56
105
  "timeout_ms": 240000,
57
- "expect_tools": ["molbio_pcr_simulate"],
106
+ "expect_tools": [
107
+ "molbio_pcr_simulate"
108
+ ],
58
109
  "assertions": [
59
- { "where": "tool", "pattern": "1 product\\(s\\) of 201 bp" },
60
- { "where": "tool", "pattern": "201 bp: 101-120 → 282-301", "note": "the amplicon's coordinates on the top strand" },
61
- { "where": "tool", "pattern": "specific", "note": "the tool's verdict word for a single clean product" },
62
- { "where": "answer", "pattern": "201" },
63
- { "where": "answer", "pattern": "101" },
64
- { "where": "answer", "pattern": "301" }
110
+ {
111
+ "where": "tool",
112
+ "pattern": "1 product\\(s\\) of 201 bp"
113
+ },
114
+ {
115
+ "where": "tool",
116
+ "pattern": "201 bp: 101-120 → 282-301",
117
+ "note": "the amplicon's coordinates on the top strand"
118
+ },
119
+ {
120
+ "where": "tool",
121
+ "pattern": "specific",
122
+ "note": "the tool's verdict word for a single clean product"
123
+ },
124
+ {
125
+ "where": "answer",
126
+ "pattern": "201"
127
+ },
128
+ {
129
+ "where": "answer",
130
+ "pattern": "101"
131
+ },
132
+ {
133
+ "where": "answer",
134
+ "pattern": "301"
135
+ }
65
136
  ]
66
137
  },
67
138
  {
68
139
  "id": "gel-preview",
69
140
  "category": "visualisation",
70
141
  "tier": "full",
71
- "covers": ["molbio_virtual_gel"],
142
+ "covers": [
143
+ "molbio_virtual_gel"
144
+ ],
72
145
  "instruction": "I am running an EcoRI+HindIII double digest of pUC118.dna (full length 3162 bp, in your working directory) on a 1 kb ladder gel. Draw me the expected band pattern, and tell me how many bands I should see and whether the small fragment will be visible.",
73
146
  "timeout_ms": 240000,
74
- "expect_tools": ["molbio_virtual_gel"],
75
- "fixtures": { "pUC118.dna": "test/fixtures/pUC118.dna" },
147
+ "expect_tools": [
148
+ "molbio_virtual_gel"
149
+ ],
150
+ "fixtures": {
151
+ "pUC118.dna": "test/fixtures/pUC118.dna"
152
+ },
76
153
  "assertions": [
77
154
  {
78
155
  "where": "tool",
@@ -84,13 +161,20 @@
84
161
  "pattern": "3111|3\\.1 kb|3\\.1kb|3162 ?- ?51",
85
162
  "note": "the backbone fragment. Accepted as the exact size or its conventional `~3.1 kb` shorthand: both are correct, and demanding the digits graded how the model chose to round."
86
163
  },
87
- { "where": "answer", "pattern": "\\b51\\b", "note": "the small fragment's exact size — pinned, because it explains the whole answer" },
164
+ {
165
+ "where": "answer",
166
+ "pattern": "\\b51\\b",
167
+ "note": "the small fragment's exact size — pinned, because it explains the whole answer"
168
+ },
88
169
  {
89
170
  "where": "answer",
90
171
  "pattern": "1 band|one band|2 band|two band|1 fragment|2 fragment|single .{0,12}band",
91
172
  "note": "the band count. The model is EXPECTED to distinguish the two theoretical fragments from the single band a 1 kb-ladder gel actually resolves, so both counts are accepted."
92
173
  },
93
- { "where": "answer", "pattern": "not visible|too small|will not|won't|below|faint|run off|dye front|below detection" }
174
+ {
175
+ "where": "answer",
176
+ "pattern": "not visible|too small|will not|won't|below|faint|run off|dye front|below detection"
177
+ }
94
178
  ]
95
179
  }
96
180
  ]